Skip to content

Commit

Permalink
flood fill paint function (#2683)
Browse files Browse the repository at this point in the history
* flood fill paint function

* increase max paint stack to 4000

* optional paint argument bordercolor

* replace painter stack with queue

* fix janet and wren bindings for paint function
  • Loading branch information
anescient authored Sep 30, 2024
1 parent 2d4415f commit e63ab8d
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,18 @@ enum
tic_mem*, s32 x, s32 y, s32 a, s32 b, u8 color) \
\
\
macro(paint, \
"paint(x y color bordercolor=-1)", \
\
"This function fills a contiguous area with a new color.\n" \
"If bordercolor is given fill will extend to color boundary.", \
4, \
3, \
0, \
void, \
tic_mem*, s32 x, s32 y, u8 color, u8 bordercolor) \
\
\
macro(tri, \
"tri(x1 y1 x2 y2 x3 y3 color)", \
\
Expand Down
16 changes: 16 additions & 0 deletions src/api/janet.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ static Janet janet_circ(int32_t argc, Janet* argv);
static Janet janet_circb(int32_t argc, Janet* argv);
static Janet janet_elli(int32_t argc, Janet* argv);
static Janet janet_ellib(int32_t argc, Janet* argv);
static Janet janet_paint(int32_t argc, Janet* argv);
static Janet janet_tri(int32_t argc, Janet* argv);
static Janet janet_trib(int32_t argc, Janet* argv);
static Janet janet_ttri(int32_t argc, Janet* argv);
Expand Down Expand Up @@ -130,6 +131,7 @@ static const JanetReg janet_c_functions[] =
{"circb", janet_circb, NULL},
{"elli", janet_elli, NULL},
{"ellib", janet_ellib, NULL},
{"paint", janet_paint, NULL},
{"tri", janet_tri, NULL},
{"trib", janet_trib, NULL},
{"ttri", janet_ttri, NULL},
Expand Down Expand Up @@ -833,6 +835,20 @@ static Janet janet_ellib(int32_t argc, Janet* argv)
return janet_wrap_nil();
}

static Janet janet_paint(int32_t argc, Janet* argv)
{
janet_arity(argc, 3, 4);

s32 x = janet_getinteger(argv, 0);
s32 y = janet_getinteger(argv, 1);
u8 color = janet_getinteger(argv, 2);
u8 bordercolor = janet_optnumber(argv, argc, 3, 255);

tic_core* core = getJanetMachine(); tic_mem* tic = (tic_mem*)core;
core->api.paint(tic, x, y, color, bordercolor);
return janet_wrap_nil();
}

