From 9b7c0a6fe401f12a4007ee62b9f1b59565bd7dee Mon Sep 17 00:00:00 2001 From: Matt Zykan Date: Fri, 13 Sep 2024 15:49:09 -0500 Subject: [PATCH] flood fill paint function --- src/api.h | 11 +++++++ src/api/janet.c | 14 +++++++++ src/api/js.c | 13 ++++++++ src/api/luaapi.c | 20 +++++++++++++ src/api/mruby.c | 12 ++++++++ src/api/python.c | 20 +++++++++++++ src/api/scheme.c | 10 +++++++ src/api/squirrel.c | 19 ++++++++++++ src/api/wasm.c | 12 ++++++++ src/api/wren.c | 11 +++++++ src/core/draw.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 217 insertions(+) diff --git a/src/api.h b/src/api.h index 728cfa5cd..ef67fb4ed 100644 --- a/src/api.h +++ b/src/api.h @@ -632,6 +632,17 @@ enum tic_mem*, s32 x, s32 y, s32 a, s32 b, u8 color) \ \ \ + macro(paint, \ + "paint(x y color)", \ + \ + "This function fills an area.", \ + 3, \ + 3, \ + 0, \ + void, \ + tic_mem*, s32 x, s32 y, u8 color) \ + \ + \ macro(tri, \ "tri(x1 y1 x2 y2 x3 y3 color)", \ \ diff --git a/src/api/janet.c b/src/api/janet.c index fbd4e40ef..a1f26e13c 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); @@ -833,6 +834,19 @@ static Janet janet_ellib(int32_t argc, Janet* argv) return janet_wrap_nil(); } +static Janet janet_paint(int32_t argc, Janet* argv) +{ + janet_fixarity(argc, 3); + + s32 x = janet_getinteger(argv, 0); + s32 y = janet_getinteger(argv, 1); + u8 color = janet_getinteger(argv, 2); + + tic_core* core = getJanetMachine(); tic_mem* tic = (tic_mem*)core; + core->api.paint(tic, x, y, color); + 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..44c5abf48 100644 --- a/src/api/js.c +++ b/src/api/js.c @@ -769,6 +769,19 @@ 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]); + + tic_core* core = getCore(ctx); tic_mem* tic = (tic_mem*)core; + + core->api.paint(tic, x, y, color); + + 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..a994a39ce 100644 --- a/src/api/luaapi.c +++ b/src/api/luaapi.c @@ -209,6 +209,26 @@ static s32 lua_cls(lua_State* lua) return 0; } +static s32 lua_paint(lua_State* lua) +{ + s32 top = lua_gettop(lua); + + if(top == 3) + { + s32 x = getLuaNumber(lua, 1); + s32 y = getLuaNumber(lua, 2); + s32 color = getLuaNumber(lua, 3); + + tic_core* core = getLuaCore(lua); + tic_mem* tic = (tic_mem*)core; + + core->api.paint(tic, x, y, color); + } + else luaL_error(lua, "invalid parameters, paint(x y color)\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 6ef6f771f..1d3c05cf0 100644 --- a/src/api/mruby.c +++ b/src/api/mruby.c @@ -270,6 +270,18 @@ 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_get_args(mrb, "iii", &x, &y, &color); + + tic_core* core = getMRubyMachine(mrb); tic_mem* tic = (tic_mem*)core; + + core->api.paint(tic, x, y, color); + + 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..3945d3e1e 100644 --- a/src/api/python.c +++ b/src/api/python.c @@ -235,6 +235,23 @@ static int py_ellib(pkpy_vm* vm) return 0; } +static int py_paint(pkpy_vm* vm) +{ + int x; + int y; + int color; + + pkpy_to_int(vm, 0, &x); + pkpy_to_int(vm, 1, &y); + pkpy_to_int(vm, 2, &color); + 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); + return 0; +} + static int py_clip(pkpy_vm* vm) { @@ -1212,6 +1229,9 @@ static bool setup_c_bindings(pkpy_vm* vm) { pkpy_push_function(vm, "ellib(x: int, y: int, a: int, b: int, color: int)", py_ellib); pkpy_setglobal_2(vm, "ellib"); + pkpy_push_function(vm, "paint(x: int, y: int, color: int)", py_paint); + pkpy_setglobal_2(vm, "paint"); + pkpy_push_function(vm, "exit()", py_exit); pkpy_setglobal_2(vm, "exit"); diff --git a/src/api/scheme.c b/src/api/scheme.c index 84c38c3f4..8860d15ea 100644 --- a/src/api/scheme.c +++ b/src/api/scheme.c @@ -569,6 +569,16 @@ 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) +{ + // ellib(x y a b color) + 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)); + core->api.paint(tic, x, y, color); + 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..cbd8b3f41 100644 --- a/src/api/squirrel.c +++ b/src/api/squirrel.c @@ -423,6 +423,25 @@ static SQInteger squirrel_ellib(HSQUIRRELVM vm) return 0; } +static SQInteger squirrel_paint(HSQUIRRELVM vm) +{ + SQInteger top = sq_gettop(vm); + + if(top == 6) + { + s32 x = getSquirrelNumber(vm, 2); + s32 y = getSquirrelNumber(vm, 3); + s32 color = getSquirrelNumber(vm, 4); + + tic_core* core = getSquirrelCore(vm); tic_mem* tic = (tic_mem*)core; + + core->api.paint(tic, x, y, color); + } + else return sq_throwerror(vm, "invalid parameters, paint(x,y,color)\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..f7e0ce971 100644 --- a/src/api/wasm.c +++ b/src/api/wasm.c @@ -223,6 +223,18 @@ m3ApiRawFunction(wasmtic_ellib) m3ApiSuccess(); } +m3ApiRawFunction(wasmtic_paint) +{ + m3ApiGetArg (int32_t, x) + m3ApiGetArg (int32_t, y) + m3ApiGetArg (int8_t, color) + + tic_core* core = getWasmCore(runtime); tic_mem* tic = (tic_mem*)core; + core->api.paint(tic, x, y, color); + + m3ApiSuccess(); +} + m3ApiRawFunction(wasmtic_rect) { m3ApiGetArg (int32_t, x) diff --git a/src/api/wren.c b/src/api/wren.c index df319c072..17ba22ff4 100644 --- a/src/api/wren.c +++ b/src/api/wren.c @@ -976,6 +976,17 @@ static void wren_ellib(WrenVM* vm) core->api.ellib(tic, x, y, a, b, color); } +static void wren_paint(WrenVM* vm) +{ + s32 x = getWrenNumber(vm, 1); + s32 y = getWrenNumber(vm, 2); + s32 color = getWrenNumber(vm, 3); + + tic_core* core = getWrenCore(vm); tic_mem* tic = (tic_mem*)core; + + core->api.paint(tic, x, y, color); +} + static void wren_rect(WrenVM* vm) { s32 x = getWrenNumber(vm, 1); diff --git a/src/core/draw.c b/src/core/draw.c index b928cc513..df6ade2e6 100644 --- a/src/core/draw.c +++ b/src/core/draw.c @@ -613,6 +613,76 @@ static void drawLine(tic_mem* tic, float x0, float y0, float x1, float y1, u8 co setPixel((tic_core*)tic, x1, y1, color); } +// Stack 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 FILLSTACKMAX 1000 +static FillSegment FillStack[FILLSTACKMAX]; + +// Push a segment on the stack unless it's clipped or stack is full. +static inline void floodFillPush(tic_core* tic, size_t* si, s32 y, s32 xl, s32 xr, s32 dy) +{ + if (*si >= FILLSTACKMAX || y + dy < tic->state.clip.t || y + dy >= tic->state.clip.b) + return; + FillStack[*si].y = y; + FillStack[*si].xl = xl; + FillStack[*si].xr = xr; + FillStack[*si].dy = dy; + (*si)++; +} + +// "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) +{ + 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) + return; + size_t si = 0; // stack count, index of free top + floodFillPush(tic, &si, y, x, x, 1); // needed in some cases + floodFillPush(tic, &si, y + 1, x, x, -1); // seed segment + s32 l, x1, x2, dy; + while (si > 0) + { + // pop segment off stack and fill a neighboring scan line + si--; + dy = FillStack[si].dy; + y = FillStack[si].y + dy; + x1 = FillStack[si].xl; + x2 = FillStack[si].xr; + // 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 && getPixel(tic, x, y) == ov; x--) + setPixelFast(tic, x, y, color); + if (x >= x1) + goto floodFill_skip; + l = x + 1; + if (l < x1) + floodFillPush(tic, &si, y, l, x1 - 1, -dy); // check leak left + x = x1 + 1; + do { + for (; x < tic->state.clip.r && getPixel(tic, x, y) == ov; x++) + setPixelFast(tic, x, y, color); + floodFillPush(tic, &si, y, l, x - 1, dy); + if (x > x2 + 1) + floodFillPush(tic, &si, y, x2 + 1, x - 1, -dy); // check leak right +floodFill_skip: + for (x++; x <= x2 && getPixel(tic, x, y) != ov; x++); + l = x; + } while (x <= x2); + } +} + typedef union { struct @@ -916,6 +986,11 @@ 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) +{ + floodFill((tic_core*)memory, x, y, mapColor(memory, color)); +} + #if defined(BUILD_DEPRECATED) #include "draw_dep.c" #endif