Reworked Signed Distance Field for shaped fill
Based on http://www.codersnotes.com/notes/signed-distance-fields/ by Richard Mitton
This commit is contained in:
parent
e5a817e1cd
commit
ecab00d4a9
3 changed files with 197 additions and 68 deletions
|
|
@ -6,7 +6,7 @@ Copyright (c) 2002-2023
|
||||||
Various contributors (see below, and CHANGES.txt)
|
Various contributors (see below, and CHANGES.txt)
|
||||||
https://tuxpaint.org/
|
https://tuxpaint.org/
|
||||||
|
|
||||||
June 17, 2002 - February 22, 2023
|
June 17, 2002 - February 25, 2023
|
||||||
|
|
||||||
* Design and Coding:
|
* Design and Coding:
|
||||||
|
|
||||||
|
|
@ -18,6 +18,10 @@ June 17, 2002 - February 22, 2023
|
||||||
http://www.wikipedia.org/wiki/Flood_fill/C_example
|
http://www.wikipedia.org/wiki/Flood_fill/C_example
|
||||||
by Damian Yerrick - http://www.wikipedia.org/wiki/Damian_Yerrick
|
by Damian Yerrick - http://www.wikipedia.org/wiki/Damian_Yerrick
|
||||||
|
|
||||||
|
Shaped fill (Signed Distance Field routine) based on
|
||||||
|
http://www.codersnotes.com/notes/signed-distance-fields/
|
||||||
|
by Richard Mitton
|
||||||
|
|
||||||
800x600 resolution support patch by:
|
800x600 resolution support patch by:
|
||||||
TOYAMA Shin-ichi <dolphin6k@wmail.plala.or.jp>
|
TOYAMA Shin-ichi <dolphin6k@wmail.plala.or.jp>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Various contributors (see below, and AUTHORS.txt)
|
||||||
https://tuxpaint.org/
|
https://tuxpaint.org/
|
||||||
|
|
||||||
|
|
||||||
2023.February.24 (0.9.29)
|
2023.February.25 (0.9.29)
|
||||||
* Improvements to "Stamp" tool:
|
* Improvements to "Stamp" tool:
|
||||||
-----------------------------
|
-----------------------------
|
||||||
* Stamps may now be rotated.
|
* Stamps may now be rotated.
|
||||||
|
|
@ -78,8 +78,8 @@ https://tuxpaint.org/
|
||||||
* [WIP] Shaped fill tool (similar to radial gradient, but
|
* [WIP] Shaped fill tool (similar to radial gradient, but
|
||||||
follows the shape of the object).
|
follows the shape of the object).
|
||||||
Bill Kendrick <bill@newbreedsofware.com>
|
Bill Kendrick <bill@newbreedsofware.com>
|
||||||
(Based on https://github.com/mattdesl/image-sdf
|
(Based on http://www.codersnotes.com/notes/signed-distance-fields/
|
||||||
by Matt DesLauriers (https://www.mattdesl.com/), MIT License)
|
by Richard Mitton)
|
||||||
|
|
||||||
* New Starter
|
* New Starter
|
||||||
-----------
|
-----------
|
||||||
|
|
|
||||||
253
src/fill.c
253
src/fill.c
|
|
@ -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 24, 2023
|
Last updated: February 25, 2023
|
||||||
$Id$
|
$Id$
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -72,6 +72,21 @@ typedef struct queue_s
|
||||||
queue_t *queue;
|
queue_t *queue;
|
||||||
int queue_size = 0, queue_end = 0;
|
int queue_size = 0, queue_end = 0;
|
||||||
|
|
||||||
|
typedef struct sdf_point_s
|
||||||
|
{
|
||||||
|
int dx, dy;
|
||||||
|
} sdf_point;
|
||||||
|
|
||||||
|
sdf_point sdf_pt_inside = { 0, 0 };
|
||||||
|
sdf_point sdf_pt_empty = { 9999, 9999 };
|
||||||
|
|
||||||
|
typedef struct sdf_grid_s
|
||||||
|
{
|
||||||
|
sdf_point * * grid;
|
||||||
|
int w, h;
|
||||||
|
} sdf_grid;
|
||||||
|
|
||||||
|
|
||||||
/* Local function prototypes: */
|
/* Local function prototypes: */
|
||||||
|
|
||||||
SDL_Surface *global_screen, *global_last, *global_canvas;
|
SDL_Surface *global_screen, *global_last, *global_canvas;
|
||||||
|
|
@ -94,9 +109,15 @@ void init_queue(void);
|
||||||
void add_to_queue(int x, int y, int y_outside);
|
void add_to_queue(int x, int y, int y_outside);
|
||||||
int remove_from_queue(int *x, int *y, int *y_outside);
|
int remove_from_queue(int *x, int *y, int *y_outside);
|
||||||
void cleanup_queue(void);
|
void cleanup_queue(void);
|
||||||
int squareDist(int x1, int y1, int x2, int y2);
|
void sdf_pt_get(sdf_grid * g, int x, int y, sdf_point * p);
|
||||||
float findSignedDistance(Uint8 * bitmask, int w, int h, int x, int y, float spread);
|
void sdf_pt_put(sdf_grid * g, int x, int y, sdf_point p);
|
||||||
void compute_sdf(float * sdf, Uint8 * bitmask, int w, int h);
|
int sdf_distsq(sdf_point p);
|
||||||
|
void sdf_compare(sdf_grid * g, sdf_point * p, int x, int y, int offsetx, int offsety);
|
||||||
|
int malloc_sdf_grid(sdf_grid * g, int w, int h);
|
||||||
|
void free_sdf_grid(sdf_grid * g);
|
||||||
|
void sdf_fill_bitmask_to_sdf_grids(Uint8 * bitmask, int w, int h, sdf_grid * g1, sdf_grid * g2);
|
||||||
|
void sdf_generate(sdf_grid * g);
|
||||||
|
|
||||||
|
|
||||||
void init_queue(void)
|
void init_queue(void)
|
||||||
{
|
{
|
||||||
|
|
@ -819,75 +840,149 @@ void draw_radial_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Signed Distance Field functions --------------------------------------
|
/* Signed Distance Field functions --------------------------------------
|
||||||
Based on `image-sdf` <https://github.com/mattdesl/image-sdf>
|
Based on `8ssedt` example code by Richard Mitton <http://www.codersnotes.com/about/>, 2009
|
||||||
Copyright (c) 2014 by Matt DesLauriers (https://www.mattdesl.com/)
|
Converted to C for Tux Paint by Bill Kendrick <bill@newbreedsoftware.com>, 2023
|
||||||
in JavaScript, released under the The MIT License
|
|
||||||
(which was based on DistanceFieldGenerator.java from `libgdx` <https://github.com/libgdx>
|
|
||||||
by Thomas ten Cate, in Java, released under the Apache License 2.0).
|
|
||||||
|
|
||||||
Converted to C for Tux Paint by Bill Kendrick 2023, GPL v2
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int squareDist(int x1, int y1, int x2, int y2) {
|
void sdf_pt_get(sdf_grid * g, int x, int y, sdf_point * p)
|
||||||
int dx, dy;
|
{
|
||||||
|
if (x >= 0 && x < g->w && y >= 0 && y < g->h) {
|
||||||
dx = (x1 - x2);
|
memcpy(p, &(g->grid[y][x]), sizeof(sdf_point));
|
||||||
dy = (y1 - y2);
|
} else {
|
||||||
return (dx * dx) + (dy * dy);
|
memcpy(p, &(sdf_pt_empty), sizeof(sdf_point));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float findSignedDistance(Uint8 * bitmask, int w, int h, int x, int y, float spread)
|
void sdf_pt_put(sdf_grid * g, int x, int y, sdf_point p)
|
||||||
{
|
{
|
||||||
int startX, endX, startY, endY, xx, yy;
|
memcpy(&(g->grid[y][x]), &p, sizeof(sdf_point));
|
||||||
int base;
|
}
|
||||||
int closestSquareDist, delta, sqDist;
|
|
||||||
float closestDist, deltaf;
|
|
||||||
|
|
||||||
base = bitmask[y * w + x];
|
int sdf_distsq(sdf_point p)
|
||||||
|
{
|
||||||
|
return ((p.dx * p.dx) + (p.dy * p.dy));
|
||||||
|
}
|
||||||
|
|
||||||
deltaf = ceilf(spread);
|
void sdf_compare(sdf_grid * g, sdf_point * p, int x, int y, int offsetx, int offsety)
|
||||||
delta = (int) deltaf;
|
{
|
||||||
startX = max((int) 0, x - delta);
|
sdf_point other;
|
||||||
startY = max((int) 0, y - delta);
|
|
||||||
endX = min(w - 1, x + delta);
|
|
||||||
endY = min(h - 1, y + delta);
|
|
||||||
|
|
||||||
closestSquareDist = (delta * delta);
|
sdf_pt_get(g, x + offsetx, y + offsety, &other);
|
||||||
|
other.dx += offsetx;
|
||||||
|
other.dy += offsety;
|
||||||
|
|
||||||
for (yy = startY; yy <= endY; yy++) {
|
if (sdf_distsq(other) < sdf_distsq(*p)) {
|
||||||
for (xx = startX; xx <= endX; xx++) {
|
p->dx = other.dx;
|
||||||
if (base != bitmask[yy * w + xx]) {
|
p->dy = other.dy;
|
||||||
sqDist = squareDist(x, y, xx, yy);
|
}
|
||||||
if (sqDist < closestSquareDist) {
|
}
|
||||||
closestSquareDist = sqDist;
|
|
||||||
}
|
int malloc_sdf_grid(sdf_grid * g, int w, int h) {
|
||||||
}
|
int i, abort;
|
||||||
|
|
||||||
|
g->w = w;
|
||||||
|
g->h = h;
|
||||||
|
g->grid = (sdf_point * *) malloc(h * sizeof(sdf_point *));
|
||||||
|
if (g->grid == NULL) {
|
||||||
|
fprintf(stderr, "malloc_sdf_grid() cannot malloc() g->grid!\n");
|
||||||
|
free(g);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < h; i++) {
|
||||||
|
g->grid[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort = 0;
|
||||||
|
for (i = 0; i < h && !abort; i++) {
|
||||||
|
g->grid[i] = (sdf_point *) malloc(w * sizeof(sdf_point));
|
||||||
|
if (g->grid[i] == NULL) {
|
||||||
|
abort = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closestDist = sqrt((float) closestSquareDist);
|
if (abort) {
|
||||||
return (base ? 1.0 : -1.0) * min(closestDist, spread);
|
fprintf(stderr, "malloc_sdf_grid() cannot malloc() g->grid[]!\n");
|
||||||
|
free_sdf_grid(g);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void compute_sdf(float * sdf, Uint8 * bitmask, int w, int h)
|
void free_sdf_grid(sdf_grid * g) {
|
||||||
{
|
int i;
|
||||||
int x, y;
|
|
||||||
float spread, signedDistance, alpha;
|
|
||||||
|
|
||||||
spread = 1.0;
|
for (i = 0; i < g->h; i++) {
|
||||||
|
if (g->grid[i] != NULL) {
|
||||||
|
free(g->grid[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(g->grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sdf_fill_bitmask_to_sdf_grids(Uint8 * bitmask, int w, int h, sdf_grid * g1, sdf_grid * g2) {
|
||||||
|
int x, y;
|
||||||
|
|
||||||
for (y = 0; y < h; y++) {
|
for (y = 0; y < h; y++) {
|
||||||
for (x = 0; x < w; x++) {
|
for (x = 0; x < w; x++) {
|
||||||
signedDistance = findSignedDistance(bitmask, w, h, x, y, spread);
|
if (bitmask[y * w + x]) {
|
||||||
alpha = 0.5 + 0.5 * (signedDistance / spread);
|
sdf_pt_put(g1, x, y, sdf_pt_inside);
|
||||||
alpha = min((float) 1.0, max((float) 0.0, alpha));
|
sdf_pt_put(g2, x, y, sdf_pt_empty);
|
||||||
sdf[y * w + x] = alpha;
|
} else {
|
||||||
|
sdf_pt_put(g1, x, y, sdf_pt_empty);
|
||||||
|
sdf_pt_put(g2, x, y, sdf_pt_inside);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sdf_generate(sdf_grid * g) {
|
||||||
|
int x, y;
|
||||||
|
sdf_point p;
|
||||||
|
|
||||||
|
/* Pass 0 */
|
||||||
|
for (y = 0; y < g->h; y++) {
|
||||||
|
for (x = 0; x < g->w; x++) {
|
||||||
|
sdf_pt_get(g, x, y, &p);
|
||||||
|
sdf_compare(g, &p, x, y, -1, 0);
|
||||||
|
sdf_compare(g, &p, x, y, 0, -1);
|
||||||
|
sdf_compare(g, &p, x, y, -1, -1);
|
||||||
|
sdf_compare(g, &p, x, y, 1, -1);
|
||||||
|
sdf_pt_put(g, x, y, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x = g->w - 1; x >= 0; x--) {
|
||||||
|
sdf_pt_get(g, x, y, &p);
|
||||||
|
sdf_compare(g, &p, x, y, 1, 0);
|
||||||
|
sdf_pt_put(g, x, y, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pass 1 */
|
||||||
|
for (y = g->h - 1; y >= 0; y--) {
|
||||||
|
for (x = g->w - 1; x >= 0; x--) {
|
||||||
|
sdf_pt_get(g, x, y, &p);
|
||||||
|
sdf_compare(g, &p, x, y, 1, 0);
|
||||||
|
sdf_compare(g, &p, x, y, 0, 1);
|
||||||
|
sdf_compare(g, &p, x, y, -1, 1);
|
||||||
|
sdf_compare(g, &p, x, y, 1, 1);
|
||||||
|
sdf_pt_put(g, x, y, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x = 0; x < g->w; x++) {
|
||||||
|
sdf_pt_get(g, x, y, &p);
|
||||||
|
sdf_compare(g, &p, x, y, -1, 0);
|
||||||
|
sdf_pt_put(g, x, y, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* End of Signed Distance Field functions ------------------------------- */
|
/* End of Signed Distance Field functions ------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
int x_right, int y_bottom,
|
int x_right, int y_bottom,
|
||||||
Uint32 draw_color, Uint8 * touched)
|
Uint32 draw_color, Uint8 * touched)
|
||||||
|
|
@ -895,26 +990,35 @@ void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
Uint32 old_colr, new_colr;
|
Uint32 old_colr, new_colr;
|
||||||
int w, h;
|
int w, h;
|
||||||
int xx, yy;
|
int xx, yy;
|
||||||
int pix, pix2;
|
int pix_idx;
|
||||||
|
int scale;
|
||||||
float ratio;
|
float ratio;
|
||||||
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 * sdf;
|
|
||||||
Uint8 * bitmask;
|
Uint8 * bitmask;
|
||||||
|
sdf_grid g1, g2;
|
||||||
|
|
||||||
/* Create space for bitmask (based on `touched`) and SDF output
|
/* Create space for bitmask (based on `touched`) and SDF output
|
||||||
large enough for the area being filled */
|
large enough for the area being filled */
|
||||||
w = x_right - x_left + 1;
|
w = x_right - x_left + 1;
|
||||||
h = y_bottom - y_top + 1;
|
h = y_bottom - y_top + 1;
|
||||||
sdf = (float *) malloc(sizeof(float) * w * h);
|
|
||||||
if (sdf == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bitmask = (Uint8 *) malloc(sizeof(Uint8) * w * h);
|
bitmask = (Uint8 *) malloc(sizeof(Uint8) * w * h);
|
||||||
if (bitmask == NULL) {
|
if (bitmask == NULL) {
|
||||||
free(sdf);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!malloc_sdf_grid(&g1, w, h)) {
|
||||||
|
free(bitmask);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!malloc_sdf_grid(&g2, w, h)) {
|
||||||
|
free(bitmask);
|
||||||
|
free_sdf_grid(&g1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Convert the `touched` values into a bitmask to feed into the SDF routines */
|
||||||
for (yy = 0; yy < h; yy++) {
|
for (yy = 0; yy < h; yy++) {
|
||||||
for (xx = 0; xx < w; xx++) {
|
for (xx = 0; xx < w; xx++) {
|
||||||
/* Converting 0-255 to 0/1 */
|
/* Converting 0-255 to 0/1 */
|
||||||
|
|
@ -922,8 +1026,11 @@ void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute the alpha mask using a Signed Distance Field */
|
/* Compute the Signed Distance Field (we'll use as an alpha mask) */
|
||||||
compute_sdf(sdf, bitmask, w, h);
|
|
||||||
|
sdf_fill_bitmask_to_sdf_grids(bitmask, w, h, &g1, &g2);
|
||||||
|
sdf_generate(&g1);
|
||||||
|
sdf_generate(&g2);
|
||||||
|
|
||||||
/* Get our target color */
|
/* Get our target color */
|
||||||
SDL_GetRGB(draw_color, canvas->format, &draw_r, &draw_g, &draw_b);
|
SDL_GetRGB(draw_color, canvas->format, &draw_r, &draw_g, &draw_b);
|
||||||
|
|
@ -934,16 +1041,33 @@ void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
for (xx = x_left; xx <= x_right; xx++)
|
for (xx = x_left; xx <= x_right; xx++)
|
||||||
{
|
{
|
||||||
/* Only alter the pixels within the flood itself */
|
/* Only alter the pixels within the flood itself */
|
||||||
pix = (yy * canvas->w) + xx;
|
pix_idx = (yy * canvas->w) + xx;
|
||||||
|
|
||||||
if (pix >= 0 && pix < canvas->w * canvas->h)
|
if (pix_idx >= 0 && pix_idx < canvas->w * canvas->h)
|
||||||
{
|
{
|
||||||
if (touched[pix])
|
if (touched[pix_idx])
|
||||||
{
|
{
|
||||||
pix2 = ((yy - y_top) * w) + (xx - x_left);
|
sdf_point p;
|
||||||
|
double dist1, dist2, dist;
|
||||||
|
int gx, gy;
|
||||||
|
|
||||||
|
gx = xx - x_left;
|
||||||
|
gy = yy - y_top;
|
||||||
|
|
||||||
|
sdf_pt_get(&g1, gx, gy, &p);
|
||||||
|
dist1 = sqrt(sdf_distsq(p));
|
||||||
|
|
||||||
|
sdf_pt_get(&g2, gx, gy, &p);
|
||||||
|
dist2 = sqrt(sdf_distsq(p));
|
||||||
|
|
||||||
|
dist = dist1 - dist2;
|
||||||
|
|
||||||
/* Determine the distance from the click point */
|
/* Determine the distance from the click point */
|
||||||
ratio = 1.0 - sdf[pix2];
|
ratio = ((float) ((dist * 3) + 255)) / 255.0; // Magic numbers :-( -bjk 2023.02.25
|
||||||
|
if (ratio < 0.0)
|
||||||
|
ratio = 0.0;
|
||||||
|
else if (ratio > 1.0)
|
||||||
|
ratio = 1.0;
|
||||||
|
|
||||||
/* Get the old color, and blend it (with a distance-based ratio) with the target color */
|
/* Get the old color, and blend it (with a distance-based ratio) with the target color */
|
||||||
old_colr =
|
old_colr =
|
||||||
|
|
@ -951,7 +1075,7 @@ void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
SDL_GetRGB(old_colr, canvas->format, &old_r, &old_g, &old_b);
|
SDL_GetRGB(old_colr, canvas->format, &old_r, &old_g, &old_b);
|
||||||
|
|
||||||
/* Apply fuzziness at any antialiased edges we detected */
|
/* Apply fuzziness at any antialiased edges we detected */
|
||||||
ratio = (ratio * ((float) touched[pix] / 255.0));
|
ratio = (ratio * ((float) touched[pix_idx] / 255.0));
|
||||||
|
|
||||||
new_r =
|
new_r =
|
||||||
(Uint8) (((float) old_r) * ratio +
|
(Uint8) (((float) old_r) * ratio +
|
||||||
|
|
@ -970,6 +1094,7 @@ void draw_shaped_gradient(SDL_Surface * canvas, int x_left, int y_top,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(sdf);
|
|
||||||
free(bitmask);
|
free(bitmask);
|
||||||
|
free_sdf_grid(&g1);
|
||||||
|
free_sdf_grid(&g2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue