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.)
This commit is contained in:
Bill Kendrick 2021-03-07 01:00:53 -08:00
parent f40dc07f37
commit 3b5a0a1586
4 changed files with 158 additions and 49 deletions

View file

@ -8,7 +8,7 @@ http://www.tuxpaint.org/
$Id$ $Id$
2021.March.3 (0.9.26) 2021.March.7 (0.9.26)
* New Features * New Features
------------ ------------
* Larger UI buttons * Larger UI buttons
@ -64,6 +64,9 @@ $Id$
* Other Improvements * 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 * Reduce CPU usage by increasing delay in main loop
from 1ms to 10ms, and only using SDL's Timer subsystem from 1ms to 10ms, and only using SDL's Timer subsystem
when scrolling happens. when scrolling happens.

View file

@ -27,7 +27,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt) (See COPYING.txt)
Last updated: September 14, 2019 Last updated: March 7, 2021
$Id$ $Id$
*/ */
@ -43,14 +43,28 @@
#include "rgblinear.h" #include "rgblinear.h"
#include "playsound.h" #include "playsound.h"
#include "pixels.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: */ /* 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; 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! */ /* Get it over with quick, if possible! */
return 1; return 0.0;
} }
else else
{ {
@ -79,14 +93,13 @@ int colors_close(SDL_Surface * canvas, Uint32 c1, Uint32 c2)
// dark grey, brown, purple // dark grey, brown, purple
// light grey, tan // light grey, tan
// red, orange // 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) 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; return 0;
} else { } 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; 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; return;
if (y < *y1) if (y < *y1)
@ -121,31 +158,56 @@ void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Ui
fillL = x; fillL = x;
fillR = x; fillR = x;
narrowFillL = x;
narrowFillR = x;
prog_anim++; prog_anim++;
if ((prog_anim % 4) == 0) if ((prog_anim % 4) == 0)
{ {
/* FIXME: api->update_progress_bar(); */ show_progress_bar(screen);
playsound(canvas, 1, SND_FILL, 1, x, SNDDIST_NEAR); playsound(canvas, 1, SND_FILL, 1, x, SNDDIST_NEAR);
} }
/* Find left side, filling along the way */ /* Find left side, filling along the way */
in_line = 1; px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillL /* - 1 */, y);
in_line = colors_close(canvas, px_colr, old_colr);
while (in_line) outside = 0;
while (in_line < COLOR_MATCH_WIDE && outside < WIDE_MATCH_THRESHOLD)
{ {
if (touched != NULL) { if (in_line > COLOR_MATCH_NARROW) {
touched[(y * canvas->w) + fillL] = 1; 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--; 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) if (fillL < *x1)
{ {
*x1 = fillL; *x1 = fillL;
@ -153,18 +215,42 @@ void simulate_flood_fill(SDL_Surface * canvas, int x, int y, Uint32 cur_colr, Ui
fillL++; fillL++;
/* Find right side, filling along the way */ /* Find right side, filling along the way */
in_line = 1; px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, fillR + 1, y);
while (in_line) in_line = colors_close(canvas, px_colr, old_colr);
outside = 0;
while (in_line < COLOR_MATCH_WIDE && outside < WIDE_MATCH_THRESHOLD)
{ {
if (touched != NULL) { if (in_line > COLOR_MATCH_NARROW) {
touched[(y * canvas->w) + fillR] = 1; 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++; 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) 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 */ /* 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)) px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, i, y - 1);
simulate_flood_fill(canvas, i, y - 1, cur_colr, old_colr, x1, y1, x2, y2, touched); 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)) px_colr = getpixels[canvas->format->BytesPerPixel] (canvas, i, y + 1);
simulate_flood_fill(canvas, i, y + 1, cur_colr, old_colr, x1, y1, x2, y2, touched); 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; Uint32 old_colr, new_colr;
int xx, yy; 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; 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; 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) */ https://stackoverflow.com/questions/521493/creating-a-linear-gradient-in-2d-array) */
C = (A * xx) + (B * yy); C = (A * xx) + (B * yy);
/* FIXME: Blend gradient color based on touched[] */
if (C < C1) { if (C < C1) {
/* At/beyond the click spot (opposite direction of mouse); solid color */ /* At/beyond the click spot (opposite direction of mouse); solid color */
putpixels[canvas->format->BytesPerPixel] (canvas, xx, yy, draw_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_b = (Uint8) (((float) old_b) * ratio + ((float) draw_b * (1.00 - ratio)));
new_colr = SDL_MapRGB(canvas->format, new_r, new_g, new_b); 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); putpixels[canvas->format->BytesPerPixel] (canvas, xx, yy, new_colr);
} }
} }

View file

@ -4,7 +4,7 @@
Fill tool Fill tool
Tux Paint - A simple drawing program for children. 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 bill@newbreedsoftware.com
http://www.tuxpaint.org/ http://www.tuxpaint.org/
@ -27,7 +27,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt) (See COPYING.txt)
Last updated: February 20, 2021 Last updated: March 7, 2021
$Id$ $Id$
*/ */
@ -37,8 +37,8 @@
#include "SDL.h" #include "SDL.h"
int would_flood_fill(SDL_Surface * canvas, Uint32 cur_colr, Uint32 old_colr); 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);
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 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, void draw_linear_gradient(SDL_Surface * canvas, SDL_Surface * last,
int x_left, int y_top, int x_right, int y_bottom, 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); int x1, int y1, int x2, int y2, Uint32 draw_color, Uint8 * touched);

View file

@ -22,7 +22,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(See COPYING.txt) (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(); rec_undo_buffer();
x1 = x2 = old_x; x1 = x2 = old_x;
y1 = y2 = old_y; 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) if (cur_fill == FILL_FLOOD)
{ {
/* Flood fill a solid color */ /* 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); update_canvas(x1, y1, x2, y2);
} }
else else
{ {
SDL_Surface * tmp_canvas; 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, tmp_canvas = SDL_CreateRGBSurface(canvas->flags,
canvas->w, canvas->h, canvas->format->BitsPerPixel, canvas->w, canvas->h, canvas->format->BitsPerPixel,
canvas->format->Rmask, canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask); canvas->format->Rmask, canvas->format->Gmask, canvas->format->Bmask, canvas->format->Amask);
SDL_BlitSurface(canvas, NULL, tmp_canvas, NULL); 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); SDL_FreeSurface(tmp_canvas);
sim_flood_x1 = x1; sim_flood_x1 = x1;
@ -4596,6 +4596,8 @@ static void mainloop(void)
update_canvas(x1, y1, x2, y2); 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) else if (cur_tool == TOOL_TEXT || cur_tool == TOOL_LABEL)
@ -4725,7 +4727,6 @@ static void mainloop(void)
} }
do_render_cur_text(0); do_render_cur_text(0);
} }
button_down = 1; button_down = 1;