2026-07-30 01:10:31 -04:00
|
|
|
/**
|
|
|
|
|
* @file draw.c
|
|
|
|
|
* @brief Implements the draw subsystem.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:05:36 -04:00
|
|
|
#include <akerror.h>
|
|
|
|
|
#include <akgl/draw.h>
|
|
|
|
|
#include <akgl/error.h>
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/game.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:05:36 -04:00
|
|
|
/** @brief One horizontal run of pixels the flood fill has still to examine. */
|
|
|
|
|
typedef struct {
|
|
|
|
|
int x1;
|
|
|
|
|
int x2;
|
|
|
|
|
int y;
|
|
|
|
|
} FloodSpan;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The flood fill's working stack. File scope and fixed size rather than a local
|
|
|
|
|
* array because AKGL_DRAW_MAX_FLOOD_SPANS spans is 48 KB, which does not belong
|
|
|
|
|
* on the stack of a function a game may call every frame. The consequence is
|
|
|
|
|
* that akgl_draw_flood_fill is not reentrant -- it is a single-threaded
|
|
|
|
|
* immediate-mode operation on a single render target, and so is everything else
|
|
|
|
|
* that touches an SDL_Renderer.
|
|
|
|
|
*/
|
|
|
|
|
static FloodSpan floodspans[AKGL_DRAW_MAX_FLOOD_SPANS];
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
/* Draw a Gimpish background pattern to show transparency in the image */
|
2026-05-06 23:18:42 -04:00
|
|
|
void akgl_draw_background(int w, int h)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
SDL_Color col[2] = {
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
{ 0x66, 0x66, 0x66, 0xff },
|
|
|
|
|
{ 0x99, 0x99, 0x99, 0xff },
|
2025-08-03 10:07:35 -04:00
|
|
|
};
|
|
|
|
|
int i, x, y;
|
|
|
|
|
SDL_FRect rect;
|
|
|
|
|
const int dx = 8, dy = 8;
|
|
|
|
|
|
|
|
|
|
rect.w = (float)dx;
|
|
|
|
|
rect.h = (float)dy;
|
|
|
|
|
for (y = 0; y < h; y += dy) {
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
for (x = 0; x < w; x += dx) {
|
|
|
|
|
/* use an 8x8 checkerboard pattern */
|
|
|
|
|
i = (((x ^ y) >> 3) & 1);
|
|
|
|
|
SDL_SetRenderDrawColor(renderer->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a);
|
2025-08-03 10:07:35 -04:00
|
|
|
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
rect.x = (float)x;
|
|
|
|
|
rect.y = (float)y;
|
|
|
|
|
SDL_RenderFillRect(renderer->sdl_renderer, &rect);
|
|
|
|
|
}
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.
Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.
tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:05:36 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Remember the renderer's draw color and replace it with @p color.
|
|
|
|
|
*
|
|
|
|
|
* @p previous is written before anything that can fail, so a caller may restore
|
|
|
|
|
* it unconditionally from a CLEANUP block.
|
|
|
|
|
*/
|
|
|
|
|
static akerr_ErrorContext *push_draw_color(akgl_RenderBackend *self, SDL_Color color, SDL_Color *previous)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
previous->r = 0x00;
|
|
|
|
|
previous->g = 0x00;
|
|
|
|
|
previous->b = 0x00;
|
|
|
|
|
previous->a = SDL_ALPHA_OPAQUE;
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetRenderDrawColor(self->sdl_renderer, &previous->r, &previous->g, &previous->b, &previous->a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetRenderDrawColor(self->sdl_renderer, color.r, color.g, color.b, color.a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief Put back the draw color push_draw_color() recorded. */
|
|
|
|
|
static akerr_ErrorContext *pop_draw_color(akgl_RenderBackend *self, SDL_Color *previous)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetRenderDrawColor(self->sdl_renderer, previous->r, previous->g, previous->b, previous->a),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Fill the four-connected region of @p oldpixel around a seed pixel.
|
|
|
|
|
*
|
|
|
|
|
* A scanline fill: each entry on the stack is a run of pixels on one row that
|
|
|
|
|
* still has to be examined. Finding a matching pixel expands it to the whole
|
|
|
|
|
* run it belongs to, fills that run, and pushes the rows above and below.
|
|
|
|
|
* Filled pixels no longer match @p oldpixel, which is what terminates it.
|
|
|
|
|
*
|
|
|
|
|
* @p surface must be SDL_PIXELFORMAT_RGBA32; the fill compares and writes whole
|
|
|
|
|
* 32-bit words rather than going through SDL_ReadSurfacePixel per pixel.
|
|
|
|
|
*
|
|
|
|
|
* @p dirty is set to the bounding box of everything written, so the caller can
|
|
|
|
|
* put back only the pixels that changed.
|
|
|
|
|
*
|
|
|
|
|
* Running out of stack leaves the region partially filled and reports
|
|
|
|
|
* AKERR_OUTOFBOUNDS. There is no way to unwind a partial fill short of keeping
|
|
|
|
|
* a copy of the whole surface, and the caller asked for a bounded operation.
|
|
|
|
|
*/
|
|
|
|
|
static akerr_ErrorContext *flood_region(SDL_Surface *surface, int x, int y, uint32_t oldpixel, uint32_t newpixel, SDL_Rect *dirty)
|
|
|
|
|
{
|
|
|
|
|
uint32_t *pixels = (uint32_t *)surface->pixels;
|
|
|
|
|
int pitch = surface->pitch / (int)sizeof(uint32_t);
|
|
|
|
|
int count = 0;
|
|
|
|
|
int col = 0;
|
|
|
|
|
int left = 0;
|
|
|
|
|
int right = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
int minx = surface->w;
|
|
|
|
|
int miny = surface->h;
|
|
|
|
|
int maxx = -1;
|
|
|
|
|
int maxy = -1;
|
|
|
|
|
FloodSpan span;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
floodspans[0].x1 = x;
|
|
|
|
|
floodspans[0].x2 = x;
|
|
|
|
|
floodspans[0].y = y;
|
|
|
|
|
count = 1;
|
|
|
|
|
|
|
|
|
|
while ( count > 0 ) {
|
|
|
|
|
count -= 1;
|
|
|
|
|
span = floodspans[count];
|
|
|
|
|
col = span.x1;
|
|
|
|
|
while ( col <= span.x2 ) {
|
|
|
|
|
if ( pixels[(span.y * pitch) + col] != oldpixel ) {
|
|
|
|
|
col += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
left = col;
|
|
|
|
|
while ( left > 0 && pixels[(span.y * pitch) + (left - 1)] == oldpixel ) {
|
|
|
|
|
left -= 1;
|
|
|
|
|
}
|
|
|
|
|
right = col;
|
|
|
|
|
while ( right < (surface->w - 1) && pixels[(span.y * pitch) + (right + 1)] == oldpixel ) {
|
|
|
|
|
right += 1;
|
|
|
|
|
}
|
|
|
|
|
for ( i = left; i <= right; i++ ) {
|
|
|
|
|
pixels[(span.y * pitch) + i] = newpixel;
|
|
|
|
|
}
|
|
|
|
|
if ( left < minx ) {
|
|
|
|
|
minx = left;
|
|
|
|
|
}
|
|
|
|
|
if ( right > maxx ) {
|
|
|
|
|
maxx = right;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y < miny ) {
|
|
|
|
|
miny = span.y;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y > maxy ) {
|
|
|
|
|
maxy = span.y;
|
|
|
|
|
}
|
|
|
|
|
// Two pushes per run, so the check is for room for both.
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
|
|
|
|
errctx,
|
|
|
|
|
((count + 2) > AKGL_DRAW_MAX_FLOOD_SPANS),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region needs more than %d pending spans; it is partially filled",
|
|
|
|
|
AKGL_DRAW_MAX_FLOOD_SPANS);
|
|
|
|
|
if ( span.y > 0 ) {
|
|
|
|
|
floodspans[count].x1 = left;
|
|
|
|
|
floodspans[count].x2 = right;
|
|
|
|
|
floodspans[count].y = span.y - 1;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
if ( span.y < (surface->h - 1) ) {
|
|
|
|
|
floodspans[count].x1 = left;
|
|
|
|
|
floodspans[count].x2 = right;
|
|
|
|
|
floodspans[count].y = span.y + 1;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
col = right + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dirty->x = minx;
|
|
|
|
|
dirty->y = miny;
|
|
|
|
|
dirty->w = (maxx - minx) + 1;
|
|
|
|
|
dirty->h = (maxy - miny) + 1;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderPoint(self->sdl_renderer, x, y),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderLine(self->sdl_renderer, x1, y1, x2, y2),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "rect");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderRect(self->sdl_renderer, rect),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "rect");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderFillRect(self->sdl_renderer, rect),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Color previous;
|
|
|
|
|
SDL_FPoint octants[8];
|
|
|
|
|
int centerx = 0;
|
|
|
|
|
int centery = 0;
|
|
|
|
|
int r = 0;
|
|
|
|
|
int offsetx = 0;
|
|
|
|
|
int offsety = 0;
|
|
|
|
|
int decision = 0;
|
|
|
|
|
bool plotted = true;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (radius < 0), AKERR_OUTOFBOUNDS, "Negative radius %f", radius);
|
|
|
|
|
|
|
|
|
|
centerx = (int)SDL_lroundf(x);
|
|
|
|
|
centery = (int)SDL_lroundf(y);
|
|
|
|
|
r = (int)SDL_lroundf(radius);
|
|
|
|
|
offsety = r;
|
|
|
|
|
// The midpoint decision variable, started so the first step chooses between
|
|
|
|
|
// (0, r) and (1, r-1) correctly.
|
|
|
|
|
decision = 1 - r;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, push_draw_color(self, color, &previous));
|
|
|
|
|
while ( offsety >= offsetx ) {
|
|
|
|
|
// Eight-way symmetry: one computed point in the second octant gives
|
|
|
|
|
// the seven others by reflection.
|
|
|
|
|
octants[0].x = (float)(centerx + offsetx);
|
|
|
|
|
octants[0].y = (float)(centery + offsety);
|
|
|
|
|
octants[1].x = (float)(centerx - offsetx);
|
|
|
|
|
octants[1].y = (float)(centery + offsety);
|
|
|
|
|
octants[2].x = (float)(centerx + offsetx);
|
|
|
|
|
octants[2].y = (float)(centery - offsety);
|
|
|
|
|
octants[3].x = (float)(centerx - offsetx);
|
|
|
|
|
octants[3].y = (float)(centery - offsety);
|
|
|
|
|
octants[4].x = (float)(centerx + offsety);
|
|
|
|
|
octants[4].y = (float)(centery + offsetx);
|
|
|
|
|
octants[5].x = (float)(centerx - offsety);
|
|
|
|
|
octants[5].y = (float)(centery + offsetx);
|
|
|
|
|
octants[6].x = (float)(centerx + offsety);
|
|
|
|
|
octants[6].y = (float)(centery - offsetx);
|
|
|
|
|
octants[7].x = (float)(centerx - offsety);
|
|
|
|
|
octants[7].y = (float)(centery - offsetx);
|
|
|
|
|
// A CATCH here would break this loop rather than leave the function,
|
|
|
|
|
// so failure is recorded and reported once the loop is done.
|
|
|
|
|
if ( !SDL_RenderPoints(self->sdl_renderer, octants, 8) ) {
|
|
|
|
|
plotted = false;
|
|
|
|
|
}
|
|
|
|
|
offsetx += 1;
|
|
|
|
|
if ( decision < 0 ) {
|
|
|
|
|
decision += (2 * offsetx) + 1;
|
|
|
|
|
} else {
|
|
|
|
|
offsety -= 1;
|
|
|
|
|
decision += 2 * (offsetx - offsety) + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, plotted, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(pop_draw_color(self, &previous));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color)
|
|
|
|
|
{
|
|
|
|
|
SDL_Surface *target = NULL;
|
|
|
|
|
SDL_Surface *rgba = NULL;
|
|
|
|
|
SDL_Texture *patch = NULL;
|
|
|
|
|
SDL_Rect dirty;
|
|
|
|
|
SDL_FRect src;
|
|
|
|
|
SDL_FRect dest;
|
|
|
|
|
uint32_t *pixels = NULL;
|
|
|
|
|
uint32_t oldpixel = 0;
|
|
|
|
|
uint32_t newpixel = 0;
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetCurrentRenderOutputSize(self->sdl_renderer, &width, &height),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((x < 0) || (y < 0) || (x >= width) || (y >= height)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Seed pixel %d,%d is outside the %dx%d render target",
|
|
|
|
|
x, y, width, height);
|
|
|
|
|
|
|
|
|
|
target = SDL_RenderReadPixels(self->sdl_renderer, NULL);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, target, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// The fill works on 32-bit words, so the layout has to be known rather
|
|
|
|
|
// than whatever the render target happens to use.
|
|
|
|
|
rgba = SDL_ConvertSurface(target, SDL_PIXELFORMAT_RGBA32);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, rgba, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
pixels = (uint32_t *)rgba->pixels;
|
|
|
|
|
oldpixel = pixels[(y * (rgba->pitch / (int)sizeof(uint32_t))) + x];
|
|
|
|
|
newpixel = SDL_MapSurfaceRGBA(rgba, color.r, color.g, color.b, color.a);
|
|
|
|
|
if ( oldpixel == newpixel ) {
|
|
|
|
|
// Already the requested color. Walking it would compare filled
|
|
|
|
|
// pixels against themselves and find nothing, so say so up front.
|
|
|
|
|
SUCCEED_BREAK(errctx);
|
|
|
|
|
}
|
|
|
|
|
CATCH(errctx, flood_region(rgba, x, y, oldpixel, newpixel, &dirty));
|
|
|
|
|
|
|
|
|
|
patch = SDL_CreateTextureFromSurface(self->sdl_renderer, rgba);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, patch, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// Replace rather than blend: this is a framebuffer operation, and the
|
|
|
|
|
// pixels being put back are the ones that were just read out of it.
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetTextureBlendMode(patch, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
|
|
|
|
|
// Only the bounding box of what changed goes back to the target.
|
|
|
|
|
src.x = (float)dirty.x;
|
|
|
|
|
src.y = (float)dirty.y;
|
|
|
|
|
src.w = (float)dirty.w;
|
|
|
|
|
src.h = (float)dirty.h;
|
|
|
|
|
dest = src;
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderTexture(self->sdl_renderer, patch, &src, &dest),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( patch != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(patch);
|
|
|
|
|
}
|
|
|
|
|
if ( rgba != NULL ) {
|
|
|
|
|
SDL_DestroySurface(rgba);
|
|
|
|
|
}
|
|
|
|
|
if ( target != NULL ) {
|
|
|
|
|
SDL_DestroySurface(target);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest)
|
|
|
|
|
{
|
|
|
|
|
SDL_Surface *saved = NULL;
|
|
|
|
|
int width = 0;
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, src, AKERR_NULLPOINTER, "src");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((src->w <= 0) || (src->h <= 0)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region %dx%d has no area",
|
|
|
|
|
src->w, src->h);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_GetCurrentRenderOutputSize(self->sdl_renderer, &width, &height),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
// SDL clips a read to the target and hands back a smaller surface than
|
|
|
|
|
// was asked for, which a caller pasting it back would not notice.
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((src->x < 0) || (src->y < 0) ||
|
|
|
|
|
((src->x + src->w) > width) || ((src->y + src->h) > height)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Region %d,%d %dx%d does not fit inside the %dx%d render target",
|
|
|
|
|
src->x, src->y, src->w, src->h, width, height);
|
|
|
|
|
|
|
|
|
|
saved = SDL_RenderReadPixels(self->sdl_renderer, src);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, saved, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
if ( *dest == NULL ) {
|
|
|
|
|
*dest = saved;
|
|
|
|
|
// Ownership has moved to the caller; CLEANUP must not free it.
|
|
|
|
|
saved = NULL;
|
|
|
|
|
} else {
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(((*dest)->w != src->w) || ((*dest)->h != src->h)),
|
|
|
|
|
AKERR_OUTOFBOUNDS,
|
|
|
|
|
"Destination surface is %dx%d, region is %dx%d",
|
|
|
|
|
(*dest)->w, (*dest)->h, src->w, src->h);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetSurfaceBlendMode(saved, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_BlitSurface(saved, NULL, *dest, NULL),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( saved != NULL ) {
|
|
|
|
|
SDL_DestroySurface(saved);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y)
|
|
|
|
|
{
|
|
|
|
|
SDL_Texture *patch = NULL;
|
|
|
|
|
SDL_FRect dest;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, src, AKERR_NULLPOINTER, "src");
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
patch = SDL_CreateTextureFromSurface(self->sdl_renderer, src);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, patch, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
// Replace what is on the target, the way GSHAPE does by default.
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_SetTextureBlendMode(patch, SDL_BLENDMODE_NONE),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
dest.x = x;
|
|
|
|
|
dest.y = y;
|
|
|
|
|
dest.w = (float)src->w;
|
|
|
|
|
dest.h = (float)src->h;
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
SDL_RenderTexture(self->sdl_renderer, patch, NULL, &dest),
|
|
|
|
|
AKGL_ERR_SDL,
|
|
|
|
|
"%s",
|
|
|
|
|
SDL_GetError());
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( patch != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(patch);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|