7#include "fifechan/backends/sdl3/graphics.hpp"
24#include "fifechan/backends/sdl3/image.hpp"
25#include "fifechan/backends/sdl3/truetypefont.hpp"
26#include "fifechan/exception.hpp"
27#include "fifechan/font.hpp"
28#include "fifechan/image.hpp"
35 constexpr float kColorNormalizationFactor = 1.0F / 255.0F;
37 void setRenderDrawColor(SDL_Renderer* renderer, fcn::Color
const & color)
39 SDL_SetRenderDrawColorFloat(
41 static_cast<float>(color.
r) * kColorNormalizationFactor,
42 static_cast<float>(color.
g) * kColorNormalizationFactor,
43 static_cast<float>(color.
b) * kColorNormalizationFactor,
44 static_cast<float>(color.
a) * kColorNormalizationFactor);
47 SDL_FColor toSDLVertexColor(fcn::Color
const & color)
50 .r =
static_cast<float>(color.
r) * kColorNormalizationFactor,
51 .g =
static_cast<float>(color.
g) * kColorNormalizationFactor,
52 .b =
static_cast<float>(color.
b) * kColorNormalizationFactor,
53 .a =
static_cast<float>(color.
a) * kColorNormalizationFactor};
56 SDL_Vertex makeSolidVertex(
float x,
float y, SDL_FColor
const & color)
58 return SDL_Vertex{.position = {.x = x, .y = y}, .color = color};
62 Graphics::Graphics() : mAlpha(false)
66 Graphics::~Graphics() =
default;
100 rect.w = clip_rect.
width;
101 rect.h = clip_rect.
height;
119 rect.x = clip_rect.
x;
120 rect.y = clip_rect.
y;
121 rect.w = clip_rect.
width;
122 rect.h = clip_rect.
height;
136 "Clip stack is empty, perhaps you"
137 "called a draw function outside of _beginDraw() and _endDraw()?");
143 src.x =
static_cast<float>(srcX);
144 src.y =
static_cast<float>(srcY);
145 src.w =
static_cast<float>(width);
146 src.h =
static_cast<float>(height);
147 dst.x =
static_cast<float>(dstX + top.
xOffset);
148 dst.y =
static_cast<float>(dstY + top.
yOffset);
149 dst.w =
static_cast<float>(width);
150 dst.h =
static_cast<float>(height);
152 auto const * srcImage =
dynamic_cast<Image const *
>(image);
154 if (srcImage ==
nullptr) {
155 throwException(
"Trying to draw an image of unknown format, must be an Image.");
158 SDL_Texture* texture = srcImage->
getTexture();
159 if (texture !=
nullptr) {
168 "Clip stack is empty, perhaps you"
169 "called a draw function outside of _beginDraw() and _endDraw()?");
183 rect.x =
static_cast<float>(area.
x);
184 rect.y =
static_cast<float>(area.
y);
185 rect.w =
static_cast<float>(area.
width);
186 rect.h =
static_cast<float>(area.
height);
198 "Clip stack is empty, perhaps you"
199 "called a draw function outside of _beginDraw() and _endDraw()?");
221 "Clip stack is empty, perhaps you"
222 "called a draw function outside of _beginDraw() and _endDraw()?");
230 if (y < top.y || y >= top.
y + top.
height) {
247 if (top.
x + top.
width <= x2) {
248 if (top.
x + top.
width <= x1) {
251 x2 = top.
x + top.
width - 1;
264 "Clip stack is empty, perhaps you"
265 "called a draw function outside of _beginDraw() and _endDraw()?");
273 if (x < top.x || x >= top.
x + top.
width) {
290 if (top.
y + top.
height <= y2) {
291 if (top.
y + top.
height <= y1) {
305 int const x1 = rectangle.
x;
306 int const x2 = rectangle.
x + rectangle.
width - 1;
307 int const y1 = rectangle.
y;
308 int const y2 = rectangle.
y + rectangle.
height - 1;
322 "Clip stack is empty, perhaps you"
323 "called a draw function outside of _beginDraw() and _endDraw()?");
342 "Clip stack is empty, perhaps you"
343 "called a draw function outside of _beginDraw() and _endDraw()?");
357 if (x1 == x2 && y1 == y2) {
367 auto const dx =
static_cast<float>(x2 - x1);
368 auto const dy =
static_cast<float>(y2 - y1);
369 float const length = std::sqrt((dx * dx) + (dy * dy));
371 if (length < 0.001F) {
377 float const offsetX = (dy / length) * (
static_cast<float>(width) / 2.0F);
378 float const offsetY = (dx / length) * (
static_cast<float>(width) / 2.0F);
383 std::vector<SDL_Vertex> vertices = {
385 .position = {.x =
static_cast<float>(x1) - offsetX, .y =
static_cast<float>(y1) + offsetY},
387 .r =
static_cast<float>(
mColor.r) / 255.0F,
388 .
g =
static_cast<float>(
mColor.g) / 255.0F,
389 .b =
static_cast<float>(
mColor.b) / 255.0F,
390 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
392 .position = {.x =
static_cast<float>(x1) + offsetX, .y =
static_cast<float>(y1) - offsetY},
394 .r =
static_cast<float>(
mColor.r) / 255.0F,
395 .
g =
static_cast<float>(
mColor.g) / 255.0F,
396 .b =
static_cast<float>(
mColor.b) / 255.0F,
397 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
399 .position = {.x =
static_cast<float>(x2) + offsetX, .y =
static_cast<float>(y2) - offsetY},
401 .r =
static_cast<float>(
mColor.r) / 255.0F,
402 .
g =
static_cast<float>(
mColor.g) / 255.0F,
403 .b =
static_cast<float>(
mColor.b) / 255.0F,
404 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
406 .position = {.x =
static_cast<float>(x2) - offsetX, .y =
static_cast<float>(y2) + offsetY},
408 .r =
static_cast<float>(
mColor.r) / 255.0F,
409 .
g =
static_cast<float>(
mColor.g) / 255.0F,
410 .b =
static_cast<float>(
mColor.b) / 255.0F,
411 .
a =
static_cast<float>(
mColor.a) / 255.0F}}};
415 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
421 static_cast<int>(vertices.size()),
423 static_cast<int>(indices.size()));
432 "Clip stack is empty, perhaps you"
433 "called a draw function outside of _beginDraw() and _endDraw()?");
447 int const radius = std::max(1,
static_cast<int>(width) / 2);
450 if (x1 == x2 && y1 == y2) {
459 int const dx = x2 - x1;
460 int const dy = y2 - y1;
461 int const stepCount = std::max(std::abs(dx), std::abs(dy));
465 int const circleSegments = std::max(8, radius * 2);
467 for (
int step = 0; step <= stepCount; ++step) {
468 int const centerX = x1 + ((dx * step) / stepCount);
469 int const centerY = y1 + ((dy * step) / stepCount);
472 std::vector<SDL_Vertex> vertices;
473 vertices.reserve(circleSegments + 2);
480 .position = {.x =
static_cast<float>(centerX), .y =
static_cast<float>(centerY)},
482 .r =
static_cast<float>(
mColor.r) / 255.0F,
483 .
g =
static_cast<float>(
mColor.g) / 255.0F,
484 .b =
static_cast<float>(
mColor.b) / 255.0F,
485 .
a =
static_cast<float>(
mColor.a) / 255.0F}});
489 for (
int i = 0; i <= circleSegments; ++i) {
491 2.0F * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(circleSegments);
492 float const x =
static_cast<float>(centerX) + (radius * std::cos(angle));
493 float const y =
static_cast<float>(centerY) + (radius * std::sin(angle));
498 .position = {.x = x, .y = y},
500 .r =
static_cast<float>(
mColor.r) / 255.0F,
501 .
g =
static_cast<float>(
mColor.g) / 255.0F,
502 .b =
static_cast<float>(
mColor.b) / 255.0F,
503 .
a =
static_cast<float>(
mColor.a) / 255.0F}});
508 std::vector<int> indices;
509 indices.reserve(
static_cast<std::size_t
>(circleSegments) * 3);
510 for (
int i = 1; i <= circleSegments; ++i) {
511 indices.push_back(0);
512 indices.push_back(i);
513 indices.push_back(i + 1 <= circleSegments ? i + 1 : 1);
520 static_cast<int>(vertices.size()),
522 static_cast<int>(indices.size()));
532 "Clip stack is empty, perhaps you"
533 "called a draw function outside of _beginDraw() and _endDraw()?");
537 int const x0 = center.x + top.
xOffset;
538 int const y0 = center.y + top.
yOffset;
542 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
543 std::vector<SDL_Vertex> vertices;
544 vertices.reserve(numSegments + 2);
545 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
548 vertices.push_back(makeSolidVertex(
static_cast<float>(x0),
static_cast<float>(y0), vertexColor));
551 for (
int i = 0; i <= numSegments; ++i) {
553 2.0F * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(numSegments);
554 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
555 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
556 vertices.push_back(makeSolidVertex(x, y, vertexColor));
560 std::vector<int> indices;
561 indices.reserve(
static_cast<std::size_t
>(numSegments) * 3);
562 for (
int i = 1; i <= numSegments; ++i) {
563 indices.push_back(0);
564 indices.push_back(i);
565 indices.push_back(i + 1);
574 static_cast<int>(vertices.size()),
576 static_cast<int>(indices.size()));
582 int normalizeAngle(
int angle)
594 std::vector<fcn::Point> points = controlPoints;
595 while (points.size() > 1) {
596 std::vector<fcn::Point> next;
597 for (
size_t i = 0; i + 1 < points.size(); ++i) {
598 float const x = ((1.0F - t) *
static_cast<float>(points.at(i).x)) +
599 (t *
static_cast<float>(points.at(i + 1).x));
600 float const y = ((1.0F - t) *
static_cast<float>(points.at(i).y)) +
601 (t *
static_cast<float>(points.at(i + 1).y));
602 next.emplace_back(
static_cast<int>(x),
static_cast<int>(y));
604 points = std::move(next);
606 return points.empty() ?
fcn::Point{} : points.at(0);
614 "Clip stack is empty, perhaps you"
615 "called a draw function outside of _beginDraw() and _endDraw()?");
619 int const x0 = center.x + top.
xOffset;
620 int const y0 = center.y + top.
yOffset;
624 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
625 std::vector<SDL_Vertex> vertices;
626 vertices.reserve(numSegments + 1);
627 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
629 for (
int i = 0; i <= numSegments; ++i) {
631 2.0F * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(numSegments);
632 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
633 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
634 vertices.push_back(makeSolidVertex(x, y, vertexColor));
638 std::vector<int> indices;
639 indices.reserve(
static_cast<std::size_t
>(numSegments) * 2);
640 for (
int i = 0; i < numSegments; ++i) {
641 indices.push_back(i);
642 indices.push_back(i + 1);
651 static_cast<int>(vertices.size()),
653 static_cast<int>(indices.size()));
658 Point const & ,
unsigned int ,
int ,
int )
664 Point const & ,
unsigned int ,
int ,
int )
673 "Clip stack is empty, perhaps you"
674 "called a draw function outside of _beginDraw() and _endDraw()?");
683 std::vector<SDL_FPoint> points;
684 points.reserve(segments + 1);
686 for (
int i = 0; i <= segments; ++i) {
687 float const t =
static_cast<float>(i) /
static_cast<float>(segments);
688 fcn::Point const point = bezierPoint(controlPoints, t);
691 .x =
static_cast<float>(point.x + top.
xOffset), .y =
static_cast<float>(point.y + top.
yOffset)});
698 SDL_RenderLines(
mRenderTarget, points.data(),
static_cast<int>(points.size()));
705 float const halfWidth =
static_cast<float>(width) / 2.0F;
707 for (
size_t i = 0; i < points.size() - 1; ++i) {
708 float const x1 = points.at(i).x;
709 float const y1 = points.at(i).y;
710 float const x2 = points.at(i + 1).x;
711 float const y2 = points.at(i + 1).y;
713 float const dx = x2 - x1;
714 float const dy = y2 - y1;
715 float const length = std::sqrt((dx * dx) + (dy * dy));
717 if (length < 0.001F) {
722 float const offsetX = (dy / length) * halfWidth;
723 float const offsetY = (dx / length) * halfWidth;
728 std::vector<SDL_Vertex> vertices = {
730 .position = {.x = x1 - offsetX, .y = y1 + offsetY},
732 {.r =
static_cast<float>(
mColor.r) / 255.0F,
733 .
g =
static_cast<float>(
mColor.g) / 255.0F,
734 .b =
static_cast<float>(
mColor.b) / 255.0F,
735 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
737 .position = {.x = x1 + offsetX, .y = y1 - offsetY},
739 {.r =
static_cast<float>(
mColor.r) / 255.0F,
740 .
g =
static_cast<float>(
mColor.g) / 255.0F,
741 .b =
static_cast<float>(
mColor.b) / 255.0F,
742 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
744 .position = {.x = x2 + offsetX, .y = y2 - offsetY},
746 {.r =
static_cast<float>(
mColor.r) / 255.0F,
747 .
g =
static_cast<float>(
mColor.g) / 255.0F,
748 .b =
static_cast<float>(
mColor.b) / 255.0F,
749 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
751 .position = {.x = x2 - offsetX, .y = y2 + offsetY},
753 .r =
static_cast<float>(
mColor.r) / 255.0F,
754 .
g =
static_cast<float>(
mColor.g) / 255.0F,
755 .b =
static_cast<float>(
mColor.b) / 255.0F,
756 .
a =
static_cast<float>(
mColor.a) / 255.0F}}};
759 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
764 static_cast<int>(vertices.size()),
766 static_cast<int>(indices.size()));
777 "Clip stack is empty, perhaps you"
778 "called a draw function outside of _beginDraw() and _endDraw()?");
781 if (points.size() < 2) {
788 std::vector<SDL_FPoint> sdlPoints;
789 sdlPoints.reserve(points.size());
790 std::ranges::transform(points, std::back_inserter(sdlPoints), [&top](
auto const & point) {
792 .x =
static_cast<float>(point.x + top.
xOffset), .y =
static_cast<float>(point.y + top.
yOffset)};
799 SDL_RenderLines(
mRenderTarget, sdlPoints.data(),
static_cast<int>(sdlPoints.size()));
806 float const halfWidth =
static_cast<float>(width) / 2.0F;
808 for (
size_t i = 0; i < sdlPoints.size() - 1; ++i) {
809 float const x1 = sdlPoints.at(i).x;
810 float const y1 = sdlPoints.at(i).y;
811 float const x2 = sdlPoints.at(i + 1).x;
812 float const y2 = sdlPoints.at(i + 1).y;
814 float const dx = x2 - x1;
815 float const dy = y2 - y1;
816 float const length = std::sqrt((dx * dx) + (dy * dy));
818 if (length < 0.001F) {
823 float const offsetX = (dy / length) * halfWidth;
824 float const offsetY = (dx / length) * halfWidth;
829 std::vector<SDL_Vertex> vertices = {
831 .position = {.x = x1 - offsetX, .y = y1 + offsetY},
833 {.r =
static_cast<float>(
mColor.r) / 255.0F,
834 .
g =
static_cast<float>(
mColor.g) / 255.0F,
835 .b =
static_cast<float>(
mColor.b) / 255.0F,
836 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
838 .position = {.x = x1 + offsetX, .y = y1 - offsetY},
840 {.r =
static_cast<float>(
mColor.r) / 255.0F,
841 .
g =
static_cast<float>(
mColor.g) / 255.0F,
842 .b =
static_cast<float>(
mColor.b) / 255.0F,
843 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
845 .position = {.x = x2 + offsetX, .y = y2 - offsetY},
847 {.r =
static_cast<float>(
mColor.r) / 255.0F,
848 .
g =
static_cast<float>(
mColor.g) / 255.0F,
849 .b =
static_cast<float>(
mColor.b) / 255.0F,
850 .
a =
static_cast<float>(
mColor.a) / 255.0F}},
852 .position = {.x = x2 - offsetX, .y = y2 + offsetY},
854 .r =
static_cast<float>(
mColor.r) / 255.0F,
855 .
g =
static_cast<float>(
mColor.g) / 255.0F,
856 .b =
static_cast<float>(
mColor.b) / 255.0F,
857 .
a =
static_cast<float>(
mColor.a) / 255.0F}}};
860 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
865 static_cast<int>(vertices.size()),
867 static_cast<int>(indices.size()));
888 return std::make_shared<TrueTypeFont>(filename, size);
895 "Clip stack is empty, perhaps you"
896 "called a draw function outside of _beginDraw() and _endDraw()?");
901 destination.x +=
static_cast<float>(top.
xOffset);
902 destination.y +=
static_cast<float>(top.
yOffset);
903 destination.w = source.w;
904 destination.h = source.h;
906 SDL_RenderTexture(
mRenderTarget, texture, &source, &destination);
A rectangle specifically used for clipping rendering regions.
int xOffset
Holds the x offset of the x coordinate.
int yOffset
Holds the y offset of the y coordinate.
uint8_t a
Alpha color component (0-255).
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).
virtual void popClipArea()
Removes the top most clip area from the stack.
virtual bool pushClipArea(Rectangle area)
Pushes a clip area onto the stack.
std::stack< ClipRectangle > mClipStack
Holds the clip area stack.
Abstract holder for image data.
Represents a 2D coordinate (X, Y).
Represents a rectangular area (X, Y, Width, Height).
bool isContaining(int x, int y) const
Checks the rectangle contains a point.
int width
Holds the width of the rectangle.
int y
Holds the x coordinate of the rectangle.
int x
Holds the x coordinate of the rectangle.
int height
Holds the height of the rectangle.
bool isIntersecting(Rectangle const &rectangle) const
Checks if another rectangle intersects with the rectangle.
Uint8 a
Previous alpha component from renderer.
void restoreRenderColor()
Restore the rendering color after drawing.
void drawFillCircle(Point const ¢er, unsigned int radius) override
Draws a filled circle.
void drawPoint(int x, int y) override
Draws a single point/pixel.
void drawImage(fcn::Image const *image, int srcX, int srcY, int dstX, int dstY, int width, int height) override
Draws a part of an image.
void drawRoundStroke(int x1, int y1, int x2, int y2, unsigned int width) override
Draws a round brush stroke along the line segment.
int mHeight
Screen height.
void saveRenderColor()
Save the current rendering color before drawing.
virtual SDL_Renderer * getRenderTarget() const
Gets the target SDL_Renderer.
void drawFillCircleSegment(Point const ¢er, unsigned int radius, int startAngle, int endAngle) override
Draws a filled circle segment.
Color const & getColor() const override
Gets the color to use when drawing.
void _beginDraw() override
Initializes drawing.
void drawPolyLine(PointVector const &points, unsigned int width) override
Draws lines between points with given width.
SDL_Renderer * mRenderTarget
The SDL_Renderer used for accelerated drawing.
void drawRectangle(Rectangle const &rectangle) override
Draws a simple, non-filled rectangle with a one pixel width.
Uint8 r
Cached renderer color components (previous renderer color).
void _endDraw() override
Deinitializes the drawing process.
void drawLine(int x1, int y1, int x2, int y2) override
Draws a line.
void drawHorizontalLine(int x1, int y, int x2)
Draws a horizontal line.
void drawCircle(Point const ¢er, unsigned int radius) override
Draws a simple, non-filled circle with a one pixel width.
virtual void drawSDLTexture(SDL_Texture *texture, SDL_FRect source, SDL_FRect destination)
Draws an SDL_Texture on the target surface.
void drawVerticalLine(int x, int y1, int y2)
Draws a vertical line.
void setColor(Color const &color) override
Sets the color to use when drawing.
bool mAlpha
Whether alpha blending is enabled.
bool pushClipArea(fcn::Rectangle area) override
Pushes a clip area onto the stack.
void fillRectangle(Rectangle const &rectangle) override
Draws a filled rectangle.
void popClipArea() override
Removes the top most clip area from the stack.
void drawCircleSegment(Point const ¢er, unsigned int radius, int startAngle, int endAngle) override
Draws a simple, non-filled circle segment with a one pixel width.
Uint8 b
Previous blue component from renderer.
Uint8 g
Previous green component from renderer.
std::shared_ptr< Font > createFont(std::string const &filename, int size) override
Creates a font for this graphics backend.
void drawBezier(PointVector const &points, int segments, unsigned int width) override
Draws a bezier curve.
Color mColor
Current drawing color.
virtual void setTarget(SDL_Renderer *renderer, int width, int height)
Sets the target SDL_Renderer to use for drawing.
SDL3-specific implementation of Image.
virtual SDL_Texture * getTexture() const
Gets the SDL texture for the image.
std::vector< Point > PointVector
A list of points.
void throwException(std::string const &message, std::source_location location=std::source_location::current())
Throw an Exception capturing the current source location.