Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flood fill paint function #2683

Merged
merged 5 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -97,6 +97,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 @@ -976,6 +978,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 @@ -1546,6 +1561,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
Loading