From e63ab8d2929557701d29e01d8b1d4f45e1d71a4b Mon Sep 17 00:00:00 2001 From: Matt Zykan Date: Mon, 30 Sep 2024 13:17:02 -0500 Subject: [PATCH] flood fill paint function (#2683) * 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 --- src/api.h | 12 ++++++ src/api/janet.c | 16 ++++++++ src/api/js.c | 14 +++++++ src/api/luaapi.c | 21 ++++++++++ src/api/mruby.c | 13 ++++++ src/api/python.c | 22 +++++++++++ src/api/scheme.c | 12 ++++++ src/api/squirrel.c | 20 ++++++++++ src/api/wasm.c | 13 ++++++ src/api/wren.c | 17 ++++++++ src/core/draw.c | 98 +++++++++++++++++++++++++++++++++++++++++++++- 11 files changed, 257 insertions(+), 1 deletion(-) diff --git a/src/api.h b/src/api.h index 728cfa5cd..977d6b964 100644 --- a/src/api.h +++ b/src/api.h @@ -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)", \ \ diff --git a/src/api/janet.c b/src/api/janet.c index fbd4e40ef..c50ca0c66 100644 --- a/src/api/janet.c +++ b/src/api/janet.c @@ -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); @@ -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}, @@ -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); diff --git a/src/api/js.c b/src/api/js.c index 2fe58d4f7..7bef996c8 100644 --- a/src/api/js.c +++ b/src/api/js.c @@ -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]; diff --git a/src/api/luaapi.c b/src/api/luaapi.c index da995b819..e1cde9dca 100644 --- a/src/api/luaapi.c +++ b/src/api/luaapi.c @@ -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); diff --git a/src/api/mruby.c b/src/api/mruby.c index 61924644f..84f16d28e 100644 --- a/src/api/mruby.c +++ b/src/api/mruby.c @@ -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; diff --git a/src/api/python.c b/src/api/python.c index 0da8e5846..391fd2f43 100644 --- a/src/api/python.c +++ b/src/api/python.c @@ -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) { @@ -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); diff --git a/src/api/scheme.c b/src/api/scheme.c index 84c38c3f4..2049c860c 100644 --- a/src/api/scheme.c +++ b/src/api/scheme.c @@ -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) diff --git a/src/api/squirrel.c b/src/api/squirrel.c index 4ba1b76fe..681e359ac 100644 --- a/src/api/squirrel.c +++ b/src/api/squirrel.c @@ -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); diff --git a/src/api/wasm.c b/src/api/wasm.c index d5c960cfd..3480a2d2a 100644 --- a/src/api/wasm.c +++ b/src/api/wasm.c @@ -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) diff --git a/src/api/wren.c b/src/api/wren.c index 725e1b6fa..5a99a3526 100644 --- a/src/api/wren.c +++ b/src/api/wren.c @@ -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\ @@ -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); @@ -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; diff --git a/src/core/draw.c b/src/core/draw.c index b928cc513..bcb022696 100644 --- a/src/core/draw.c +++ b/src/core/draw.c @@ -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 @@ -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 @@ -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