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 static_cast<float>(color.
r) * kColorNormalizationFactor,
51 static_cast<float>(color.
g) * kColorNormalizationFactor,
52 static_cast<float>(color.
b) * kColorNormalizationFactor,
53 static_cast<float>(color.
a) * kColorNormalizationFactor};
56 SDL_Vertex makeSolidVertex(
float x,
float y, SDL_FColor
const & color)
58 return SDL_Vertex{.position = {x, 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);
381 std::vector<SDL_Vertex> vertices = {
383 .position = {
static_cast<float>(x1) - offsetX,
static_cast<float>(y1) + offsetY},
385 {
static_cast<float>(
mColor.r) / 255.0F,
386 static_cast<float>(
mColor.g) / 255.0F,
387 static_cast<float>(
mColor.b) / 255.0F,
388 static_cast<float>(
mColor.a) / 255.0F}},
390 .position = {
static_cast<float>(x1) + offsetX,
static_cast<float>(y1) - offsetY},
392 {
static_cast<float>(
mColor.r) / 255.0F,
393 static_cast<float>(
mColor.g) / 255.0F,
394 static_cast<float>(
mColor.b) / 255.0F,
395 static_cast<float>(
mColor.a) / 255.0F}},
397 .position = {
static_cast<float>(x2) + offsetX,
static_cast<float>(y2) - offsetY},
399 {
static_cast<float>(
mColor.r) / 255.0F,
400 static_cast<float>(
mColor.g) / 255.0F,
401 static_cast<float>(
mColor.b) / 255.0F,
402 static_cast<float>(
mColor.a) / 255.0F}},
404 .position = {
static_cast<float>(x2) - offsetX,
static_cast<float>(y2) + offsetY},
406 static_cast<float>(
mColor.r) / 255.0F,
407 static_cast<float>(
mColor.g) / 255.0F,
408 static_cast<float>(
mColor.b) / 255.0F,
409 static_cast<float>(
mColor.a) / 255.0F}}};
412 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
418 static_cast<int>(vertices.size()),
420 static_cast<int>(indices.size()));
429 "Clip stack is empty, perhaps you"
430 "called a draw function outside of _beginDraw() and _endDraw()?");
444 int const radius = std::max(1,
static_cast<int>(width) / 2);
447 if (x1 == x2 && y1 == y2) {
456 int const dx = x2 - x1;
457 int const dy = y2 - y1;
458 int const stepCount = std::max(std::abs(dx), std::abs(dy));
462 int const circleSegments = std::max(8, radius * 2);
464 for (
int step = 0; step <= stepCount; ++step) {
465 int const centerX = x1 + ((dx * step) / stepCount);
466 int const centerY = y1 + ((dy * step) / stepCount);
469 std::vector<SDL_Vertex> vertices;
470 vertices.reserve(circleSegments + 2);
475 .position = {
static_cast<float>(centerX),
static_cast<float>(centerY)},
477 static_cast<float>(
mColor.r) / 255.0F,
478 static_cast<float>(
mColor.g) / 255.0F,
479 static_cast<float>(
mColor.b) / 255.0F,
480 static_cast<float>(
mColor.a) / 255.0F}});
483 for (
int i = 0; i <= circleSegments; ++i) {
485 2.0f * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(circleSegments);
486 float const x =
static_cast<float>(centerX) + (radius * std::cos(angle));
487 float const y =
static_cast<float>(centerY) + (radius * std::sin(angle));
492 static_cast<float>(
mColor.r) / 255.0F,
493 static_cast<float>(
mColor.g) / 255.0F,
494 static_cast<float>(
mColor.b) / 255.0F,
495 static_cast<float>(
mColor.a) / 255.0F}});
499 std::vector<int> indices;
500 indices.reserve(circleSegments * 3);
501 for (
int i = 1; i <= circleSegments; ++i) {
502 indices.push_back(0);
503 indices.push_back(i);
504 indices.push_back(i + 1 <= circleSegments ? i + 1 : 1);
511 static_cast<int>(vertices.size()),
513 static_cast<int>(indices.size()));
523 "Clip stack is empty, perhaps you"
524 "called a draw function outside of _beginDraw() and _endDraw()?");
528 int const x0 = center.x + top.
xOffset;
529 int const y0 = center.y + top.
yOffset;
533 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
534 std::vector<SDL_Vertex> vertices;
535 vertices.reserve(numSegments + 2);
536 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
539 vertices.push_back(makeSolidVertex(
static_cast<float>(x0),
static_cast<float>(y0), vertexColor));
542 for (
int i = 0; i <= numSegments; ++i) {
544 2.0f * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(numSegments);
545 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
546 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
547 vertices.push_back(makeSolidVertex(x, y, vertexColor));
551 std::vector<int> indices;
552 indices.reserve(numSegments * 3);
553 for (
int i = 1; i <= numSegments; ++i) {
554 indices.push_back(0);
555 indices.push_back(i);
556 indices.push_back(i + 1 <= numSegments ? i + 1 : 1);
565 static_cast<int>(vertices.size()),
567 static_cast<int>(indices.size()));
573 fcn::Point bezierPoint(std::vector<fcn::Point>
const & controlPoints,
float t)
575 std::vector<fcn::Point> points = controlPoints;
576 while (points.size() > 1) {
577 std::vector<fcn::Point> nextPoints;
578 for (
size_t i = 0; i < points.size() - 1; ++i) {
579 int const x =
static_cast<int>(((1 - t) * points[i].x) + (t * points[i + 1].x));
580 int const y =
static_cast<int>(((1 - t) * points[i].y) + (t * points[i + 1].y));
581 nextPoints.emplace_back(x, y);
588 constexpr float degToRad(
float degrees)
590 return degrees * std::numbers::pi_v<float> / 180.0F;
598 "Clip stack is empty, perhaps you"
599 "called a draw function outside of _beginDraw() and _endDraw()?");
603 int const x0 = center.x + top.
xOffset;
604 int const y0 = center.y + top.
yOffset;
607 startAngle = startAngle % 360;
608 endAngle = endAngle % 360;
610 if (endAngle < startAngle) {
616 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
617 std::vector<SDL_Vertex> vertices;
618 vertices.reserve(numSegments + 2);
619 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
622 vertices.push_back(makeSolidVertex(
static_cast<float>(x0),
static_cast<float>(y0), vertexColor));
624 float const startRad =
static_cast<float>(startAngle) * std::numbers::pi_v<float> / 180.0F;
625 float const endRad =
static_cast<float>(endAngle) * std::numbers::pi_v<float> / 180.0F;
627 for (
int i = 0; i <= numSegments; ++i) {
628 float const t =
static_cast<float>(i) /
static_cast<float>(numSegments);
629 float const angle = startRad + (t * (endRad - startRad));
630 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
631 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
632 vertices.push_back(makeSolidVertex(x, y, vertexColor));
636 std::vector<int> indices;
637 indices.reserve(numSegments * 3);
638 for (
int i = 1; i <= numSegments; ++i) {
639 indices.push_back(0);
640 indices.push_back(i);
641 indices.push_back(i + 1);
650 static_cast<int>(vertices.size()),
652 static_cast<int>(indices.size()));
658 int normalizeAngle(
int angle)
672 "Clip stack is empty, perhaps you"
673 "called a draw function outside of _beginDraw() and _endDraw()?");
677 int const x0 = center.x + top.
xOffset;
678 int const y0 = center.y + top.
yOffset;
682 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
683 std::vector<SDL_Vertex> vertices;
684 vertices.reserve(numSegments + 1);
685 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
687 for (
int i = 0; i <= numSegments; ++i) {
689 2.0f * std::numbers::pi_v<float> *
static_cast<float>(i) /
static_cast<float>(numSegments);
690 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
691 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
692 vertices.push_back(makeSolidVertex(x, y, vertexColor));
696 std::vector<int> indices;
697 indices.reserve(numSegments * 2);
698 for (
int i = 0; i < numSegments; ++i) {
699 indices.push_back(i);
700 indices.push_back(i + 1);
709 static_cast<int>(vertices.size()),
711 static_cast<int>(indices.size()));
719 "Clip stack is empty, perhaps you"
720 "called a draw function outside of _beginDraw() and _endDraw()?");
724 int const x0 = center.x + top.
xOffset;
725 int const y0 = center.y + top.
yOffset;
728 startAngle = normalizeAngle(startAngle);
729 endAngle = normalizeAngle(endAngle);
731 if (endAngle < startAngle) {
737 int const numSegments = std::max(8,
static_cast<int>(radius / 2));
738 std::vector<SDL_Vertex> vertices;
739 vertices.reserve(numSegments + 1);
740 SDL_FColor
const vertexColor = toSDLVertexColor(
mColor);
742 float const startRad =
static_cast<float>(startAngle) * std::numbers::pi_v<float> / 180.0F;
743 float const endRad =
static_cast<float>(endAngle) * std::numbers::pi_v<float> / 180.0F;
745 for (
int i = 0; i <= numSegments; ++i) {
746 float const t =
static_cast<float>(i) /
static_cast<float>(numSegments);
747 float const angle = startRad + (t * (endRad - startRad));
748 float const x =
static_cast<float>(x0) + (radius * std::cos(angle));
749 float const y =
static_cast<float>(y0) + (radius * std::sin(angle));
750 vertices.push_back(makeSolidVertex(x, y, vertexColor));
754 std::vector<int> indices;
755 indices.reserve(numSegments * 2);
756 for (
int i = 0; i < numSegments; ++i) {
757 indices.push_back(i);
758 indices.push_back(i + 1);
767 static_cast<int>(vertices.size()),
769 static_cast<int>(indices.size()));
777 "Clip stack is empty, perhaps you"
778 "called a draw function outside of _beginDraw() and _endDraw()?");
787 std::vector<SDL_FPoint> points;
788 points.reserve(segments + 1);
790 for (
int i = 0; i <= segments; ++i) {
791 float const t =
static_cast<float>(i) /
static_cast<float>(segments);
792 fcn::Point const point = bezierPoint(controlPoints, t);
794 SDL_FPoint{
static_cast<float>(point.x + top.
xOffset),
static_cast<float>(point.y + top.
yOffset)});
801 SDL_RenderLines(
mRenderTarget, points.data(),
static_cast<int>(points.size()));
808 float const halfWidth =
static_cast<float>(width) / 2.0F;
810 for (
size_t i = 0; i < points.size() - 1; ++i) {
811 float const x1 = points[i].x;
812 float const y1 = points[i].y;
813 float const x2 = points[i + 1].x;
814 float const y2 = points[i + 1].y;
816 float const dx = x2 - x1;
817 float const dy = y2 - y1;
818 float const length = std::sqrt((dx * dx) + (dy * dy));
820 if (length < 0.001F) {
825 float const offsetX = (dy / length) * halfWidth;
826 float const offsetY = (dx / length) * halfWidth;
829 std::vector<SDL_Vertex> vertices = {
831 .position = {x1 - offsetX, y1 + offsetY},
833 {
static_cast<float>(
mColor.r) / 255.0F,
834 static_cast<float>(
mColor.g) / 255.0F,
835 static_cast<float>(
mColor.b) / 255.0F,
836 static_cast<float>(
mColor.a) / 255.0F}},
838 .position = {x1 + offsetX, y1 - offsetY},
840 {
static_cast<float>(
mColor.r) / 255.0F,
841 static_cast<float>(
mColor.g) / 255.0F,
842 static_cast<float>(
mColor.b) / 255.0F,
843 static_cast<float>(
mColor.a) / 255.0F}},
845 .position = {x2 + offsetX, y2 - offsetY},
847 {
static_cast<float>(
mColor.r) / 255.0F,
848 static_cast<float>(
mColor.g) / 255.0F,
849 static_cast<float>(
mColor.b) / 255.0F,
850 static_cast<float>(
mColor.a) / 255.0F}},
852 .position = {x2 - offsetX, y2 + offsetY},
854 static_cast<float>(
mColor.r) / 255.0F,
855 static_cast<float>(
mColor.g) / 255.0F,
856 static_cast<float>(
mColor.b) / 255.0F,
857 static_cast<float>(
mColor.a) / 255.0F}}};
859 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
864 static_cast<int>(vertices.size()),
866 static_cast<int>(indices.size()));
877 "Clip stack is empty, perhaps you"
878 "called a draw function outside of _beginDraw() and _endDraw()?");
881 if (points.size() < 2) {
888 std::vector<SDL_FPoint> sdlPoints;
889 sdlPoints.reserve(points.size());
890 std::transform(points.begin(), points.end(), std::back_inserter(sdlPoints), [&top](
auto const & point) {
891 return SDL_FPoint{static_cast<float>(point.x + top.xOffset), static_cast<float>(point.y + top.yOffset)};
897 setRenderDrawColor(mRenderTarget, mColor);
898 SDL_RenderLines(mRenderTarget, sdlPoints.data(),
static_cast<int>(sdlPoints.size()));
899 restoreRenderColor();
903 setRenderDrawColor(mRenderTarget, mColor);
905 float const halfWidth =
static_cast<float>(width) / 2.0F;
907 for (
size_t i = 0; i < sdlPoints.size() - 1; ++i) {
908 float const x1 = sdlPoints[i].x;
909 float const y1 = sdlPoints[i].y;
910 float const x2 = sdlPoints[i + 1].x;
911 float const y2 = sdlPoints[i + 1].y;
913 float const dx = x2 - x1;
914 float const dy = y2 - y1;
915 float const length = std::sqrt((dx * dx) + (dy * dy));
917 if (length < 0.001F) {
922 float const offsetX = (dy / length) * halfWidth;
923 float const offsetY = (dx / length) * halfWidth;
926 std::vector<SDL_Vertex> vertices = {
928 .position = {x1 - offsetX, y1 + offsetY},
930 {
static_cast<float>(mColor.r) / 255.0F,
931 static_cast<float>(mColor.g) / 255.0F,
932 static_cast<float>(mColor.b) / 255.0F,
933 static_cast<float>(mColor.a) / 255.0F}},
935 .position = {x1 + offsetX, y1 - offsetY},
937 {
static_cast<float>(mColor.r) / 255.0F,
938 static_cast<float>(mColor.g) / 255.0F,
939 static_cast<float>(mColor.b) / 255.0F,
940 static_cast<float>(mColor.a) / 255.0F}},
942 .position = {x2 + offsetX, y2 - offsetY},
944 {
static_cast<float>(mColor.r) / 255.0F,
945 static_cast<float>(mColor.g) / 255.0F,
946 static_cast<float>(mColor.b) / 255.0F,
947 static_cast<float>(mColor.a) / 255.0F}},
949 .position = {x2 - offsetX, y2 + offsetY},
951 static_cast<float>(mColor.r) / 255.0F,
952 static_cast<float>(mColor.g) / 255.0F,
953 static_cast<float>(mColor.b) / 255.0F,
954 static_cast<float>(mColor.a) / 255.0F}}};
956 std::array<int, 6>
const indices = {0, 1, 2, 0, 2, 3};
961 static_cast<int>(vertices.size()),
963 static_cast<int>(indices.size()));
966 restoreRenderColor();
984 return std::make_shared<TrueTypeFont>(filename, size);
991 "Clip stack is empty, perhaps you"
992 "called a draw function outside of _beginDraw() and _endDraw()?");
997 destination.x +=
static_cast<float>(top.
xOffset);
998 destination.y +=
static_cast<float>(top.
yOffset);
999 destination.w = source.w;
1000 destination.h = source.h;
1002 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.