diff --git a/ChangeLog b/ChangeLog index fe5543bc8a..467ff77e56 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2003-10-01 Simon Budig + + * app/base/boundary.[ch]: Implemented simplify_boundary (), + which tries to reduce the number of coordinates to get + better interpolation for stroking. + + The results still need tweaking. + + * app/core/gimpdrawable-stroke.c: Use it. + 2003-10-01 Sven Neumann * libgimpwidgets/gimpunitmenu.[ch]: diff --git a/app/base/boundary.c b/app/base/boundary.c index 42b9c324ee..7bc13de2a7 100644 --- a/app/base/boundary.c +++ b/app/base/boundary.c @@ -22,6 +22,8 @@ #include +#include "libgimpmath/gimpmath.h" + #include "base-types.h" #include "boundary.h" @@ -86,7 +88,10 @@ static void generate_boundary (PixelRegion *PR, gint x2, gint y2, guchar threshold); - +static void simplify_subdivide (const BoundSeg *segs, + gint start_idx, + gint end_idx, + GArray **ret_points); /* Function definitions */ @@ -568,3 +573,166 @@ sort_boundary (const BoundSeg *segs, /* Return the new boundary */ return new_segs; } + +/*********************************/ +/* Reducing the number of points */ +/* We expect the Boundary to be */ +/* sorted. */ + +BoundSeg * +simplify_boundary (const BoundSeg *stroke_segs, + gint num_groups, + gint *num_segs) +{ + GArray *new_bounds; + GArray *points; + BoundSeg *ret_bounds; + gint i, j, seg, start, n_points; + + g_return_val_if_fail (num_segs != NULL, NULL); + + new_bounds = g_array_new (FALSE, FALSE, sizeof (BoundSeg)); + + seg = 0; + + for (i = 0; i < num_groups; i++) + { + start = seg; + n_points = 0; + + while (stroke_segs[seg].x1 != -1 || + stroke_segs[seg].x2 != -1 || + stroke_segs[seg].y1 != -1 || + stroke_segs[seg].y2 != -1) + { + n_points++; + seg++; + } + + if (n_points > 0) + { + points = g_array_new (FALSE, FALSE, sizeof (gint)); + + simplify_subdivide (stroke_segs, start, start + n_points - 1, + &points); + + for (j = 0; j < points->len; j++) + g_array_append_val (new_bounds, + stroke_segs [g_array_index (points, gint, j)]); + + if (n_points > 1) + g_array_append_val (new_bounds, + stroke_segs [start + n_points - 1]); + + g_array_append_val (new_bounds, stroke_segs[seg]); + + g_array_free (points, TRUE); + } + seg++; + } + + if (new_bounds->len > 0) + { + ret_bounds = (BoundSeg *) new_bounds->data; + *num_segs = new_bounds->len; + } + else + { + ret_bounds = NULL; + *num_segs = 0; + } + + g_array_free (new_bounds, FALSE); + + return ret_bounds; +} + +static void +simplify_subdivide (const BoundSeg *segs, + gint start_idx, + gint end_idx, + GArray **ret_points) +{ + gint maxdist_idx; + gint dist, maxdist; + gint i, dx, dy; + gdouble realdist; + + /* g_printerr ("subdiv %d - %d\n", start_idx, end_idx); */ + + if (end_idx - start_idx < 2) + { + *ret_points = g_array_append_val (*ret_points, start_idx); + /* g_printerr (" %d\n", start_idx); */ + return; + } + + maxdist = 0; + maxdist_idx = -1; + + if (segs[start_idx].x1 == segs[end_idx].x1 && + segs[start_idx].y1 == segs[end_idx].y1) + { + /* start and endpoint are at the same coordinates */ + for (i = start_idx + 1; i < end_idx; i++) + { + /* compare the sqared distances */ + dist = (SQR (segs[i].x1 - segs[start_idx].x1) + + SQR (segs[i].y1 - segs[start_idx].y1)); + if (dist > maxdist) + { + maxdist = dist; + maxdist_idx = i; + } + } + realdist = sqrt ((gdouble) maxdist); + } + else + { + dx = segs[end_idx].x1 - segs[start_idx].x1; + dy = segs[end_idx].y1 - segs[start_idx].y1; + + /* g_printerr ("dx: %d, dy: %d\n", dx, dy); */ + + for (i = start_idx + 1; i < end_idx; i++) + { + /* this is not really the euclidic distance, but is + * proportional for this part of the line + * (for the real distance we'd have to divide by + * (SQR(dx)+SQR(dy))) + */ + dist = (dx * (segs[start_idx].y1 - segs[i].y1) - + dy * (segs[start_idx].x1 - segs[i].x1)); + + if (dist < 0) + dist *= -1; + + + if (dist > maxdist) + { + maxdist = dist; + maxdist_idx = i; + } + } + realdist = ((gdouble) maxdist) / sqrt ((gdouble) (SQR (dx) + SQR (dy))); + } + + /* g_printerr ("Index %d, x: %d, y: %d, distance: %.4f\n", maxdist_idx, + segs[maxdist_idx].x1, segs[maxdist_idx].y1, realdist); */ + /* threshold is chosen to catch 45 degree stairs */ + + if (realdist <= 1.0) + { + *ret_points = g_array_append_val (*ret_points, start_idx); + /* g_printerr (" %d\n", start_idx); */ + return; + } + + /* Simons hack */ + maxdist_idx = (start_idx + end_idx) / 2; + + simplify_subdivide (segs, start_idx, maxdist_idx, ret_points); + simplify_subdivide (segs, maxdist_idx, end_idx, ret_points); + +} + diff --git a/app/base/boundary.h b/app/base/boundary.h index a5e379e645..3ba3fb2850 100644 --- a/app/base/boundary.h +++ b/app/base/boundary.h @@ -53,6 +53,10 @@ BoundSeg * find_mask_boundary (PixelRegion *maskPR, BoundSeg * sort_boundary (const BoundSeg *segs, gint num_segs, gint *num_groups); +BoundSeg * simplify_boundary (const BoundSeg *stroke_segs, + gint num_groups, + gint *num_segs); + #endif /* __BOUNDARY_H__ */ diff --git a/app/core/gimpdrawable-stroke.c b/app/core/gimpdrawable-stroke.c index e8f5f98380..fb852d8134 100644 --- a/app/core/gimpdrawable-stroke.c +++ b/app/core/gimpdrawable-stroke.c @@ -68,6 +68,7 @@ gimp_drawable_stroke_boundary (GimpDrawable *drawable, gint offset_y) { GimpScanConvert *scan_convert; + BoundSeg *sorted_segs; BoundSeg *stroke_segs; gint n_stroke_segs; GimpVector2 *points; @@ -80,7 +81,10 @@ gimp_drawable_stroke_boundary (GimpDrawable *drawable, g_return_if_fail (bound_segs != NULL); g_return_if_fail (n_bound_segs > 0); - stroke_segs = sort_boundary (bound_segs, n_bound_segs, &n_stroke_segs); + sorted_segs = sort_boundary (bound_segs, n_bound_segs, &n_stroke_segs); + stroke_segs = simplify_boundary (sorted_segs, n_stroke_segs, &n_bound_segs); + + g_free (sorted_segs); if (n_stroke_segs == 0) return;