From 3b5a0a158688970c53c6bd9492dd3dc04d605e8d Mon Sep 17 00:00:00 2001 From: Bill Kendrick Date: Sun, 7 Mar 2021 01:00:53 -0800 Subject: [PATCH] WIP: Fill tool improvement related to antialiasing Fill tools do a better job at filling around antialiased edges, and apply some blending. (Blending does not yet occur with gradient tools.) --- docs/CHANGES.txt | 5 +- src/fill.c | 169 ++++++++++++++++++++++++++++++++++++++--------- src/fill.h | 8 +-- src/tuxpaint.c | 25 +++---- 4 files changed, 158 insertions(+), 49 deletions(-) diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index 47dbb8cc9..d882258b8 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -8,7 +8,7 @@ http://www.tuxpaint.org/ $Id$ -2021.March.3 (0.9.26) +2021.March.7 (0.9.26) * New Features ------------ * Larger UI buttons @@ -64,6 +64,9 @@ $Id$ * Other Improvements ------------------ + * [WIP] Fill tools do a better job at filling around + antialiased edges, and apply some blending. + * Reduce CPU usage by increasing delay in main loop from 1ms to 10ms, and only using SDL's Timer subsystem when scrolling happens. diff --git a/src/fill.c b/src/fill.c index 70fd40419..3c3c611f4 100644 --- a/src/fill.c +++ b/src/fill.c @@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA (See COPYING.txt) - Last updated: September 14, 2019 + Last updated: March 7, 2021 $Id$ */ @@ -43,14 +43,28 @@ #include "rgblinear.h" #include "playsound.h" #include "pixels.h" +#include "progressbar.h" + + +/* How close colors need to be to match all the time */ +#define COLOR_MATCH_NARROW 0.04 + +/* How close colors can be to match for a few pixels */ +#define COLOR_MATCH_WIDE 0.60 + +/* How many pixels can we allow a wide match before stopping? */ +#define WIDE_MATCH_THRESHOLD 3 /* Local function prototypes: */ -int colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2); +double colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2); +Uint32 blend(SDL_Surface * canvas, Uint32 draw_colr, Uint32 old_colr, double pct); +void simulate_flood_fill_outside_check(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched, int y_outside); -int colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2) +/* Returns how similar colors 'c1' and 'c2' are */ +double colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2) { Uint8 r1, g1, b1, r2, g2, b2; @@ -58,7 +72,7 @@ int colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2) { /* Get it over with quick, if possible! */ - return 1; + return 0.0; } else { @@ -79,14 +93,13 @@ int colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2) // dark grey, brown, purple // light grey, tan // red, orange - return r + g + b < 0.04; + return (r + g + b); } } - int would_flood_fill(SDL_Surface * canvas, Uint32 cur_colr, Uint32 old_colr) { - if (cur_colr == old_colr || colors_close(canvas, cur_colr, old_colr)) + if (colors_close(canvas, cur_colr, old_colr) < COLOR_MATCH_NARROW) { return 0; } else { @@ -94,19 +107,43 @@ int would_flood_fill(SDL_Surface * canvas, Uint32 cur_colr, Uint32 old_colr) } } -void do_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2) +void do_flood_fill(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched) { - simulate_flood_fill(canvas, x, y, cur_colr, old_colr, x1, y1, x2, y2, NULL); + simulate_flood_fill(screen, canvas, x, y, cur_colr, old_colr, x1, y1, x2, y2, touched); } -void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched) +Uint32 blend(SDL_Surface * canvas, Uint32 draw_colr, Uint32 old_colr, double pct) { + Uint8 old_r, old_g, old_b, draw_r, draw_g, draw_b, new_r, new_g, new_b; + + SDL_GetRGB(draw_colr, canvas->format, &draw_r, &draw_g, &draw_b); + SDL_GetRGB(old_colr, canvas->format, &old_r, &old_g, &old_b); + + new_r = (Uint8) (((float) old_r) * (1.00 - pct) + ((float) draw_r * pct)); + new_g = (Uint8) (((float) old_g) * (1.00 - pct) + ((float) draw_g * pct)); + new_b = (Uint8) (((float) old_b) * (1.00 - pct) + ((float) draw_b * pct)); + + return SDL_MapRGB(canvas->format, new_r, new_g, new_b); +} + +void simulate_flood_fill(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched) { + simulate_flood_fill_outside_check(screen, canvas, x, y, cur_colr, old_colr, x1, y1, x2, y2, touched, 0); +} + +void simulate_flood_fill_outside_check(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched, int y_outside) { - int fillL, fillR, i, in_line; + int fillL, fillR, narrowFillL, narrowFillR, i, outside; + double in_line, closeness; static unsigned char prog_anim; + Uint32 px_colr; - if (cur_colr == old_colr || colors_close(canvas, cur_colr, old_colr)) + /* "Same" color? No need to fill */ + if (!would_flood_fill(canvas, cur_colr, old_colr)) + return; + + /* Don't re-visit the same pixel */ + if (touched[(y * canvas->w) + x]) return; if (y < *y1) @@ -121,31 +158,56 @@ void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Ui fillL = x; fillR = x; + narrowFillL = x; + narrowFillR = x; prog_anim++; if ((prog_anim % 4) == 0) { - /* FIXME: api->update_progress_bar(); */ + show_progress_bar(screen); playsound(canvas, 1, SND_FILL, 1, x, SNDDIST_NEAR); } /* Find left side, filling along the way */ - in_line = 1; - - while (in_line) + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillL /* - 1 */, y); + in_line = colors_close(canvas, px_colr, old_colr); + outside = 0; + while (in_line < COLOR_MATCH_WIDE && outside < WIDE_MATCH_THRESHOLD) { - if (touched != NULL) { - touched[(y * canvas->w) + fillL] = 1; + if (in_line > COLOR_MATCH_NARROW) { + outside++; + } else { + narrowFillL = fillL; } - putpixels[canvas->format->BytesPerPixel] (canvas, fillL, y, cur_colr); + if (touched != NULL) { + touched[(y * canvas->w) + fillL] = (255 - ((Uint8) (in_line * 85))); + } + + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillL, y); + putpixels[canvas->format->BytesPerPixel] (canvas, fillL, y, blend(canvas, cur_colr, px_colr, (3.0 - in_line) / 3.0)); fillL--; - in_line = (fillL < 0) ? 0 : colors_close(canvas, getpixels[canvas->format->BytesPerPixel] (canvas, fillL, y), old_colr); + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillL, y); + + if (fillL >= 0) + { + in_line = colors_close(canvas, px_colr, old_colr); + } + else + { + in_line = 3.0; + } } + if (touched != NULL && fillL >= 0) + { + touched[(y * canvas->w) + fillL] = (255 - ((Uint8) (in_line * 85))); + } + + if (fillL < *x1) { *x1 = fillL; @@ -153,18 +215,42 @@ void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Ui fillL++; + /* Find right side, filling along the way */ - in_line = 1; - while (in_line) + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillR + 1, y); + in_line = colors_close(canvas, px_colr, old_colr); + outside = 0; + while (in_line < COLOR_MATCH_WIDE && outside < WIDE_MATCH_THRESHOLD) { - if (touched != NULL) { - touched[(y * canvas->w) + fillR] = 1; + if (in_line > COLOR_MATCH_NARROW) { + outside++; + } else { + narrowFillR = fillR; } - putpixels[canvas->format->BytesPerPixel] (canvas, fillR, y, cur_colr); + + if (touched != NULL) { + touched[(y * canvas->w) + fillR] = (255 - ((Uint8) (in_line * 85))); + } + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillR, y); + putpixels[canvas->format->BytesPerPixel] (canvas, fillR, y, blend(canvas, cur_colr, px_colr, (3.0 - in_line) / 3.0)); fillR++; - in_line = (fillR >= canvas->w) ? 0 : colors_close(canvas, getpixels[canvas->format->BytesPerPixel] (canvas, fillR, y), old_colr); + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillR, y); + + if (fillR < canvas->w) + { + in_line = colors_close(canvas, px_colr, old_colr); + } + else + { + in_line = 3.0; + } + } + + if (touched != NULL && fillR < canvas->w) + { + touched[(y * canvas->w) + fillR] = (255 - ((Uint8) (in_line * 85))); } if (fillR > *x2) @@ -177,13 +263,31 @@ void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Ui /* Search top and bottom */ - for (i = fillL; i <= fillR; i++) + for (i = narrowFillL; i <= narrowFillR; i++) { - if (y > 0 && colors_close(canvas, getpixels[canvas->format->BytesPerPixel] (canvas, i, y - 1), old_colr)) - simulate_flood_fill(canvas, i, y - 1, cur_colr, old_colr, x1, y1, x2, y2, touched); + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, i, y - 1); + closeness = colors_close(canvas, px_colr, old_colr); + if (y > 0 && + ( + closeness < COLOR_MATCH_NARROW || + (closeness < COLOR_MATCH_WIDE && y_outside < WIDE_MATCH_THRESHOLD) + ) + ) + { + simulate_flood_fill_outside_check(screen, canvas, i, y - 1, cur_colr, old_colr, x1, y1, x2, y2, touched, y_outside + 1); + } - if (y < canvas->h && colors_close(canvas, getpixels[canvas->format->BytesPerPixel] (canvas, i, y + 1), old_colr)) - simulate_flood_fill(canvas, i, y + 1, cur_colr, old_colr, x1, y1, x2, y2, touched); + px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, i, y + 1); + closeness = colors_close(canvas, px_colr, old_colr); + if (y < canvas->h && + ( + closeness < COLOR_MATCH_NARROW || + (closeness < COLOR_MATCH_WIDE && y_outside < WIDE_MATCH_THRESHOLD) + ) + ) + { + simulate_flood_fill_outside_check(screen, canvas, i, y + 1, cur_colr, old_colr, x1, y1, x2, y2, touched, y_outside + 1); + } } } @@ -194,7 +298,6 @@ void draw_linear_gradient(SDL_Surface * canvas, SDL_Surface * last, ) { Uint32 old_colr, new_colr; int xx, yy; - float xd, yd; Uint8 draw_r, draw_g, draw_b, old_r, old_g, old_b, new_r, new_g, new_b; float A, B, C, C1, C2, ratio; @@ -218,6 +321,7 @@ void draw_linear_gradient(SDL_Surface * canvas, SDL_Surface * last, https://stackoverflow.com/questions/521493/creating-a-linear-gradient-in-2d-array) */ C = (A * xx) + (B * yy); + /* FIXME: Blend gradient color based on touched[] */ if (C < C1) { /* At/beyond the click spot (opposite direction of mouse); solid color */ putpixels[canvas->format->BytesPerPixel] (canvas, xx, yy, draw_color); @@ -283,6 +387,7 @@ void draw_radial_gradient(SDL_Surface * canvas, int x_left, int y_top, int x_rig new_b = (Uint8) (((float) old_b) * ratio + ((float) draw_b * (1.00 - ratio))); new_colr = SDL_MapRGB(canvas->format, new_r, new_g, new_b); + /* FIXME: Blend gradient color based on touched[] */ putpixels[canvas->format->BytesPerPixel] (canvas, xx, yy, new_colr); } } diff --git a/src/fill.h b/src/fill.h index 100c54702..6208ee567 100644 --- a/src/fill.h +++ b/src/fill.h @@ -4,7 +4,7 @@ Fill tool Tux Paint - A simple drawing program for children. - Copyright (c) 2002-2019 by Bill Kendrick and others; see AUTHORS.txt + Copyright (c) 2002-2021 by Bill Kendrick and others; see AUTHORS.txt bill@newbreedsoftware.com http://www.tuxpaint.org/ @@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA (See COPYING.txt) - Last updated: February 20, 2021 + Last updated: March 7, 2021 $Id$ */ @@ -37,8 +37,8 @@ #include "SDL.h" int would_flood_fill(SDL_Surface * canvas, Uint32 cur_colr, Uint32 old_colr); -void do_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2); -void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched); +void do_flood_fill(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched); +void simulate_flood_fill(SDL_Surface * screen, SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Uint32 old_colr, int * x1, int * y1, int * x2, int * y2, Uint8 * touched); void draw_linear_gradient(SDL_Surface * canvas, SDL_Surface * last, int x_left, int y_top, int x_right, int y_bottom, int x1, int y1, int x2, int y2, Uint32 draw_color, Uint8 * touched); diff --git a/src/tuxpaint.c b/src/tuxpaint.c index ae692a406..9ad49f55d 100644 --- a/src/tuxpaint.c +++ b/src/tuxpaint.c @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA (See COPYING.txt) - June 14, 2002 - February 20, 2021 + June 14, 2002 - March 7, 2021 */ @@ -4550,30 +4550,30 @@ static void mainloop(void) rec_undo_buffer(); x1 = x2 = old_x; y1 = y2 = old_y; - + + for (y1 = 0; y1 < canvas->h; y1++) { + for (x1 = 0; x1 < canvas->w; x1++) { + sim_flood_touched[(y1 * canvas->w) + x1] = 0; + } + } + if (cur_fill == FILL_FLOOD) { /* Flood fill a solid color */ - do_flood_fill(canvas, old_x, old_y, draw_color, canv_color, &x1, &y1, &x2, &y2); - + do_flood_fill(screen, canvas, old_x, old_y, draw_color, canv_color, &x1, &y1, &x2, &y2, sim_flood_touched); + update_canvas(x1, y1, x2, y2); } else { SDL_Surface * tmp_canvas; - for (y1 = 0; y1 < canvas->h; y1++) { - for (x1 = 0; x1 < canvas->w; x1++) { - sim_flood_touched[(y1 * canvas->w) + x1] = 0; - } - } - tmp_canvas = SDL_CreateRGBSurface(canvas->flags, canvas->w, canvas->h, canvas->format->BitsPerPixel, canvas->format->Rmask, canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask); SDL_BlitSurface(canvas, NULL, tmp_canvas, NULL); - simulate_flood_fill(tmp_canvas, old_x, old_y, draw_color, canv_color, &x1, &y1, &x2, &y2, sim_flood_touched); + simulate_flood_fill(screen, tmp_canvas, old_x, old_y, draw_color, canv_color, &x1, &y1, &x2, &y2, sim_flood_touched); SDL_FreeSurface(tmp_canvas); sim_flood_x1 = x1; @@ -4596,6 +4596,8 @@ static void mainloop(void) update_canvas(x1, y1, x2, y2); } + + draw_tux_text(TUX_GREAT, tool_tips[TOOL_FILL], 1); } } else if (cur_tool == TOOL_TEXT || cur_tool == TOOL_LABEL) @@ -4725,7 +4727,6 @@ static void mainloop(void) } do_render_cur_text(0); - } button_down = 1;