diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/3rdparty/autotrace/fit.c | |
parent | Initial commit. (diff) | |
download | inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/3rdparty/autotrace/fit.c | 1442 |
1 files changed, 1442 insertions, 0 deletions
diff --git a/src/3rdparty/autotrace/fit.c b/src/3rdparty/autotrace/fit.c new file mode 100644 index 0000000..a6ca2b6 --- /dev/null +++ b/src/3rdparty/autotrace/fit.c @@ -0,0 +1,1442 @@ +/* fit.c: turn a bitmap representation of a curve into a list of splines. + Some of the ideas, but not the code, comes from the Phoenix thesis. + See README for the reference. + + The code was partially derived from limn. + + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* Def: HAVE_CONFIG_H */ + +#include "autotrace.h" +#include "fit.h" +#include "logreport.h" +#include "spline.h" +#include "vector.h" +#include "curve.h" +#include "pxl-outline.h" +#include "epsilon-equal.h" +#include "xstd.h" +#include <math.h> +#ifndef FLT_MAX +#include <limits.h> +#include <float.h> +#endif +#ifndef FLT_MIN +#include <limits.h> +#include <float.h> +#endif +#include <string.h> +#include <assert.h> + +#define SQUARE(x) ((x) * (x)) +#define CUBE(x) ((x) * (x) * (x)) + +/* We need to manipulate lists of array indices. */ + +typedef struct index_list { + unsigned *data; + unsigned length; +} index_list_type; + +/* The usual accessor macros. */ +#define GET_INDEX(i_l, n) ((i_l).data[n]) +#define INDEX_LIST_LENGTH(i_l) ((i_l).length) +#define GET_LAST_INDEX(i_l) ((i_l).data[INDEX_LIST_LENGTH (i_l) - 1]) + +static void append_index(index_list_type *, unsigned); +static void free_index_list(index_list_type *); +static index_list_type new_index_list(void); +static void remove_adjacent_corners(index_list_type *, unsigned, gboolean, at_exception_type * exception); +static void change_bad_lines(spline_list_type *, fitting_opts_type *); +static void filter(curve_type, fitting_opts_type *); +static void find_vectors(unsigned, pixel_outline_type, vector_type *, vector_type *, unsigned); +static index_list_type find_corners(pixel_outline_type, fitting_opts_type *, at_exception_type * exception); +static gfloat find_error(curve_type, spline_type, unsigned *, at_exception_type * exception); +static vector_type find_half_tangent(curve_type, gboolean start, unsigned *, unsigned); +static void find_tangent(curve_type, gboolean, gboolean, unsigned); +static spline_type fit_one_spline(curve_type, at_exception_type * exception); +static spline_list_type *fit_curve(curve_type, fitting_opts_type *, at_exception_type * exception); +static spline_list_type fit_curve_list(curve_list_type, fitting_opts_type *, at_distance_map *, at_exception_type * exception); +static spline_list_type *fit_with_least_squares(curve_type, fitting_opts_type *, at_exception_type * exception); +static spline_list_type *fit_with_line(curve_type); +static void remove_knee_points(curve_type, gboolean); +static void set_initial_parameter_values(curve_type); +static gboolean spline_linear_enough(spline_type *, curve_type, fitting_opts_type *); +static curve_list_array_type split_at_corners(pixel_outline_list_type, fitting_opts_type *, at_exception_type * exception); +static at_coord real_to_int_coord(at_real_coord); +static gfloat distance(at_real_coord, at_real_coord); + +/* Get a new set of fitting options */ +fitting_opts_type new_fitting_opts(void) +{ + fitting_opts_type fitting_opts; + + fitting_opts.background_color = NULL; + fitting_opts.charcode = 0; + fitting_opts.color_count = 0; + fitting_opts.corner_always_threshold = (gfloat) 60.0; + fitting_opts.corner_surround = 4; + fitting_opts.corner_threshold = (gfloat) 100.0; + fitting_opts.error_threshold = (gfloat) 2.0; + fitting_opts.filter_iterations = 4; + fitting_opts.line_reversion_threshold = (gfloat) .01; + fitting_opts.line_threshold = (gfloat) 1.0; + fitting_opts.remove_adjacent_corners = FALSE; + fitting_opts.tangent_surround = 3; + fitting_opts.despeckle_level = 0; + fitting_opts.despeckle_tightness = 2.0; + fitting_opts.noise_removal = (gfloat) 0.99; + fitting_opts.centerline = FALSE; + fitting_opts.preserve_width = FALSE; + fitting_opts.width_weight_factor = 6.0; + + return (fitting_opts); +} + +/* The top-level call that transforms the list of pixels in the outlines + of the original character to a list of spline lists fitted to those + pixels. */ + +spline_list_array_type fitted_splines(pixel_outline_list_type pixel_outline_list, fitting_opts_type * fitting_opts, at_distance_map * dist, unsigned short width, unsigned short height, at_exception_type * exception, at_progress_func notify_progress, gpointer progress_data, at_testcancel_func test_cancel, gpointer testcancel_data) +{ + unsigned this_list; + + spline_list_array_type char_splines = new_spline_list_array(); + curve_list_array_type curve_array = split_at_corners(pixel_outline_list, + fitting_opts, + exception); + + char_splines.centerline = fitting_opts->centerline; + char_splines.preserve_width = fitting_opts->preserve_width; + char_splines.width_weight_factor = fitting_opts->width_weight_factor; + + if (fitting_opts->background_color) + char_splines.background_color = at_color_copy(fitting_opts->background_color); + else + char_splines.background_color = NULL; + /* Set dummy values. Real value is set in upper context. */ + char_splines.width = width; + char_splines.height = height; + + for (this_list = 0; this_list < CURVE_LIST_ARRAY_LENGTH(curve_array); this_list++) { + spline_list_type curve_list_splines; + curve_list_type curves = CURVE_LIST_ARRAY_ELT(curve_array, this_list); + + if (notify_progress) + notify_progress((((gfloat) this_list) / ((gfloat) CURVE_LIST_ARRAY_LENGTH(curve_array) * (gfloat) 3.0) + (gfloat) 0.333), progress_data); + if (test_cancel && test_cancel(testcancel_data)) + goto cleanup; + + LOG("\nFitting curve list #%u:\n", this_list); + + curve_list_splines = fit_curve_list(curves, fitting_opts, dist, exception); + if (at_exception_got_fatal(exception)) { + if (char_splines.background_color) + at_color_free(char_splines.background_color); + goto cleanup; + } + curve_list_splines.clockwise = curves.clockwise; + + memcpy(&(curve_list_splines.color), &(O_LIST_OUTLINE(pixel_outline_list, this_list).color), sizeof(at_color)); + append_spline_list(&char_splines, curve_list_splines); + } +cleanup: + free_curve_list_array(&curve_array, notify_progress, progress_data); + + return char_splines; +} + +/* Fit the list of curves CURVE_LIST to a list of splines, and return + it. CURVE_LIST represents a single closed paths, e.g., either the + inside or outside outline of an `o'. */ + +static spline_list_type fit_curve_list(curve_list_type curve_list, fitting_opts_type * fitting_opts, at_distance_map * dist, at_exception_type * exception) +{ + curve_type curve; + unsigned this_curve, this_spline; + unsigned curve_list_length = CURVE_LIST_LENGTH(curve_list); + spline_list_type curve_list_splines = empty_spline_list(); + + curve_list_splines.open = curve_list.open; + + /* Remove the extraneous ``knee'' points before filtering. Since the + corners have already been found, we don't need to worry about + removing a point that should be a corner. */ + + LOG("\nRemoving knees:\n"); + for (this_curve = 0; this_curve < curve_list_length; this_curve++) { + LOG("#%u:", this_curve); + remove_knee_points(CURVE_LIST_ELT(curve_list, this_curve), CURVE_LIST_CLOCKWISE(curve_list)); + } + + if (dist != NULL) { + unsigned this_point; + unsigned height = dist->height; + for (this_curve = 0; this_curve < curve_list_length; this_curve++) { + curve = CURVE_LIST_ELT(curve_list, this_curve); + for (this_point = 0; this_point < CURVE_LENGTH(curve); this_point++) { + unsigned x, y; + float width, w; + at_real_coord *coord = &CURVE_POINT(curve, this_point); + x = (unsigned)(coord->x); + y = height - (unsigned)(coord->y) - 1; + + /* Each (x, y) is a point on the skeleton of the curve, which + might be offset from the TRUE centerline, where the width + is maximal. Therefore, use as the local line width the + maximum distance over the neighborhood of (x, y). */ + width = dist->d[y][x]; + if (y >= 1) { + if ((w = dist->d[y - 1][x]) > width) + width = w; + if (x >= 1) { + if ((w = dist->d[y][x - 1]) > width) + width = w; + if ((w = dist->d[y - 1][x - 1]) > width) + width = w; + } + if (x + 1 < dist->width) { + if ((w = dist->d[y][x + 1]) > width) + width = w; + if ((w = dist->d[y - 1][x + 1]) > width) + width = w; + } + } + if (y + 1 < height) { + if ((w = dist->d[y + 1][x]) > width) + width = w; + if (x >= 1 && (w = dist->d[y + 1][x - 1]) > width) + width = w; + if (x + 1 < dist->width && (w = dist->d[y + 1][x + 1]) > width) + width = w; + } + coord->z = width * (fitting_opts->width_weight_factor); + } + } + } + + /* We filter all the curves in CURVE_LIST at once; otherwise, we would + look at an unfiltered curve when computing tangents. */ + + LOG("\nFiltering curves:\n"); + for (this_curve = 0; this_curve < curve_list.length; this_curve++) { + LOG("#%u: ", this_curve); + filter(CURVE_LIST_ELT(curve_list, this_curve), fitting_opts); + } + + /* Make the first point in the first curve also be the last point in + the last curve, so the fit to the whole curve list will begin and + end at the same point. This may cause slight errors in computing + the tangents and t values, but it's worth it for the continuity. + Of course we don't want to do this if the two points are already + the same, as they are if the curve is cyclic. (We don't append it + earlier, in `split_at_corners', because that confuses the + filtering.) Finally, we can't append the point if the curve is + exactly three points long, because we aren't adding any more data, + and three points isn't enough to determine a spline. Therefore, + the fitting will fail. */ + curve = CURVE_LIST_ELT(curve_list, 0); + if (CURVE_CYCLIC(curve) == TRUE) + append_point(curve, CURVE_POINT(curve, 0)); + + /* Finally, fit each curve in the list to a list of splines. */ + for (this_curve = 0; this_curve < curve_list_length; this_curve++) { + spline_list_type *curve_splines; + curve_type current_curve = CURVE_LIST_ELT(curve_list, this_curve); + + LOG("\nFitting curve #%u:\n", this_curve); + + curve_splines = fit_curve(current_curve, fitting_opts, exception); + if (at_exception_got_fatal(exception)) + goto cleanup; + else if (curve_splines == NULL) { + LOG("Could not fit curve #%u", this_curve); + at_exception_warning(exception, "Could not fit curve"); + } else { + LOG("Fitted splines for curve #%u:\n", this_curve); + for (this_spline = 0; this_spline < SPLINE_LIST_LENGTH(*curve_splines); this_spline++) { + LOG(" %u: ", this_spline); + if (logging) + print_spline(SPLINE_LIST_ELT(*curve_splines, this_spline)); + } + + /* After fitting, we may need to change some would-be lines + back to curves, because they are in a list with other + curves. */ + change_bad_lines(curve_splines, fitting_opts); + + concat_spline_lists(&curve_list_splines, *curve_splines); + free_spline_list(*curve_splines); + free(curve_splines); + } + } + + if (logging) { + LOG("\nFitted splines are:\n"); + for (this_spline = 0; this_spline < SPLINE_LIST_LENGTH(curve_list_splines); this_spline++) { + LOG(" %u: ", this_spline); + print_spline(SPLINE_LIST_ELT(curve_list_splines, this_spline)); + } + } +cleanup: + return curve_list_splines; +} + +/* Transform a set of locations to a list of splines (the fewer the + better). We are guaranteed that CURVE does not contain any corners. + We return NULL if we cannot fit the points at all. */ + +static spline_list_type *fit_curve(curve_type curve, fitting_opts_type * fitting_opts, at_exception_type * exception) +{ + spline_list_type *fittedsplines; + + if (CURVE_LENGTH(curve) < 2) { + LOG("Tried to fit curve with less than two points"); + at_exception_warning(exception, "Tried to fit curve with less than two points"); + return NULL; + } + + /* Do we have enough points to fit with a spline? */ + fittedsplines = CURVE_LENGTH(curve) < 4 ? fit_with_line(curve) + : fit_with_least_squares(curve, fitting_opts, exception); + + return fittedsplines; +} + +/* As mentioned above, the first step is to find the corners in + PIXEL_LIST, the list of points. (Presumably we can't fit a single + spline around a corner.) The general strategy is to look through all + the points, remembering which we want to consider corners. Then go + through that list, producing the curve_list. This is dictated by the + fact that PIXEL_LIST does not necessarily start on a corner---it just + starts at the character's first outline pixel, going left-to-right, + top-to-bottom. But we want all our splines to start and end on real + corners. + + For example, consider the top of a capital `C' (this is in cmss20): + x + *********** + ****************** + + PIXEL_LIST will start at the pixel below the `x'. If we considered + this pixel a corner, we would wind up matching a very small segment + from there to the end of the line, probably as a straight line, which + is certainly not what we want. + + PIXEL_LIST has one element for each closed outline on the character. + To preserve this information, we return an array of curve_lists, one + element (which in turn consists of several curves, one between each + pair of corners) for each element in PIXEL_LIST. */ + +static curve_list_array_type split_at_corners(pixel_outline_list_type pixel_list, fitting_opts_type * fitting_opts, at_exception_type * exception) +{ + unsigned this_pixel_o; + curve_list_array_type curve_array = new_curve_list_array(); + + LOG("\nFinding corners:\n"); + + for (this_pixel_o = 0; this_pixel_o < O_LIST_LENGTH(pixel_list); this_pixel_o++) { + curve_type curve, first_curve; + index_list_type corner_list; + unsigned p, this_corner; + curve_list_type curve_list = new_curve_list(); + pixel_outline_type pixel_o = O_LIST_OUTLINE(pixel_list, this_pixel_o); + + CURVE_LIST_CLOCKWISE(curve_list) = O_CLOCKWISE(pixel_o); + curve_list.open = pixel_o.open; + + LOG("#%u:", this_pixel_o); + + /* If the outline does not have enough points, we can't do + anything. The endpoints of the outlines are automatically + corners. We need at least `corner_surround' more pixels on + either side of a point before it is conceivable that we might + want another corner. */ + if (O_LENGTH(pixel_o) > fitting_opts->corner_surround * 2 + 2) + corner_list = find_corners(pixel_o, fitting_opts, exception); + + else { + int surround; + if ((surround = (int)(O_LENGTH(pixel_o) - 3) / 2) >= 2) { + unsigned save_corner_surround = fitting_opts->corner_surround; + fitting_opts->corner_surround = surround; + corner_list = find_corners(pixel_o, fitting_opts, exception); + fitting_opts->corner_surround = save_corner_surround; + } else { + corner_list.length = 0; + corner_list.data = NULL; + } + } + + /* Remember the first curve so we can make it be the `next' of the + last one. (And vice versa.) */ + first_curve = new_curve(); + + curve = first_curve; + + if (corner_list.length == 0) { /* No corners. Use all of the pixel outline as the curve. */ + for (p = 0; p < O_LENGTH(pixel_o); p++) + append_pixel(curve, O_COORDINATE(pixel_o, p)); + + if (curve_list.open == TRUE) + CURVE_CYCLIC(curve) = FALSE; + else + CURVE_CYCLIC(curve) = TRUE; + } else { /* Each curve consists of the points between (inclusive) each pair + of corners. */ + for (this_corner = 0; this_corner < corner_list.length - 1; this_corner++) { + curve_type previous_curve = curve; + unsigned corner = GET_INDEX(corner_list, this_corner); + unsigned next_corner = GET_INDEX(corner_list, this_corner + 1); + + for (p = corner; p <= next_corner; p++) + append_pixel(curve, O_COORDINATE(pixel_o, p)); + + append_curve(&curve_list, curve); + curve = new_curve(); + NEXT_CURVE(previous_curve) = curve; + PREVIOUS_CURVE(curve) = previous_curve; + } + + /* The last curve is different. It consists of the points + (inclusive) between the last corner and the end of the list, + and the beginning of the list and the first corner. */ + for (p = GET_LAST_INDEX(corner_list); p < O_LENGTH(pixel_o); p++) + append_pixel(curve, O_COORDINATE(pixel_o, p)); + + if (!pixel_o.open) { + for (p = 0; p <= GET_INDEX(corner_list, 0); p++) + append_pixel(curve, O_COORDINATE(pixel_o, p)); + } else { + curve_type last_curve = PREVIOUS_CURVE(curve); + PREVIOUS_CURVE(first_curve) = NULL; + if (last_curve) + NEXT_CURVE(last_curve) = NULL; + } + } + + LOG(" [%u].\n", corner_list.length); + free_index_list(&corner_list); + + /* Add `curve' to the end of the list, updating the pointers in + the chain. */ + append_curve(&curve_list, curve); + NEXT_CURVE(curve) = first_curve; + PREVIOUS_CURVE(first_curve) = curve; + + /* And now add the just-completed curve list to the array. */ + append_curve_list(&curve_array, curve_list); + } /* End of considering each pixel outline. */ + + return curve_array; +} + +/* We consider a point to be a corner if (1) the angle defined by the + `corner_surround' points coming into it and going out from it is less + than `corner_threshold' degrees, and no point within + `corner_surround' points has a smaller angle; or (2) the angle is less + than `corner_always_threshold' degrees. + + Because of the different cases, it is convenient to have the + following macro to append a corner on to the list we return. The + character argument C is simply so that the different cases can be + distinguished in the log file. */ + +#define APPEND_CORNER(index, angle, c) \ + do \ + { \ + append_index (&corner_list, index); \ + LOG (" (%d,%d)%c%.3f", \ + O_COORDINATE (pixel_outline, index).x, \ + O_COORDINATE (pixel_outline, index).y, \ + c, angle); \ + } \ + while (0) + +static index_list_type find_corners(pixel_outline_type pixel_outline, fitting_opts_type * fitting_opts, at_exception_type * exception) +{ + unsigned p, start_p, end_p; + index_list_type corner_list = new_index_list(); + + start_p = 0; + end_p = O_LENGTH(pixel_outline) - 1; + if (pixel_outline.open) { + if (end_p <= fitting_opts->corner_surround * 2) + return corner_list; + APPEND_CORNER(0, 0.0, '@'); + start_p += fitting_opts->corner_surround; + end_p -= fitting_opts->corner_surround; + } + + /* Consider each pixel on the outline in turn. */ + for (p = start_p; p <= end_p; p++) { + gfloat corner_angle; + vector_type in_vector, out_vector; + + /* Check if the angle is small enough. */ + find_vectors(p, pixel_outline, &in_vector, &out_vector, fitting_opts->corner_surround); + corner_angle = Vangle(in_vector, out_vector, exception); + if (at_exception_got_fatal(exception)) + goto cleanup; + + if (fabs(corner_angle) <= fitting_opts->corner_threshold) { + /* We want to keep looking, instead of just appending the + first pixel we find with a small enough angle, since there + might be another corner within `corner_surround' pixels, with + a smaller angle. If that is the case, we want that one. */ + gfloat best_corner_angle = corner_angle; + unsigned best_corner_index = p; + index_list_type equally_good_list = new_index_list(); + /* As we come into the loop, `p' is the index of the point + that has an angle less than `corner_angle'. We use `i' to + move through the pixels next to that, and `q' for moving + through the adjacent pixels to each `p'. */ + unsigned q = p; + unsigned i = p + 1; + + while (TRUE) { + /* Perhaps the angle is sufficiently small that we want to + consider this a corner, even if it's not the best + (unless we've already wrapped around in the search, + i.e., `q<i', in which case we have already added the + corner, and we don't want to add it again). We want to + do this check on the first candidate we find, as well + as the others in the loop, hence this comes before the + stopping condition. */ + if (corner_angle <= fitting_opts->corner_always_threshold && q >= p) + APPEND_CORNER(q, corner_angle, '\\'); + + /* Exit the loop if we've looked at `corner_surround' + pixels past the best one we found, or if we've looked + at all the pixels. */ + if (i >= best_corner_index + fitting_opts->corner_surround || i >= O_LENGTH(pixel_outline)) + break; + + /* Check the angle. */ + q = i % O_LENGTH(pixel_outline); + find_vectors(q, pixel_outline, &in_vector, &out_vector, fitting_opts->corner_surround); + corner_angle = Vangle(in_vector, out_vector, exception); + if (at_exception_got_fatal(exception)) + goto cleanup; + + /* If we come across a corner that is just as good as the + best one, we should make it a corner, too. This + happens, for example, at the points on the `W' in some + typefaces, where the ``points'' are flat. */ + if (epsilon_equal(corner_angle, best_corner_angle)) + append_index(&equally_good_list, q); + + else if (corner_angle < best_corner_angle) { + best_corner_angle = corner_angle; + /* We want to check `corner_surround' pixels beyond the + new best corner. */ + i = best_corner_index = q; + free_index_list(&equally_good_list); + equally_good_list = new_index_list(); + } + + i++; + } + + /* After we exit the loop, `q' is the index of the last point + we checked. We have already added the corner if + `best_corner_angle' is less than `corner_always_threshold'. + Again, if we've already wrapped around, we don't want to + add the corner again. */ + if (best_corner_angle > fitting_opts->corner_always_threshold && best_corner_index >= p) { + unsigned j; + + APPEND_CORNER(best_corner_index, best_corner_angle, '/'); + + for (j = 0; j < INDEX_LIST_LENGTH(equally_good_list); j++) + APPEND_CORNER(GET_INDEX(equally_good_list, j), best_corner_angle, '@'); + } + free_index_list(&equally_good_list); + + /* If we wrapped around in our search, we're done; otherwise, + we don't want the outer loop to look at the pixels that we + already looked at in searching for the best corner. */ + p = (q < p) ? O_LENGTH(pixel_outline) : q; + } /* End of searching for the best corner. */ + } /* End of considering each pixel. */ + + if (INDEX_LIST_LENGTH(corner_list) > 0) + /* We never want two corners next to each other, since the + only way to fit such a ``curve'' would be with a straight + line, which usually interrupts the continuity dreadfully. */ + remove_adjacent_corners(&corner_list, O_LENGTH(pixel_outline) - (pixel_outline.open ? 2 : 1), fitting_opts->remove_adjacent_corners, exception); +cleanup: + return corner_list; +} + +/* Return the difference vectors coming in and going out of the outline + OUTLINE at the point whose index is TEST_INDEX. In Phoenix, + Schneider looks at a single point on either side of the point we're + considering. That works for him because his points are not touching. + But our points *are* touching, and so we have to look at + `corner_surround' points on either side, to get a better picture of + the outline's shape. */ + +static void find_vectors(unsigned test_index, pixel_outline_type outline, vector_type * in, vector_type * out, unsigned corner_surround) +{ + int i; + unsigned n_done; + at_coord candidate = O_COORDINATE(outline, test_index); + + in->dx = in->dy = in->dz = 0.0; + out->dx = out->dy = out->dz = 0.0; + + /* Add up the differences from p of the `corner_surround' points + before p. */ + for (i = O_PREV(outline, test_index), n_done = 0; n_done < corner_surround; i = O_PREV(outline, i), n_done++) + *in = Vadd(*in, IPsubtract(O_COORDINATE(outline, i), candidate)); + + /* And the points after p. */ + for (i = O_NEXT(outline, test_index), n_done = 0; n_done < corner_surround; i = O_NEXT(outline, i), n_done++) + *out = Vadd(*out, IPsubtract(O_COORDINATE(outline, i), candidate)); +} + +/* Remove adjacent points from the index list LIST. We do this by first + sorting the list and then running through it. Since these lists are + quite short, a straight selection sort (e.g., p.139 of the Art of + Computer Programming, vol.3) is good enough. LAST_INDEX is the index + of the last pixel on the outline, i.e., the next one is the first + pixel. We need this for checking the adjacency of the last corner. + + We need to do this because the adjacent corners turn into + two-pixel-long curves, which can only be fit by straight lines. */ + +static void remove_adjacent_corners(index_list_type * list, unsigned last_index, gboolean remove_adj_corners, at_exception_type * exception) +{ + unsigned j; + unsigned last; + index_list_type new_list = new_index_list(); + + for (j = INDEX_LIST_LENGTH(*list) - 1; j > 0; j--) { + unsigned search; + unsigned temp; + /* Find maximal element below `j'. */ + unsigned max_index = j; + + for (search = 0; search < j; search++) + if (GET_INDEX(*list, search) > GET_INDEX(*list, max_index)) + max_index = search; + + if (max_index != j) { + temp = GET_INDEX(*list, j); + GET_INDEX(*list, j) = GET_INDEX(*list, max_index); + GET_INDEX(*list, max_index) = temp; + + /* xx -- really have to sort? */ + LOG("needed exchange"); + at_exception_warning(exception, "needed exchange"); + } + } + + /* The list is sorted. Now look for adjacent entries. Each time + through the loop we insert the current entry and, if appropriate, + the next entry. */ + for (j = 0; j < INDEX_LIST_LENGTH(*list) - 1; j++) { + unsigned current = GET_INDEX(*list, j); + unsigned next = GET_INDEX(*list, j + 1); + + /* We should never have inserted the same element twice. */ + /* assert (current != next); */ + + if ((remove_adj_corners) && ((next == current + 1) || (next == current))) + j++; + + append_index(&new_list, current); + } + + /* Don't append the last element if it is 1) adjacent to the previous + one; or 2) adjacent to the very first one. */ + last = GET_LAST_INDEX(*list); + if (INDEX_LIST_LENGTH(new_list) == 0 || !(last == GET_LAST_INDEX(new_list) + 1 || (last == last_index && GET_INDEX(*list, 0) == 0))) + append_index(&new_list, last); + + free_index_list(list); + *list = new_list; +} + +/* A ``knee'' is a point which forms a ``right angle'' with its + predecessor and successor. See the documentation (the `Removing + knees' section) for an example and more details. + + The argument CLOCKWISE tells us which direction we're moving. (We + can't figure that information out from just the single segment with + which we are given to work.) + + We should never find two consecutive knees. + + Since the first and last points are corners (unless the curve is + cyclic), it doesn't make sense to remove those. */ + +/* This evaluates to TRUE if the vector V is zero in one direction and + nonzero in the other. */ +#define ONLY_ONE_ZERO(v) \ + (((v).dx == 0.0 && (v).dy != 0.0) || ((v).dy == 0.0 && (v).dx != 0.0)) + +/* There are four possible cases for knees, one for each of the four + corners of a rectangle; and then the cases differ depending on which + direction we are going around the curve. The tests are listed here + in the order of upper left, upper right, lower right, lower left. + Perhaps there is some simple pattern to the + clockwise/counterclockwise differences, but I don't see one. */ +#define CLOCKWISE_KNEE(prev_delta, next_delta) \ + ((prev_delta.dx == -1.0 && next_delta.dy == 1.0) \ + || (prev_delta.dy == 1.0 && next_delta.dx == 1.0) \ + || (prev_delta.dx == 1.0 && next_delta.dy == -1.0) \ + || (prev_delta.dy == -1.0 && next_delta.dx == -1.0)) + +#define COUNTERCLOCKWISE_KNEE(prev_delta, next_delta) \ + ((prev_delta.dy == 1.0 && next_delta.dx == -1.0) \ + || (prev_delta.dx == 1.0 && next_delta.dy == 1.0) \ + || (prev_delta.dy == -1.0 && next_delta.dx == 1.0) \ + || (prev_delta.dx == -1.0 && next_delta.dy == -1.0)) + +static void remove_knee_points(curve_type curve, gboolean clockwise) +{ + unsigned i; + unsigned offset = (CURVE_CYCLIC(curve) == TRUE) ? 0 : 1; + at_coord previous = real_to_int_coord(CURVE_POINT(curve, CURVE_PREV(curve, offset))); + curve_type trimmed_curve = copy_most_of_curve(curve); + + if (CURVE_CYCLIC(curve) == FALSE) + append_pixel(trimmed_curve, real_to_int_coord(CURVE_POINT(curve, 0))); + + for (i = offset; i < CURVE_LENGTH(curve) - offset; i++) { + at_coord current = real_to_int_coord(CURVE_POINT(curve, i)); + at_coord next = real_to_int_coord(CURVE_POINT(curve, CURVE_NEXT(curve, i))); + vector_type prev_delta = IPsubtract(previous, current); + vector_type next_delta = IPsubtract(next, current); + + if (ONLY_ONE_ZERO(prev_delta) && ONLY_ONE_ZERO(next_delta) + && ((clockwise && CLOCKWISE_KNEE(prev_delta, next_delta)) + || (!clockwise && COUNTERCLOCKWISE_KNEE(prev_delta, next_delta)))) + LOG(" (%d,%d)", current.x, current.y); + else { + previous = current; + append_pixel(trimmed_curve, current); + } + } + + if (CURVE_CYCLIC(curve) == FALSE) + append_pixel(trimmed_curve, real_to_int_coord(LAST_CURVE_POINT(curve))); + + if (CURVE_LENGTH(trimmed_curve) == CURVE_LENGTH(curve)) + LOG(" (none)"); + + LOG(".\n"); + + free_curve(curve); + *curve = *trimmed_curve; + free(trimmed_curve); /* free_curve? --- Masatake */ +} + +/* Smooth the curve by adding in neighboring points. Do this + `filter_iterations' times. But don't change the corners. */ + +static void filter(curve_type curve, fitting_opts_type * fitting_opts) +{ + unsigned iteration, this_point; + unsigned offset = (CURVE_CYCLIC(curve) == TRUE) ? 0 : 1; + at_real_coord prev_new_point; + + /* We must have at least three points---the previous one, the current + one, and the next one. But if we don't have at least five, we will + probably collapse the curve down onto a single point, which means + we won't be able to fit it with a spline. */ + if (CURVE_LENGTH(curve) < 5) { + LOG("Length is %u, not enough to filter.\n", CURVE_LENGTH(curve)); + return; + } + + prev_new_point.x = FLT_MAX; + prev_new_point.y = FLT_MAX; + prev_new_point.z = FLT_MAX; + + for (iteration = 0; iteration < fitting_opts->filter_iterations; iteration++) { + curve_type newcurve = copy_most_of_curve(curve); + gboolean collapsed = FALSE; + + /* Keep the first point on the curve. */ + if (offset) + append_point(newcurve, CURVE_POINT(curve, 0)); + + for (this_point = offset; this_point < CURVE_LENGTH(curve) - offset; this_point++) { + vector_type in, out, sum; + at_real_coord new_point; + + /* Calculate the vectors in and out, computed by looking at n points + on either side of this_point. Experimental it was found that 2 is + optimal. */ + + signed int prev, prevprev; /* have to be signed */ + unsigned int next, nextnext; + at_real_coord candidate = CURVE_POINT(curve, this_point); + + prev = CURVE_PREV(curve, this_point); + prevprev = CURVE_PREV(curve, prev); + next = CURVE_NEXT(curve, this_point); + nextnext = CURVE_NEXT(curve, next); + + /* Add up the differences from p of the `surround' points + before p. */ + in.dx = in.dy = in.dz = 0.0; + + in = Vadd(in, Psubtract(CURVE_POINT(curve, prev), candidate)); + if (prevprev >= 0) + in = Vadd(in, Psubtract(CURVE_POINT(curve, prevprev), candidate)); + + /* And the points after p. Don't use more points after p than we + ended up with before it. */ + out.dx = out.dy = out.dz = 0.0; + + out = Vadd(out, Psubtract(CURVE_POINT(curve, next), candidate)); + if (nextnext < CURVE_LENGTH(curve)) + out = Vadd(out, Psubtract(CURVE_POINT(curve, nextnext), candidate)); + + /* Start with the old point. */ + new_point = candidate; + sum = Vadd(in, out); + /* We added 2*n+2 points, so we have to divide the sum by 2*n+2 */ + new_point.x += sum.dx / 6; + new_point.y += sum.dy / 6; + new_point.z += sum.dz / 6; + if (fabs(prev_new_point.x - new_point.x) < 0.3 && fabs(prev_new_point.y - new_point.y) < 0.3 && fabs(prev_new_point.z - new_point.z) < 0.3) { + collapsed = TRUE; + break; + } + + /* Put the newly computed point into a separate curve, so it + doesn't affect future computation (on this iteration). */ + append_point(newcurve, prev_new_point = new_point); + } + + if (collapsed) + free_curve(newcurve); + else { + /* Just as with the first point, we have to keep the last point. */ + if (offset) + append_point(newcurve, LAST_CURVE_POINT(curve)); + + /* Set the original curve to the newly filtered one, and go again. */ + free_curve(curve); + *curve = *newcurve; + } + free(newcurve); + } + + if (logging) + log_curve(curve, FALSE); +} + +/* This routine returns the curve fitted to a straight line in a very + simple way: make the first and last points on the curve be the + endpoints of the line. This simplicity is justified because we are + called only on very short curves. */ + +static spline_list_type *fit_with_line(curve_type curve) +{ + spline_type line; + + LOG("Fitting with straight line:\n"); + + SPLINE_DEGREE(line) = LINEARTYPE; + START_POINT(line) = CONTROL1(line) = CURVE_POINT(curve, 0); + END_POINT(line) = CONTROL2(line) = LAST_CURVE_POINT(curve); + + /* Make sure that this line is never changed to a cubic. */ + SPLINE_LINEARITY(line) = 0; + + if (logging) { + LOG(" "); + print_spline(line); + } + + return new_spline_list_with_spline(line); +} + +/* The least squares method is well described in Schneider's thesis. + Briefly, we try to fit the entire curve with one spline. If that + fails, we subdivide the curve. */ + +static spline_list_type *fit_with_least_squares(curve_type curve, fitting_opts_type * fitting_opts, at_exception_type * exception) +{ + gfloat error = 0, best_error = FLT_MAX; + spline_type spline, best_spline; + spline_list_type *spline_list = NULL; + unsigned worst_point = 0; + gfloat previous_error = FLT_MAX; + + LOG("\nFitting with least squares:\n"); + + /* Phoenix reduces the number of points with a ``linear spline + technique''. But for fitting letterforms, that is + inappropriate. We want all the points we can get. */ + + /* It makes no difference whether we first set the `t' values or + find the tangents. This order makes the documentation a little + more coherent. */ + + LOG("Finding tangents:\n"); + find_tangent(curve, /* to_start */ TRUE, /* cross_curve */ FALSE, + fitting_opts->tangent_surround); + find_tangent(curve, /* to_start */ FALSE, /* cross_curve */ FALSE, + fitting_opts->tangent_surround); + + set_initial_parameter_values(curve); + + /* Now we loop, subdividing, until CURVE has + been fit. */ + while (TRUE) { + spline = best_spline = fit_one_spline(curve, exception); + if (at_exception_got_fatal(exception)) + goto cleanup; + + if (SPLINE_DEGREE(spline) == LINEARTYPE) + LOG(" fitted to line:\n"); + else + LOG(" fitted to spline:\n"); + + if (logging) { + LOG(" "); + print_spline(spline); + } + + if (SPLINE_DEGREE(spline) == LINEARTYPE) + break; + + error = find_error(curve, spline, &worst_point, exception); + if (error <= previous_error) { + best_error = error; + best_spline = spline; + } + break; + } + + if (SPLINE_DEGREE(spline) == LINEARTYPE) { + spline_list = new_spline_list_with_spline(spline); + LOG("Accepted error of %.3f.\n", error); + return (spline_list); + } + + /* Go back to the best fit. */ + spline = best_spline; + error = best_error; + + if (error < fitting_opts->error_threshold && CURVE_CYCLIC(curve) == FALSE) { + /* The points were fitted with a + spline. We end up here whenever a fit is accepted. We have + one more job: see if the ``curve'' that was fit should really + be a straight line. */ + if (spline_linear_enough(&spline, curve, fitting_opts)) { + SPLINE_DEGREE(spline) = LINEARTYPE; + LOG("Changed to line.\n"); + } + spline_list = new_spline_list_with_spline(spline); + LOG("Accepted error of %.3f.\n", error); + } else { + /* We couldn't fit the curve acceptably, so subdivide. */ + unsigned subdivision_index; + spline_list_type *left_spline_list; + spline_list_type *right_spline_list; + curve_type left_curve = new_curve(); + curve_type right_curve = new_curve(); + + /* Keep the linked list of curves intact. */ + NEXT_CURVE(right_curve) = NEXT_CURVE(curve); + PREVIOUS_CURVE(right_curve) = left_curve; + NEXT_CURVE(left_curve) = right_curve; + PREVIOUS_CURVE(left_curve) = curve; + NEXT_CURVE(curve) = left_curve; + + LOG("\nSubdividing (error %.3f):\n", error); + LOG(" Original point: (%.3f,%.3f), #%u.\n", CURVE_POINT(curve, worst_point).x, CURVE_POINT(curve, worst_point).y, worst_point); + subdivision_index = worst_point; + LOG(" Final point: (%.3f,%.3f), #%u.\n", CURVE_POINT(curve, subdivision_index).x, CURVE_POINT(curve, subdivision_index).y, subdivision_index); + + /* The last point of the left-hand curve will also be the first + point of the right-hand curve. */ + CURVE_LENGTH(left_curve) = subdivision_index + 1; + CURVE_LENGTH(right_curve) = CURVE_LENGTH(curve) - subdivision_index; + left_curve->point_list = curve->point_list; + right_curve->point_list = curve->point_list + subdivision_index; + + /* We want to use the tangents of the curve which we are + subdividing for the start tangent for left_curve and the + end tangent for right_curve. */ + CURVE_START_TANGENT(left_curve) = CURVE_START_TANGENT(curve); + CURVE_END_TANGENT(right_curve) = CURVE_END_TANGENT(curve); + + /* We have to set up the two curves before finding the tangent at + the subdivision point. The tangent at that point must be the + same for both curves, or noticeable bumps will occur in the + character. But we want to use information on both sides of the + point to compute the tangent, hence cross_curve = true. */ + find_tangent(left_curve, /* to_start_point: */ FALSE, + /* cross_curve: */ TRUE, fitting_opts->tangent_surround); + CURVE_START_TANGENT(right_curve) = CURVE_END_TANGENT(left_curve); + + /* Now that we've set up the curves, we can fit them. */ + left_spline_list = fit_curve(left_curve, fitting_opts, exception); + if (at_exception_got_fatal(exception)) + /* TODO: Memory allocated for left_curve and right_curve + will leak. */ + goto cleanup; + + right_spline_list = fit_curve(right_curve, fitting_opts, exception); + /* TODO: Memory allocated for left_curve and right_curve + will leak. */ + if (at_exception_got_fatal(exception)) + goto cleanup; + + /* Neither of the subdivided curves could be fit, so fail. */ + if (left_spline_list == NULL && right_spline_list == NULL) + return NULL; + + /* Put the two together (or whichever of them exist). */ + spline_list = new_spline_list(); + + if (left_spline_list == NULL) { + LOG("Could not fit spline to left curve (%lx).\n", (unsigned long)left_curve); + at_exception_warning(exception, "Could not fit left spline list"); + } else { + concat_spline_lists(spline_list, *left_spline_list); + free_spline_list(*left_spline_list); + free(left_spline_list); + } + + if (right_spline_list == NULL) { + LOG("Could not fit spline to right curve (%lx).\n", (unsigned long)right_curve); + at_exception_warning(exception, "Could not fit right spline list"); + } else { + concat_spline_lists(spline_list, *right_spline_list); + free_spline_list(*right_spline_list); + free(right_spline_list); + } + if (CURVE_END_TANGENT(left_curve)) + free(CURVE_END_TANGENT(left_curve)); + free(left_curve); + free(right_curve); + } +cleanup: + return spline_list; +} + +/* Our job here is to find alpha1 (and alpha2), where t1_hat (t2_hat) is + the tangent to CURVE at the starting (ending) point, such that: + + control1 = alpha1*t1_hat + starting point + control2 = alpha2*t2_hat + ending_point + + and the resulting spline (starting_point .. control1 and control2 .. + ending_point) minimizes the least-square error from CURVE. + + See pp.57--59 of the Phoenix thesis. + + The B?(t) here corresponds to B_i^3(U_i) there. + The Bernshte\u in polynomials of degree n are defined by + B_i^n(t) = { n \choose i } t^i (1-t)^{n-i}, i = 0..n */ + +#define B0(t) CUBE ((gfloat) 1.0 - (t)) +#define B1(t) ((gfloat) 3.0 * (t) * SQUARE ((gfloat) 1.0 - (t))) +#define B2(t) ((gfloat) 3.0 * SQUARE (t) * ((gfloat) 1.0 - (t))) +#define B3(t) CUBE (t) + +static spline_type fit_one_spline(curve_type curve, at_exception_type * exception) +{ + /* Since our arrays are zero-based, the `C0' and `C1' here correspond + to `C1' and `C2' in the paper. */ + gfloat X_C1_det, C0_X_det, C0_C1_det; + gfloat alpha1, alpha2; + spline_type spline; + vector_type start_vector, end_vector; + unsigned i; + vector_type *A; + vector_type t1_hat = *CURVE_START_TANGENT(curve); + vector_type t2_hat = *CURVE_END_TANGENT(curve); + gfloat C[2][2] = { {0.0, 0.0}, {0.0, 0.0} }; + gfloat X[2] = { 0.0, 0.0 }; + + XMALLOC(A, CURVE_LENGTH(curve) * 2 * sizeof(vector_type)); /* A dynamically allocated array. */ + + START_POINT(spline) = CURVE_POINT(curve, 0); + END_POINT(spline) = LAST_CURVE_POINT(curve); + start_vector = make_vector(START_POINT(spline)); + end_vector = make_vector(END_POINT(spline)); + + for (i = 0; i < CURVE_LENGTH(curve); i++) { + A[(i << 1) + 0] = Vmult_scalar(t1_hat, B1(CURVE_T(curve, i))); + A[(i << 1) + 1] = Vmult_scalar(t2_hat, B2(CURVE_T(curve, i))); + } + + for (i = 0; i < CURVE_LENGTH(curve); i++) { + vector_type temp, temp0, temp1, temp2, temp3; + vector_type *Ai = A + (i << 1); + + C[0][0] += Vdot(Ai[0], Ai[0]); + C[0][1] += Vdot(Ai[0], Ai[1]); + /* C[1][0] = C[0][1] (this is assigned outside the loop) */ + C[1][1] += Vdot(Ai[1], Ai[1]); + + /* Now the right-hand side of the equation in the paper. */ + temp0 = Vmult_scalar(start_vector, B0(CURVE_T(curve, i))); + temp1 = Vmult_scalar(start_vector, B1(CURVE_T(curve, i))); + temp2 = Vmult_scalar(end_vector, B2(CURVE_T(curve, i))); + temp3 = Vmult_scalar(end_vector, B3(CURVE_T(curve, i))); + + temp = make_vector(Vsubtract_point(CURVE_POINT(curve, i), Vadd(temp0, Vadd(temp1, Vadd(temp2, temp3))))); + + X[0] += Vdot(temp, Ai[0]); + X[1] += Vdot(temp, Ai[1]); + } + free(A); + + C[1][0] = C[0][1]; + + X_C1_det = X[0] * C[1][1] - X[1] * C[0][1]; + C0_X_det = C[0][0] * X[1] - C[0][1] * X[0]; + C0_C1_det = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + if (C0_C1_det == 0.0) { + /* Zero determinant */ + alpha1 = 0; + alpha2 = 0; + } else { + alpha1 = X_C1_det / C0_C1_det; + alpha2 = C0_X_det / C0_C1_det; + } + CONTROL1(spline) = Vadd_point(START_POINT(spline), Vmult_scalar(t1_hat, alpha1)); + CONTROL2(spline) = Vadd_point(END_POINT(spline), Vmult_scalar(t2_hat, alpha2)); + SPLINE_DEGREE(spline) = CUBICTYPE; + + return spline; +} + +/* Find reasonable values for t for each point on CURVE. The method is + called chord-length parameterization, which is described in Plass & + Stone. The basic idea is just to use the distance from one point to + the next as the t value, normalized to produce values that increase + from zero for the first point to one for the last point. */ + +static void set_initial_parameter_values(curve_type curve) +{ + unsigned p; + + LOG("\nAssigning initial t values:\n "); + + CURVE_T(curve, 0) = 0.0; + + for (p = 1; p < CURVE_LENGTH(curve); p++) { + at_real_coord point = CURVE_POINT(curve, p), previous_p = CURVE_POINT(curve, p - 1); + gfloat d = distance(point, previous_p); + CURVE_T(curve, p) = CURVE_T(curve, p - 1) + d; + } + + if (LAST_CURVE_T(curve) == 0.0) + LAST_CURVE_T(curve) = 1.0; + + for (p = 1; p < CURVE_LENGTH(curve); p++) + CURVE_T(curve, p) = CURVE_T(curve, p) / LAST_CURVE_T(curve); + + if (logging) + log_entire_curve(curve); +} + +/* Find an approximation to the tangent to an endpoint of CURVE (to the + first point if TO_START_POINT is TRUE, else the last). If + CROSS_CURVE is TRUE, consider points on the adjacent curve to CURVE. + + It is important to compute an accurate approximation, because the + control points that we eventually decide upon to fit the curve will + be placed on the half-lines defined by the tangents and + endpoints...and we never recompute the tangent after this. */ + +static void find_tangent(curve_type curve, gboolean to_start_point, gboolean cross_curve, unsigned tangent_surround) +{ + vector_type tangent; + vector_type **curve_tangent = (to_start_point == TRUE) ? &(CURVE_START_TANGENT(curve)) + : &(CURVE_END_TANGENT(curve)); + unsigned n_points = 0; + + LOG(" tangent to %s: ", (to_start_point == TRUE) ? "start" : "end"); + + if (*curve_tangent == NULL) { + XMALLOC(*curve_tangent, sizeof(vector_type)); + do { + tangent = find_half_tangent(curve, to_start_point, &n_points, tangent_surround); + + if ((cross_curve == TRUE) || (CURVE_CYCLIC(curve) == TRUE)) { + curve_type adjacent_curve = (to_start_point == TRUE) ? PREVIOUS_CURVE(curve) : NEXT_CURVE(curve); + vector_type tangent2 = (to_start_point == FALSE) ? find_half_tangent(adjacent_curve, TRUE, &n_points, + tangent_surround) : find_half_tangent(adjacent_curve, TRUE, &n_points, + tangent_surround); + + LOG("(adjacent curve half tangent (%.3f,%.3f,%.3f)) ", tangent2.dx, tangent2.dy, tangent2.dz); + tangent = Vadd(tangent, tangent2); + } + tangent_surround--; + + } + while (tangent.dx == 0.0 && tangent.dy == 0.0); + + assert(n_points > 0); + **curve_tangent = Vmult_scalar(tangent, (gfloat) (1.0 / n_points)); + if ((CURVE_CYCLIC(curve) == TRUE) && CURVE_START_TANGENT(curve)) + *CURVE_START_TANGENT(curve) = **curve_tangent; + if ((CURVE_CYCLIC(curve) == TRUE) && CURVE_END_TANGENT(curve)) + *CURVE_END_TANGENT(curve) = **curve_tangent; + } else + LOG("(already computed) "); + + LOG("(%.3f,%.3f,%.3f).\n", (*curve_tangent)->dx, (*curve_tangent)->dy, (*curve_tangent)->dz); +} + +/* Find the change in y and change in x for `tangent_surround' (a global) + points along CURVE. Increment N_POINTS by the number of points we + actually look at. */ + +static vector_type find_half_tangent(curve_type c, gboolean to_start_point, unsigned *n_points, unsigned tangent_surround) +{ + unsigned p; + int factor = to_start_point ? 1 : -1; + unsigned tangent_index = to_start_point ? 0 : c->length - 1; + at_real_coord tangent_point = CURVE_POINT(c, tangent_index); + vector_type tangent = { 0.0, 0.0 }; + unsigned int surround; + + if ((surround = CURVE_LENGTH(c) / 2) > tangent_surround) + surround = tangent_surround; + + for (p = 1; p <= surround; p++) { + int this_index = p * factor + tangent_index; + at_real_coord this_point; + + if (this_index < 0 || this_index >= (int)c->length) + break; + + this_point = CURVE_POINT(c, p * factor + tangent_index); + + /* Perhaps we should weight the tangent from `this_point' by some + factor dependent on the distance from the tangent point. */ + tangent = Vadd(tangent, Vmult_scalar(Psubtract(this_point, tangent_point), (gfloat) factor)); + (*n_points)++; + } + + return tangent; +} + +/* When this routine is called, we have computed a spline representation + for the digitized curve. The question is, how good is it? If the + fit is very good indeed, we might have an error of zero on each + point, and then WORST_POINT becomes irrelevant. But normally, we + return the error at the worst point, and the index of that point in + WORST_POINT. The error computation itself is the Euclidean distance + from the original curve CURVE to the fitted spline SPLINE. */ + +static gfloat find_error(curve_type curve, spline_type spline, unsigned *worst_point, at_exception_type * exception) +{ + unsigned this_point; + gfloat total_error = 0.0; + gfloat worst_error = FLT_MIN; + + *worst_point = CURVE_LENGTH(curve) + 1; /* A sentinel value. */ + + for (this_point = 0; this_point < CURVE_LENGTH(curve); this_point++) { + at_real_coord curve_point = CURVE_POINT(curve, this_point); + gfloat t = CURVE_T(curve, this_point); + at_real_coord spline_point = evaluate_spline(spline, t); + gfloat this_error = distance(curve_point, spline_point); + if (this_error >= worst_error) { + *worst_point = this_point; + worst_error = this_error; + } + total_error += this_error; + } + + if (*worst_point == CURVE_LENGTH(curve) + 1) { /* Didn't have any ``worst point''; the error should be zero. */ + if (epsilon_equal(total_error, 0.0)) + LOG(" Every point fit perfectly.\n"); + else { + LOG("No worst point found; something is wrong"); + at_exception_warning(exception, "No worst point found; something is wrong"); + } + } else { + if (epsilon_equal(total_error, 0.0)) + LOG(" Every point fit perfectly.\n"); + else { + LOG(" Worst error (at (%.3f,%.3f,%.3f), point #%u) was %.3f.\n", CURVE_POINT(curve, *worst_point).x, CURVE_POINT(curve, *worst_point).y, CURVE_POINT(curve, *worst_point).z, *worst_point, worst_error); + LOG(" Total error was %.3f.\n", total_error); + LOG(" Average error (over %u points) was %.3f.\n", CURVE_LENGTH(curve), total_error / CURVE_LENGTH(curve)); + } + } + + return worst_error; +} + +/* Supposing that we have accepted the error, another question arises: + would we be better off just using a straight line? */ + +static gboolean spline_linear_enough(spline_type * spline, curve_type curve, fitting_opts_type * fitting_opts) +{ + gfloat A, B, C; + unsigned this_point; + gfloat dist = 0.0, start_end_dist, threshold; + + LOG("Checking linearity:\n"); + + A = END_POINT(*spline).x - START_POINT(*spline).x; + B = END_POINT(*spline).y - START_POINT(*spline).y; + C = END_POINT(*spline).z - START_POINT(*spline).z; + + start_end_dist = (gfloat) (SQUARE(A) + SQUARE(B) + SQUARE(C)); + LOG("start_end_distance is %.3f.\n", sqrt(start_end_dist)); + + LOG(" Line endpoints are (%.3f, %.3f, %.3f) and ", START_POINT(*spline).x, START_POINT(*spline).y, START_POINT(*spline).z); + LOG("(%.3f, %.3f, %.3f)\n", END_POINT(*spline).x, END_POINT(*spline).y, END_POINT(*spline).z); + + /* LOG (" Line is %.3fx + %.3fy + %.3f = 0.\n", A, B, C); */ + + for (this_point = 0; this_point < CURVE_LENGTH(curve); this_point++) { + gfloat a, b, c, w; + gfloat t = CURVE_T(curve, this_point); + at_real_coord spline_point = evaluate_spline(*spline, t); + + a = spline_point.x - START_POINT(*spline).x; + b = spline_point.y - START_POINT(*spline).y; + c = spline_point.z - START_POINT(*spline).z; + w = (A * a + B * b + C * c) / start_end_dist; + + dist += (gfloat) sqrt(SQUARE(a - A * w) + SQUARE(b - B * w) + SQUARE(c - C * w)); + } + LOG(" Total distance is %.3f, ", dist); + + dist /= (CURVE_LENGTH(curve) - 1); + LOG("which is %.3f normalized.\n", dist); + + /* We want reversion of short curves to splines to be more likely than + reversion of long curves, hence the second division by the curve + length, for use in `change_bad_lines'. */ + SPLINE_LINEARITY(*spline) = dist; + LOG(" Final linearity: %.3f.\n", SPLINE_LINEARITY(*spline)); + if (start_end_dist * (gfloat) 0.5 > fitting_opts->line_threshold) + threshold = fitting_opts->line_threshold; + else + threshold = start_end_dist * (gfloat) 0.5; + LOG("threshold is %.3f .\n", threshold); + if (dist < threshold) + return TRUE; + else + return FALSE; +} + +/* Unfortunately, we cannot tell in isolation whether a given spline + should be changed to a line or not. That can only be known after the + entire curve has been fit to a list of splines. (The curve is the + pixel outline between two corners.) After subdividing the curve, a + line may very well fit a portion of the curve just as well as the + spline---but unless a spline is truly close to being a line, it + should not be combined with other lines. */ + +static void change_bad_lines(spline_list_type * spline_list, fitting_opts_type * fitting_opts) +{ + unsigned this_spline; + gboolean found_cubic = FALSE; + unsigned length = SPLINE_LIST_LENGTH(*spline_list); + + LOG("\nChecking for bad lines (length %u):\n", length); + + /* First see if there are any splines in the fitted shape. */ + for (this_spline = 0; this_spline < length; this_spline++) { + if (SPLINE_DEGREE(SPLINE_LIST_ELT(*spline_list, this_spline)) == CUBICTYPE) { + found_cubic = TRUE; + break; + } + } + + /* If so, change lines back to splines (we haven't done anything to + their control points, so we only have to change the degree) unless + the spline is close enough to being a line. */ + if (found_cubic) + for (this_spline = 0; this_spline < length; this_spline++) { + spline_type s = SPLINE_LIST_ELT(*spline_list, this_spline); + + if (SPLINE_DEGREE(s) == LINEARTYPE) { + LOG(" #%u: ", this_spline); + if (SPLINE_LINEARITY(s) > fitting_opts->line_reversion_threshold) { + LOG("reverted, "); + SPLINE_DEGREE(SPLINE_LIST_ELT(*spline_list, this_spline)) + = CUBICTYPE; + } + LOG("linearity %.3f.\n", SPLINE_LINEARITY(s)); + } + } else + LOG(" No lines.\n"); +} + +/* Lists of array indices (well, that is what we use it for). */ + +static index_list_type new_index_list(void) +{ + index_list_type index_list; + + index_list.data = NULL; + INDEX_LIST_LENGTH(index_list) = 0; + + return index_list; +} + +static void free_index_list(index_list_type * index_list) +{ + if (INDEX_LIST_LENGTH(*index_list) > 0) { + free(index_list->data); + index_list->data = NULL; + INDEX_LIST_LENGTH(*index_list) = 0; + } +} + +static void append_index(index_list_type * list, unsigned new_index) +{ + INDEX_LIST_LENGTH(*list)++; + XREALLOC(list->data, INDEX_LIST_LENGTH(*list) * sizeof(unsigned)); + list->data[INDEX_LIST_LENGTH(*list) - 1] = new_index; +} + +/* Turn an real point into a integer one. */ + +static at_coord real_to_int_coord(at_real_coord real_coord) +{ + at_coord int_coord; + + int_coord.x = lround(real_coord.x); + int_coord.y = lround(real_coord.y); + + return int_coord; +} + +/* Return the Euclidean distance between P1 and P2. */ + +static gfloat distance(at_real_coord p1, at_real_coord p2) +{ + gfloat x = p1.x - p2.x, y = p1.y - p2.y, z = p1.z - p2.z; + return (gfloat) sqrt(SQUARE(x) + + SQUARE(y) + SQUARE(z)); +} |