static Janet janet_tri(int32_t argc, Janet* argv)
{
janet_fixarity(argc, 7);
Expand Down
14 changes: 14 additions & 0 deletions src/api/js.c
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,20 @@ static JSValue js_ellib(JSContext *ctx, JSValueConst this_val, s32 argc, JSValue
return JS_UNDEFINED;
}

static JSValue js_paint(JSContext *ctx, JSValueConst this_val, s32 argc, JSValueConst *argv)
{
s32 x = getInteger(ctx, argv[0]);
s32 y = getInteger(ctx, argv[1]);
s32 color = getInteger(ctx, argv[2]);
s32 bordercolor = getInteger2(ctx, argv[3], -1);

tic_core* core = getCore(ctx); tic_mem* tic = (tic_mem*)core;

core->api.paint(tic, x, y, color, bordercolor);

return JS_UNDEFINED;
}

static JSValue js_tri(JSContext *ctx, JSValueConst this_val, s32 argc, JSValueConst *argv)
{
float pt[6];
Expand Down
21 changes: 21 additions & 0 deletions src/api/luaapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,27 @@ static s32 lua_cls(lua_State* lua)
return 0;
}

static s32 lua_paint(lua_State* lua)
{
s32 top = lua_gettop(lua);

if(top >= 3 && top <= 4)
{
s32 x = getLuaNumber(lua, 1);
s32 y = getLuaNumber(lua, 2);
s32 color = getLuaNumber(lua, 3);
s32 bordercolor = top >= 4 ? getLuaNumber(lua, 4) : -1;

tic_core* core = getLuaCore(lua);
tic_mem* tic = (tic_mem*)core;

core->api.paint(tic, x, y, color, bordercolor);
}
else luaL_error(lua, "invalid parameters, paint(x y color [bordercolor])\n");

return 0;
}

static s32 lua_pix(lua_State* lua)
{
s32 top = lua_gettop(lua);
Expand Down
13 changes: 13 additions & 0 deletions src/api/mruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ static mrb_value mrb_ellib(mrb_state* mrb, mrb_value self)
return mrb_nil_value();
}

static mrb_value mrb_paint(mrb_state* mrb, mrb_value self)
{
mrb_int x, y, color;
mrb_int bordercolor = -1;
mrb_get_args(mrb, "iii|i", &x, &y, &color, &bordercolor);

tic_core* core = getMRubyMachine(mrb); tic_mem* tic = (tic_mem*)core;

core->api.paint(tic, x, y, color, bordercolor);

return mrb_nil_value();
}

static mrb_value mrb_tri(mrb_state* mrb, mrb_value self)
{
mrb_float x1, y1, x2, y2, x3, y3;
Expand Down
22 changes: 22 additions & 0 deletions src/api/python.c
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,25 @@ static int py_ellib(pkpy_vm* vm)
return 0;
}

static int py_paint(pkpy_vm* vm)
{
int x;
int y;
int color;
int bordercolor;

pkpy_to_int(vm, 0, &x);
pkpy_to_int(vm, 1, &y);
pkpy_to_int(vm, 2, &color);
pkpy_to_int(vm, 3, &bordercolor);
tic_core* core; get_core(vm, &core); tic_mem* tic = (tic_mem*)core;
if(pkpy_check_error(vm))
return 0;

core->api.paint(tic, x, y, color, bordercolor);
return 0;
}

static int py_clip(pkpy_vm* vm)
{

Expand Down Expand Up @@ -1250,6 +1269,9 @@ static bool setup_c_bindings(pkpy_vm* vm) {
pkpy_push_function(vm, "music(track=-1, frame=-1, row=-1, loop=True, sustain=False, tempo=-1, speed=-1)", py_music);
pkpy_setglobal_2(vm, "music");

pkpy_push_function(vm, "paint(x: int, y: int, color: int, bordercolor=-1)", py_paint);
pkpy_setglobal_2(vm, "paint");

pkpy_push_function(vm, "peek(addr: int, bits=8) -> int", py_peek);
pkpy_setglobal_2(vm, "peek");
pkpy_push_function(vm, "peek1(addr: int) -> int", py_peek1);
Expand Down
12 changes: 12 additions & 0 deletions src/api/scheme.c
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,18 @@ s7_pointer scheme_ellib(s7_scheme* sc, s7_pointer args)
core->api.ellib(tic, x, y, a, b, color);
return s7_nil(sc);
}
s7_pointer scheme_paint(s7_scheme* sc, s7_pointer args)
{
// paint(x y color bordercolor=-1)
const int argn = s7_list_length(sc, args);
tic_core* core = getSchemeCore(sc); tic_mem* tic = (tic_mem*)core;
const s32 x = s7_integer(s7_car(args));
const s32 y = s7_integer(s7_cadr(args));
const s32 color = s7_integer(s7_caddr(args));
const s32 bordercolor = argn >= 4 ? s7_integer(s7_cadddr(args)) : -1;
core->api.paint(tic, x, y, color, bordercolor);
return s7_nil(sc);
}
s7_pointer scheme_tri(s7_scheme* sc, s7_pointer args)
{
// tri(x1 y1 x2 y2 x3 y3 color)
Expand Down
20 changes: 20 additions & 0 deletions src/api/squirrel.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,26 @@ static SQInteger squirrel_ellib(HSQUIRRELVM vm)
return 0;
}

static SQInteger squirrel_paint(HSQUIRRELVM vm)
{
SQInteger top = sq_gettop(vm);

if(top >= 4 && top <= 5)
{
s32 x = getSquirrelNumber(vm, 2);
s32 y = getSquirrelNumber(vm, 3);
s32 color = getSquirrelNumber(vm, 4);
s32 bordercolor = top >= 5 ? getSquirrelNumber(vm, 5) : -1;

tic_core* core = getSquirrelCore(vm); tic_mem* tic = (tic_mem*)core;

core->api.paint(tic, x, y, color, bordercolor);
}
else return sq_throwerror(vm, "invalid parameters, paint(x,y,color,[bordercolor=-1])\n");

return 0;
}

static SQInteger squirrel_tri(HSQUIRRELVM vm)
{
SQInteger top = sq_gettop(vm);
Expand Down
13 changes: 13 additions & 0 deletions src/api/wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,19 @@ m3ApiRawFunction(wasmtic_ellib)
m3ApiSuccess();
}

m3ApiRawFunction(wasmtic_paint)
{
m3ApiGetArg (int32_t, x)
m3ApiGetArg (int32_t, y)
m3ApiGetArg (int8_t, color)
m3ApiGetArg (int8_t, bordercolor)

tic_core* core = getWasmCore(runtime); tic_mem* tic = (tic_mem*)core;
core->api.paint(tic, x, y, color, bordercolor);

m3ApiSuccess();
}

m3ApiRawFunction(wasmtic_rect)
{
m3ApiGetArg (int32_t, x)
Expand Down
17 changes: 17 additions & 0 deletions src/api/wren.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class TIC {\n\
foreign static circb(x, y, radius, color)\n\
foreign static elli(x, y, a, b, color)\n\
foreign static ellib(x, y, a, b, color)\n\
foreign static paint(x, y, color)\n\
foreign static paint(x, y, color, bordercolor)\n\
foreign static rect(x, y, w, h, color)\n\
foreign static rectb(x, y, w, h, color)\n\
foreign static tri(x1, y1, x2, y2, x3, y3, color)\n\
Expand Down Expand Up @@ -977,6 +979,19 @@ static void wren_ellib(WrenVM* vm)
core->api.ellib(tic, x, y, a, b, color);
}

static void wren_paint(WrenVM* vm)
{
s32 top = wrenGetSlotCount(vm);
s32 x = getWrenNumber(vm, 1);
s32 y = getWrenNumber(vm, 2);
s32 color = getWrenNumber(vm, 3);
s32 bordercolor = top > 4 ? getWrenNumber(vm, 4) : -1;

tic_core* core = getWrenCore(vm); tic_mem* tic = (tic_mem*)core;

core->api.paint(tic, x, y, color, bordercolor);
}

static void wren_rect(WrenVM* vm)
{
s32 x = getWrenNumber(vm, 1);
Expand Down Expand Up @@ -1548,6 +1563,8 @@ static WrenForeignMethodFn foreignTicMethods(const char* signature)
if (strcmp(signature, "static TIC.circb(_,_,_,_)" ) == 0) return wren_circb;
if (strcmp(signature, "static TIC.elli(_,_,_,_,_)" ) == 0) return wren_elli;
if (strcmp(signature, "static TIC.ellib(_,_,_,_,_)" ) == 0) return wren_ellib;
if (strcmp(signature, "static TIC.paint(_,_,_)" ) == 0) return wren_paint;
if (strcmp(signature, "static TIC.paint(_,_,_,_)" ) == 0) return wren_paint;
if (strcmp(signature, "static TIC.rect(_,_,_,_,_)" ) == 0) return wren_rect;
if (strcmp(signature, "static TIC.rectb(_,_,_,_,_)" ) == 0) return wren_rectb;
if (strcmp(signature, "static TIC.tri(_,_,_,_,_,_,_)" ) == 0) return wren_tri;
Expand Down
98 changes: 97 additions & 1 deletion src/core/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ static inline void setPixelFast(tic_core* core, s32 x, s32 y, u8 color)
tic_api_poke4((tic_mem*)core, y * TIC80_WIDTH + x, color);
}

static u8 getPixel(tic_core* core, s32 x, s32 y)
static inline u8 getPixel(tic_core* core, s32 x, s32 y)
{
return x < 0 || y < 0 || x >= TIC80_WIDTH || y >= TIC80_HEIGHT
? 0
Expand Down Expand Up @@ -613,6 +613,96 @@ static void drawLine(tic_mem* tic, float x0, float y0, float x1, float y1, u8 co
setPixel((tic_core*)tic, x1, y1, color);
}

// Queue frame for floodFill.
// Filled horizontal segment of scanline y for xl <= x <= xr.
// Parent segment was on line y – dy. dy = 1 or –1.
typedef struct
{
s32 y;
s32 xl;
s32 xr;
s32 dy;
} FillSegment;

#define FILLQUEUESIZE 400
static struct
{
FillSegment seg[FILLQUEUESIZE];
size_t ini; // index of empty next in
size_t outi; // index of next out
} fillQueue;

static inline void fillEnqueue(tic_core* tic, s32 y, s32 xl, s32 xr, s32 dy)
{
size_t nextini = (fillQueue.ini + 1) % FILLQUEUESIZE;
if (nextini == fillQueue.outi)
return; // queue full
if (y + dy < tic->state.clip.t || y + dy >= tic->state.clip.b)
return;
FillSegment* qseg = &fillQueue.seg[fillQueue.ini];
qseg->y = y;
qseg->xl = xl;
qseg->xr = xr;
qseg->dy = dy;
fillQueue.ini = nextini;
}

static inline bool fillDequeue(s32* y, s32* xl, s32* xr, s32* dy)
{
if (fillQueue.ini == fillQueue.outi)
return false; // queue empty
FillSegment* qseg = &fillQueue.seg[fillQueue.outi];
*y = qseg->y + qseg->dy;
*xl = qseg->xl;
*xr = qseg->xr;
*dy = qseg->dy;
fillQueue.outi = (fillQueue.outi + 1) % FILLQUEUESIZE;
return true;
}

static inline bool floodFillInside(u8 pix, u8 paint, u8 border, u8 original)
{
return border == 255 ? pix == original : pix != paint && pix != border;
}

// "A Seed Fill Algorithm", Paul S. Heckbert, Graphics Gems, Andrew Glassner
// https://github.com/erich666/GraphicsGems/blob/master/gems/SeedFill.c
static void floodFill(tic_core* tic, s32 x, s32 y, u8 color, u8 border)
{
if (x < tic->state.clip.l || y < tic->state.clip.t || x >= tic->state.clip.r || y >= tic->state.clip.b)
return;
u8 ov = getPixel(tic, x, y);
if (ov == color || ov == border)
return;
fillQueue.ini = fillQueue.outi = 0;
fillEnqueue(tic, y, x, x, 1); // needed in some cases
fillEnqueue(tic, y + 1, x, x, -1); // seed segment
s32 l, x1, x2, dy;
while (fillDequeue(&y, &x1, &x2, &dy))
{
// segment of scan line y-dy for x1<=x<=x2 was previously filled,
// now explore adjacent pixels in scan line y
for (x = x1; x >= tic->state.clip.l && floodFillInside(getPixel(tic, x, y), color, border, ov); x--)
setPixelFast(tic, x, y, color);
if (x >= x1)
goto floodFill_skip;
l = x + 1;
if (l < x1)
fillEnqueue(tic, y, l, x1 - 1, -dy); // check leak left
x = x1 + 1;
do {
for (; x < tic->state.clip.r && floodFillInside(getPixel(tic, x, y), color, border, ov); x++)
setPixelFast(tic, x, y, color);
fillEnqueue(tic, y, l, x - 1, dy);
if (x > x2 + 1)
fillEnqueue(tic, y, x2 + 1, x - 1, -dy); // check leak right
floodFill_skip:
for (x++; x <= x2 && !floodFillInside(getPixel(tic, x, y), color, border, ov); x++);
l = x;
} while (x <= x2);
}
}

typedef union
{
struct
Expand Down Expand Up @@ -916,6 +1006,12 @@ void tic_api_line(tic_mem* memory, float x0, float y0, float x1, float y1, u8 co
drawLine(memory, x0, y0, x1, y1, mapColor(memory, color));
}

void tic_api_paint(tic_mem* memory, s32 x, s32 y, u8 color, u8 bordercolor)
{
bordercolor = bordercolor == 255 ? 255 : mapColor(memory, bordercolor);
floodFill((tic_core*)memory, x, y, mapColor(memory, color), bordercolor);
}

#if defined(BUILD_DEPRECATED)
#include "draw_dep.c"
#endif

0 comments on commit e63ab8d

Please sign in to comment.