summaryrefslogtreecommitdiffstats
path: root/plug-ins/selection-to-path/fit.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
commite42129241681dde7adae7d20697e7b421682fbb4 (patch)
treeaf1fe815a5e639e68e59fabd8395ec69458b3e5e /plug-ins/selection-to-path/fit.c
parentInitial commit. (diff)
downloadgimp-e42129241681dde7adae7d20697e7b421682fbb4.tar.xz
gimp-e42129241681dde7adae7d20697e7b421682fbb4.zip
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plug-ins/selection-to-path/fit.c')
-rw-r--r--plug-ins/selection-to-path/fit.c1967
1 files changed, 1967 insertions, 0 deletions
diff --git a/plug-ins/selection-to-path/fit.c b/plug-ins/selection-to-path/fit.c
new file mode 100644
index 0000000..8ee57ae
--- /dev/null
+++ b/plug-ins/selection-to-path/fit.c
@@ -0,0 +1,1967 @@
+/* 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.
+ *
+ * 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 3, 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <float.h>
+#include <math.h>
+#include <assert.h>
+
+#include <glib.h>
+
+#include "global.h"
+#include "spline.h"
+#include "vector.h"
+
+#include "curve.h"
+#include "fit.h"
+#include "pxl-outline.h"
+
+/* If two endpoints are closer than this, they are made to be equal.
+ (-align-threshold) */
+real align_threshold = 0.5;
+
+/* If the angle defined by a point and its predecessors and successors
+ is smaller than this, it's a corner, even if it's within
+ `corner_surround' pixels of a point with a smaller angle.
+ (-corner-always-threshold) */
+real corner_always_threshold = 60.0;
+
+/* Number of points to consider when determining if a point is a corner
+ or not. (-corner-surround) */
+unsigned corner_surround = 4;
+
+/* If a point, its predecessors, and its successors define an angle
+ smaller than this, it's a corner. Should be in range 0..180.
+ (-corner-threshold) */
+real corner_threshold = 100.0;
+
+/* Amount of error at which a fitted spline is unacceptable. If any
+ pixel is further away than this from the fitted curve, we try again.
+ (-error-threshold) */
+/* real error_threshold = .8; ALT */
+real error_threshold = .4;
+
+/* A second number of adjacent points to consider when filtering.
+ (-filter-alternative-surround) */
+unsigned filter_alternative_surround = 1;
+
+/* If the angles between the vectors produced by filter_surround and
+ filter_alternative_surround points differ by more than this, use
+ the one from filter_alternative_surround. (-filter-epsilon) */
+real filter_epsilon = 10.0;
+
+/* Number of times to smooth original data points. Increasing this
+ number dramatically---to 50 or so---can produce vastly better
+ results. But if any points that ``should'' be corners aren't found,
+ the curve goes to hell around that point. (-filter-iterations) */
+/* unsigned filter_iteration_count = 4; ALT */
+unsigned filter_iteration_count = 4;
+
+/* To produce the new point, use the old point plus this times the
+ neighbors. (-filter-percent) */
+real filter_percent = .33;
+
+/* Number of adjacent points to consider if `filter_surround' points
+ defines a straight line. (-filter-secondary-surround) */
+static unsigned filter_secondary_surround = 3;
+
+/* Number of adjacent points to consider when filtering.
+ (-filter-surround) */
+unsigned filter_surround = 2;
+
+/* Says whether or not to remove ``knee'' points after finding the outline.
+ (See the comments at `remove_knee_points'.) (-remove-knees). */
+boolean keep_knees = false;
+
+/* If a spline is closer to a straight line than this, it remains a
+ straight line, even if it would otherwise be changed back to a curve.
+ This is weighted by the square of the curve length, to make shorter
+ curves more likely to be reverted. (-line-reversion-threshold) */
+real line_reversion_threshold = .01;
+
+/* How many pixels (on the average) a spline can diverge from the line
+ determined by its endpoints before it is changed to a straight line.
+ (-line-threshold) */
+/* real line_threshold = 1.0; ALT */
+real line_threshold = 0.5;
+
+/* If reparameterization doesn't improve the fit by this much percent,
+ stop doing it. (-reparameterize-improve) */
+/* real reparameterize_improvement = .10; ALT */
+real reparameterize_improvement = .01;
+
+/* Amount of error at which it is pointless to reparameterize. This
+ happens, for example, when we are trying to fit the outline of the
+ outside of an `O' with a single spline. The initial fit is not good
+ enough for the Newton-Raphson iteration to improve it. It may be
+ that it would be better to detect the cases where we didn't find any
+ corners. (-reparameterize-threshold) */
+/* real reparameterize_threshold = 30.0; ALT */
+real reparameterize_threshold = 1.0;
+
+/* Percentage of the curve away from the worst point to look for a
+ better place to subdivide. (-subdivide-search) */
+real subdivide_search = .1;
+
+/* Number of points to consider when deciding whether a given point is a
+ better place to subdivide. (-subdivide-surround) */
+unsigned subdivide_surround = 4;
+
+/* How many pixels a point can diverge from a straight line and still be
+ considered a better place to subdivide. (-subdivide-threshold) */
+real subdivide_threshold = .03;
+
+/* Number of points to look at on either side of a point when computing
+ the approximation to the tangent at that point. (-tangent-surround) */
+unsigned tangent_surround = 3;
+
+
+/* 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);
+
+static void align (spline_list_type *);
+static void change_bad_lines (spline_list_type *);
+static void filter (curve_type);
+static real filter_angle (vector_type, vector_type);
+static void find_curve_vectors
+ (unsigned, curve_type, unsigned, vector_type *, vector_type *, unsigned *);
+static unsigned find_subdivision (curve_type, unsigned initial);
+static void find_vectors
+ (unsigned, pixel_outline_type, vector_type *, vector_type *);
+static index_list_type find_corners (pixel_outline_type);
+static real find_error (curve_type, spline_type, unsigned *);
+static vector_type find_half_tangent (curve_type, boolean start, unsigned *);
+static void find_tangent (curve_type, boolean, boolean);
+static spline_type fit_one_spline (curve_type);
+static spline_list_type *fit_curve (curve_type);
+static spline_list_type fit_curve_list (curve_list_type);
+static spline_list_type *fit_with_least_squares (curve_type);
+static spline_list_type *fit_with_line (curve_type);
+static void remove_knee_points (curve_type, boolean);
+static boolean reparameterize (curve_type, spline_type);
+static void set_initial_parameter_values (curve_type);
+static boolean spline_linear_enough (spline_type *, curve_type);
+static curve_list_array_type split_at_corners (pixel_outline_list_type);
+static boolean test_subdivision_point (curve_type, unsigned, vector_type *);
+
+/* 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)
+{
+ unsigned this_list;
+ unsigned total = 0;
+ spline_list_array_type char_splines = new_spline_list_array ();
+ curve_list_array_type curve_array = split_at_corners (pixel_outline_list);
+
+ 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);
+
+ curve_list_splines = fit_curve_list (curves);
+ append_spline_list (&char_splines, curve_list_splines);
+
+/* REPORT ("* "); */
+ }
+
+ free_curve_list_array (&curve_array);
+
+ for (this_list = 0; this_list < SPLINE_LIST_ARRAY_LENGTH (char_splines);
+ this_list++)
+ total
+ += SPLINE_LIST_LENGTH (SPLINE_LIST_ARRAY_ELT (char_splines, this_list));
+
+/* REPORT1 ("=%u", total); */
+
+ return char_splines;
+}
+
+/* Set up the internal parameters from the external ones */
+
+void
+fit_set_params(SELVALS *selVals)
+{
+ align_threshold = selVals->align_threshold;
+ corner_always_threshold = selVals->corner_always_threshold;
+ corner_surround = selVals->corner_surround;
+ corner_threshold = selVals->corner_threshold;
+ error_threshold = selVals->error_threshold;
+ filter_alternative_surround = selVals->filter_alternative_surround;
+ filter_epsilon = selVals->filter_epsilon;
+ filter_iteration_count = selVals->filter_iteration_count;
+ filter_percent = selVals->filter_percent;
+ filter_secondary_surround = selVals->filter_secondary_surround;
+ filter_surround = selVals->filter_surround;
+ keep_knees = selVals->keep_knees;
+ line_reversion_threshold = selVals->line_reversion_threshold;
+ line_threshold = selVals->line_threshold;
+ reparameterize_improvement = selVals->reparameterize_improvement;
+ reparameterize_threshold = selVals->reparameterize_threshold;
+ subdivide_search = selVals->subdivide_search;
+ subdivide_surround = selVals->subdivide_surround;
+ subdivide_threshold = selVals->subdivide_threshold;
+ tangent_surround = selVals->tangent_surround;
+}
+
+void
+fit_set_default_params(SELVALS *selVals)
+{
+ selVals->align_threshold = align_threshold;
+ selVals->corner_always_threshold = corner_always_threshold;
+ selVals->corner_surround = corner_surround;
+ selVals->corner_threshold = corner_threshold;
+ selVals->error_threshold = error_threshold;
+ selVals->filter_alternative_surround = filter_alternative_surround;
+ selVals->filter_epsilon = filter_epsilon;
+ selVals->filter_iteration_count = filter_iteration_count;
+ selVals->filter_percent = filter_percent;
+ selVals->filter_secondary_surround = filter_secondary_surround;
+ selVals->filter_surround = filter_surround;
+ selVals->keep_knees = keep_knees;
+ selVals->line_reversion_threshold = line_reversion_threshold;
+ selVals->line_threshold = line_threshold;
+ selVals->reparameterize_improvement = reparameterize_improvement;
+ selVals->reparameterize_threshold = reparameterize_threshold;
+ selVals->subdivide_search = subdivide_search;
+ selVals->subdivide_surround = subdivide_surround;
+ selVals->subdivide_threshold = subdivide_threshold;
+ selVals->tangent_surround = tangent_surround;
+}
+
+/* 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)
+{
+ curve_type curve;
+ unsigned this_curve, this_spline;
+ unsigned curve_list_length = CURVE_LIST_LENGTH (curve_list);
+ spline_list_type curve_list_splines = *new_spline_list ();
+
+ /* 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. */
+ if (!keep_knees)
+ {
+/* LOG ("\nRemoving knees:\n"); */
+ for (this_curve = 0; this_curve < curve_list_length; this_curve++)
+ {
+/* LOG1 ("#%u:", this_curve); */
+ remove_knee_points (CURVE_LIST_ELT (curve_list, this_curve),
+ CURVE_LIST_CLOCKWISE (curve_list));
+ }
+ }
+
+ /* 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++)
+ {
+/* LOG1 ("#%u: ", this_curve); */
+ filter (CURVE_LIST_ELT (curve_list, this_curve));
+/* REPORT ("f"); */
+ }
+
+ /* 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) && CURVE_LENGTH (curve) != 3)
+ 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);
+
+/* REPORT1 (" %u", this_curve); */
+/* LOG1 ("\nFitting curve #%u:\n", this_curve); */
+
+ curve_splines = fit_curve (current_curve);
+ if (curve_splines == NULL)
+ printf("Could not fit curve #%u", this_curve);
+ else
+ {
+/* LOG1 ("Fitted splines for curve #%u:\n", this_curve); */
+ for (this_spline = 0;
+ this_spline < SPLINE_LIST_LENGTH (*curve_splines);
+ this_spline++)
+ {
+/* LOG1 (" %u: ", this_spline); */
+/* if (logging) */
+/* print_spline (log_
+file, */
+/* 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);
+
+ concat_spline_lists (&curve_list_splines, *curve_splines);
+/* REPORT1 ("(%u)", SPLINE_LIST_LENGTH (*curve_splines)); */
+ }
+ }
+
+
+ /* We do this for each outline's spline list because when a point
+ is changed, it needs to be changed in both segments in which it
+ appears---and the segments might be in different curves. */
+ align (&curve_list_splines);
+
+ 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)
+{
+ spline_list_type *fitted_splines;
+
+ if (CURVE_LENGTH (curve) < 2)
+ {
+ printf ("Tried to fit curve with less than two points");
+ return NULL;
+ }
+
+ /* Do we have enough points to fit with a spline? */
+ fitted_splines = CURVE_LENGTH (curve) < 4
+ ? fit_with_line (curve)
+ : fit_with_least_squares (curve);
+
+ return fitted_splines;
+}
+
+/* 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)
+{
+ 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);
+
+/* LOG1 ("#%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) > corner_surround * 2 + 2)
+ {
+ corner_list = find_corners (pixel_o);
+ }
+ else
+ {
+ corner_list.data = NULL;
+ corner_list.length = 0;
+ }
+
+ /* 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));
+
+ /* This curve is cyclic. */
+ 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));
+
+ for (p = 0; p <= GET_INDEX (corner_list, 0); p++)
+ append_pixel (curve, O_COORDINATE (pixel_o, p));
+ }
+
+/* LOG1 (" [%u].\n", corner_list.length); */
+
+ /* 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); \
+ /*LOG4 (" (%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)
+{
+ unsigned p;
+ index_list_type corner_list = new_index_list ();
+
+ /* Consider each pixel on the outline in turn. */
+ for (p = 0; p < O_LENGTH (pixel_outline); p++)
+ {
+ real corner_angle;
+ vector_type in_vector, out_vector;
+
+ /* Check if the angle is small enough. */
+ find_vectors (p, pixel_outline, &in_vector, &out_vector);
+ corner_angle = Vangle (in_vector, out_vector);
+
+ if (fabs (corner_angle) <= 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. */
+ real 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 <= 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 + 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);
+ corner_angle = Vangle (in_vector, out_vector);
+
+ /* 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 > corner_always_threshold
+ && best_corner_index >= p)
+ {
+ unsigned i;
+
+ APPEND_CORNER (best_corner_index, best_corner_angle, '/');
+
+ for (i = 0; i < INDEX_LIST_LENGTH (equally_good_list); i++)
+ APPEND_CORNER (GET_INDEX (equally_good_list, i),
+ 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) - 1);
+
+ 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)
+{
+ int i;
+ unsigned n_done;
+ coordinate_type candidate = O_COORDINATE (outline, test_index);
+
+ in->dx = 0.0;
+ in->dy = 0.0;
+ out->dx = 0.0;
+ out->dy = 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));
+
+#if 0
+ /* We don't need this code any more, because now we create the pixel
+ outline from the corners of the pixels, rather than the edges. */
+
+ /* To see why we need this test, consider the following
+ case: four pixels stacked vertically with no other adjacent pixels,
+ i.e., *
+ *x
+ *
+ *
+ *** (etc.) We are considering the pixel marked `x' for cornerhood.
+ The out vector at this point is going to be the zero vector (if
+ `corner_surround' is 3), because the first
+ pixel on the outline is the one above the x, the second pixel x
+ itself, and the third the one below x. (Remember that we go
+ around the edges of the pixels to find the outlines, not the
+ pixels themselves.) */
+ if (magnitude (*in) == 0.0)
+ {
+ WARNING ("Zero magnitude in");
+ return corner_threshold + 1.0;
+ }
+#endif /* 0 */
+
+ /* 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));
+
+#if 0
+ /* As with the test for the in vector, we don't need this any more. */
+ if (magnitude (*out) == 0.0)
+ {
+ WARNING ("Zero magnitude out");
+ return corner_threshold + 1.0;
+ }
+#endif /* 0 */
+}
+
+
+/* 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)
+{
+ unsigned j;
+ unsigned last;
+ index_list_type new = 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;
+ printf ("needed exchange"); /* xx -- really have to sort? */
+ }
+ }
+
+ /* 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);
+
+ append_index (&new, current);
+ if (next == current + 1)
+ j++;
+ }
+
+ /* 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) == 0
+ || !(last == GET_LAST_INDEX (new) + 1
+ || (last == last_index && GET_INDEX (*list, 0) == 0)))
+ append_index (&new, last);
+
+ free_index_list (list);
+ *list = new;
+}
+
+/* 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, boolean clockwise)
+{
+ int i;
+ unsigned offset = CURVE_CYCLIC (curve) ? 0 : 1;
+ coordinate_type 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))
+ append_pixel (trimmed_curve, real_to_int_coord (CURVE_POINT (curve, 0)));
+
+ for (i = offset; i < CURVE_LENGTH (curve) - offset; i++)
+ {
+ coordinate_type current
+ = real_to_int_coord (CURVE_POINT (curve, i));
+ coordinate_type 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))))
+ {
+ /* LOG2 (" (%d,%d)", current.x, current.y); */
+ }
+ else
+ {
+ previous = current;
+ append_pixel (trimmed_curve, current);
+ }
+ }
+
+ if (!CURVE_CYCLIC (curve))
+ 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;
+}
+
+/* Smooth the curve by adding in neighboring points. Do this
+ `filter_iteration_count' times. But don't change the corners. */
+
+#if 0
+/* Computing the new point based on a single neighboring point and with
+ constant percentages, as the `SMOOTH' macro did, isn't quite good
+ enough. For example, at the top of an `o' the curve might well have
+ three consecutive horizontal pixels, even though there isn't really a
+ straight there. With this code, the middle point would remain
+ unfiltered. */
+
+#define SMOOTH(axis) \
+ CURVE_POINT (curve, this_point).axis \
+ = ((1.0 - center_percent) / 2) \
+ * CURVE_POINT (curve, CURVE_PREV (curve, this_point)).axis \
+ + center_percent * CURVE_POINT (curve, this_point).axis \
+ + ((1.0 - center_percent) / 2) \
+ * CURVE_POINT (curve, CURVE_NEXT (curve, this_point)).axis
+#endif /* 0 */
+
+/* We sometimes need to change the information about the filtered point.
+ This macro assigns to the relevant variables. */
+#define FILTER_ASSIGN(new) \
+ do \
+ { \
+ in = in_##new; \
+ out = out_##new; \
+ count = new##_count; \
+ angle = angle_##new; \
+ } \
+ while (0)
+
+static void
+filter (curve_type curve)
+{
+ unsigned iteration, this_point;
+ unsigned offset = CURVE_CYCLIC (curve) ? 0 : 1;
+
+ /* 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)
+ {
+/* LOG1 ("Length is %u, not enough to filter.\n", CURVE_LENGTH (curve)); */
+ return;
+ }
+
+ for (iteration = 0; iteration < filter_iteration_count; iteration++)
+ {
+ curve_type new_curve = copy_most_of_curve (curve);
+
+ /* Keep the first point on the curve. */
+ if (offset)
+ append_point (new_curve, CURVE_POINT (curve, 0));
+
+ for (this_point = offset; this_point < CURVE_LENGTH (curve) - offset;
+ this_point++)
+ {
+ real angle, angle_alt;
+ vector_type in, in_alt, out, out_alt, sum;
+ unsigned count, alt_count;
+ real_coordinate_type new_point;
+
+ /* Find the angle using the usual number of surrounding points
+ on the curve. */
+ find_curve_vectors (this_point, curve, filter_surround,
+ &in, &out, &count);
+ angle = filter_angle (in, out);
+
+ /* Find the angle using the alternative (presumably smaller)
+ number. */
+ find_curve_vectors (this_point, curve, filter_alternative_surround,
+ &in_alt, &out_alt, &alt_count);
+ angle_alt = filter_angle (in_alt, out_alt);
+
+ /* If the alternate angle is enough larger than the usual one
+ and neither of the components of the sum are zero, use it.
+ (We don't use absolute value here, since if the alternate
+ angle is smaller, we don't particularly care, since that
+ means the curve is pretty flat around the current point,
+ anyway.) This helps keep small features from disappearing
+ into the surrounding curve. */
+ sum = Vadd (in_alt, out_alt);
+ if (angle_alt - angle >= filter_epsilon
+ && sum.dx != 0 && sum.dy != 0)
+ FILTER_ASSIGN (alt);
+
+#if 0
+/* This code isn't needed anymore, since we do the filtering in a
+ somewhat more general way. */
+ /* If we're left with an angle of zero, don't stop yet; we
+ might be at a straight which really isn't one (as in the `o'
+ discussed above). */
+ if (epsilon_equal (angle, 0.0))
+ {
+ real angle_secondary;
+ vector_type in_secondary, out_secondary;
+ unsigned in_secondary_count, out_secondary_count;
+
+ find_curve_vectors (this_point, curve, filter_secondary_surround,
+ &in_secondary, &out_secondary,
+ &in_secondary_count, &out_secondary_count);
+ angle_secondary = filter_angle (in_secondary, out_secondary);
+ if (!epsilon_equal (angle_secondary, 0.0))
+ FILTER_ASSIGN (secondary);
+ }
+#endif /* 0 */
+
+ /* Start with the old point. */
+ new_point = CURVE_POINT (curve, this_point);
+ sum = Vadd (in, out);
+ new_point.x += sum.dx * filter_percent / count;
+ new_point.y += sum.dy * filter_percent / count;
+
+ /* Put the newly computed point into a separate curve, so it
+ doesn't affect future computation (on this iteration). */
+ append_point (new_curve, new_point);
+ }
+
+ /* Just as with the first point, we have to keep the last point. */
+ if (offset)
+ append_point (new_curve, LAST_CURVE_POINT (curve));
+
+ /* Set the original curve to the newly filtered one, and go again. */
+ free_curve (curve);
+ *curve = *new_curve;
+ }
+
+/* log_curve (curve, false); */
+/* display_curve (curve); */
+}
+
+
+/* Return the vectors IN and OUT, computed by looking at SURROUND points
+ on either side of TEST_INDEX. Also return the number of points in
+ the vectors in COUNT (we make sure they are the same). */
+
+static void
+find_curve_vectors (unsigned test_index, curve_type curve,
+ unsigned surround,
+ vector_type *in, vector_type *out, unsigned *count)
+{
+ int i;
+ unsigned in_count, out_count;
+ unsigned n_done;
+ real_coordinate_type candidate = CURVE_POINT (curve, test_index);
+
+ /* Add up the differences from p of the `surround' points
+ before p. */
+
+ in->dx = 0.0;
+ in->dy = 0.0;
+
+ for (i = CURVE_PREV (curve, test_index), n_done = 0;
+ i >= 0 && n_done < surround; /* Do not wrap around. */
+ i = CURVE_PREV (curve, i), n_done++)
+ *in = Vadd (*in, Psubtract (CURVE_POINT (curve, i), candidate));
+ in_count = n_done;
+
+ /* And the points after p. Don't use more points after p than we
+ ended up with before it. */
+ out->dx = 0.0;
+ out->dy = 0.0;
+
+ for (i = CURVE_NEXT (curve, test_index), n_done = 0;
+ i < CURVE_LENGTH (curve) && n_done < surround && n_done < in_count;
+ i = CURVE_NEXT (curve, i), n_done++)
+ *out = Vadd (*out, Psubtract (CURVE_POINT (curve, i), candidate));
+ out_count = n_done;
+
+ /* If we used more points before p than after p, we have to go back
+ and redo it. (We could just subtract the ones that were missing,
+ but for this few of points, efficiency doesn't matter.) */
+ if (out_count < in_count)
+ {
+ in->dx = 0.0;
+ in->dy = 0.0;
+
+ for (i = CURVE_PREV (curve, test_index), n_done = 0;
+ i >= 0 && n_done < out_count;
+ i = CURVE_PREV (curve, i), n_done++)
+ *in = Vadd (*in, Psubtract (CURVE_POINT (curve, i), candidate));
+ in_count = n_done;
+ }
+
+ assert (in_count == out_count);
+ *count = in_count;
+}
+
+
+/* Find the angle between the vectors IN and OUT, and bring it into the
+ range [0,45]. */
+
+static real
+filter_angle (vector_type in, vector_type out)
+{
+ real angle = Vangle (in, out);
+
+ /* What we want to do between 90 and 180 is the same as what we
+ want to do between 0 and 90. */
+ angle = fmod (angle, 1990.0);
+
+ /* And what we want to do between 45 and 90 is the same as
+ between 0 and 45, only reversed. */
+ if (angle > 45.0) angle = 90.0 - angle;
+
+ return angle;
+}
+
+/* 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 = new_spline ();
+
+/* LOG ("Fitting with straight line:\n"); */
+/* REPORT ("l"); */
+
+ SPLINE_DEGREE (line) = LINEAR;
+ 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 (log_file, line); */
+/* } */
+
+ return init_spline_list (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 try reparameterizing, i.e., finding new, and supposedly better,
+ t values. If that still fails, we subdivide the curve. */
+
+static spline_list_type *
+fit_with_least_squares (curve_type curve)
+{
+ real error, best_error = FLT_MAX;
+ spline_type spline, best_spline;
+ spline_list_type *spline_list;
+ unsigned worst_point;
+ unsigned iteration = 0;
+ real previous_error = FLT_MAX;
+ real improvement = FLT_MAX;
+
+ /* FIXME: Initialize best_spline to zeroes. This is strictly not
+ necessary as best_spline is always set in the loop below. But the
+ compiler thinks it isn't and warns. Ideally, the code should be
+ rewritten such that best_spline and best_error are initialized with
+ the first values before the loop begins. */
+ memset (&best_spline, 0, sizeof best_spline);
+
+/* 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);
+ find_tangent (curve, /* to_start */ false, /* cross_curve */ false);
+
+ set_initial_parameter_values (curve);
+
+ /* Now we loop, reparameterizing and/or subdividing, until CURVE has
+ been fit. */
+ while (true)
+ {
+/* LOG (" fitted to spline:\n"); */
+
+ spline = fit_one_spline (curve);
+
+/* if (logging) */
+/* { */
+/* LOG (" "); */
+/* print_spline (log_file, spline); */
+/* } */
+
+ error = find_error (curve, spline, &worst_point);
+ if (error > previous_error)
+ {
+/* LOG ("Reparameterization made it worse.\n"); */
+ /* Just fall through; we will subdivide. */
+ }
+ else
+ {
+ best_error = error;
+ best_spline = spline;
+ }
+
+ improvement = 1.0 - error / previous_error;
+
+ /* Don't exit, even if the error is less than `error_threshold',
+ since we might be able to improve the fit with further
+ reparameterization. If the reparameterization made it worse,
+ we will exit here, since `improvement' will be negative. */
+ if (improvement < reparameterize_improvement
+ || error > reparameterize_threshold)
+ break;
+
+ iteration++;
+/* LOG3 ("Reparameterization #%u (error was %.3f, a %u%% improvement):\n", */
+/* iteration, error, ((unsigned) (improvement * 100.0))); */
+
+ /* The reparameterization might fail, if the initial fit was
+ better than `reparameterize_threshold', yet worse than the
+ Newton-Raphson algorithm could handle. */
+ if (!reparameterize (curve, spline))
+ break;
+
+ previous_error = error;
+ }
+
+ /* Go back to the best fit. */
+ spline = best_spline;
+ error = best_error;
+
+ if (error < error_threshold)
+ {
+ /* The points were fitted with a (possibly reparameterized)
+ 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))
+ {
+ SPLINE_DEGREE (spline) = LINEAR;
+/* LOG ("Changed to line.\n"); */
+ }
+ spline_list = init_spline_list (spline);
+/* LOG1 ("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;
+
+/* REPORT ("s"); */
+/* LOG1 ("\nSubdividing (error %.3f):\n", error); */
+/* LOG3 (" Original point: (%.3f,%.3f), #%u.\n", */
+/* CURVE_POINT (curve, worst_point).x, */
+/* CURVE_POINT (curve, worst_point).y, worst_point); */
+ subdivision_index = find_subdivision (curve, worst_point);
+/* LOG3 (" Final point: (%.3f,%.3f), #%u.\n", */
+/* CURVE_POINT (curve, subdivision_index).x, */
+/* CURVE_POINT (curve, subdivision_index).y, subdivision_index); */
+/* display_subdivision (CURVE_POINT (curve, 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);
+ 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);
+ right_spline_list = fit_curve (right_curve);
+
+ /* 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)
+ {
+ WARNING ("could not fit left spline list");
+/* LOG1 ("Could not fit spline to left curve (%x).\n", */
+/* (unsigned) left_curve); */
+ }
+ else
+ concat_spline_lists (spline_list, *left_spline_list);
+
+ if (right_spline_list == NULL)
+ {
+ WARNING ("could not fit right spline list");
+/* LOG1 ("Could not fit spline to right curve (%x).\n", */
+/* (unsigned) right_curve); */
+ }
+ else
+ concat_spline_lists (spline_list, *right_spline_list);
+ }
+
+ 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 (1 - (t))
+#define B1(t) (3.0 * (t) * SQUARE (1 - (t)))
+#define B2(t) (3.0 * SQUARE (t) * (1 - (t)))
+#define B3(t) CUBE (t)
+
+#define U(i) CURVE_T (curve, i)
+
+static spline_type
+fit_one_spline (curve_type curve)
+{
+ /* Since our arrays are zero-based, the `C0' and `C1' here correspond
+ to `C1' and `C2' in the paper. */
+ real X_C1_det, C0_X_det, C0_C1_det;
+ real alpha1, alpha2;
+ spline_type spline;
+ vector_type start_vector, end_vector;
+ unsigned i;
+ vector_type t1_hat = *CURVE_START_TANGENT (curve);
+ vector_type t2_hat = *CURVE_END_TANGENT (curve);
+ real C[2][2] = { { 0.0, 0.0 }, { 0.0, 0.0 } };
+ real X[2] = { 0.0, 0.0 };
+ int Alen = CURVE_LENGTH (curve);
+ vector_type *A;
+
+ A = g_new0 (vector_type, Alen * 2);
+
+ START_POINT (spline) = CURVE_POINT (curve, 0);
+ END_POINT (spline) = LAST_CURVE_POINT (curve);
+ SPLINE_LINEARITY (spline) = 0;
+ start_vector = make_vector (START_POINT (spline));
+ end_vector = make_vector (END_POINT (spline));
+
+ for (i = 0; i < CURVE_LENGTH (curve); i++)
+ {
+ A[i*2+0] = Vmult_scalar (t1_hat, B1 (U (i)));
+ A[i*2+1] = Vmult_scalar (t2_hat, B2 (U (i)));
+ }
+
+ for (i = 0; i < CURVE_LENGTH (curve); i++)
+ {
+ vector_type temp, temp0, temp1, temp2, temp3;
+ vector_type *Ai = &A[i*2];
+
+ 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 (U (i)));
+ temp1 = Vmult_scalar (start_vector, B1 (U (i)));
+ temp2 = Vmult_scalar (end_vector, B2 (U (i)));
+ temp3 = Vmult_scalar (end_vector, B3 (U (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]);
+ }
+
+ 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)
+ FATAL ("zero determinant of C0*C1");
+
+ 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) = CUBIC;
+
+ g_free (A);
+
+ return spline;
+}
+
+/* Find closer-to-optimal t values for the given x,y's and control
+ points, using Newton-Raphson iteration. A good description of this
+ is in Plass & Stone. This routine performs one step in the
+ iteration, not the whole thing. */
+
+static boolean
+reparameterize (curve_type curve, spline_type S)
+{
+ unsigned p, i;
+ spline_type S1, S2; /* S' and S''. */
+
+/* REPORT ("r"); */
+
+ /* Find the first and second derivatives of S. To make
+ `evaluate_spline' work, we fill the beginning points (i.e., the first
+ two for a linear spline and the first three for a quadratic one),
+ even though this is at odds with the rest of the program. */
+ for (i = 0; i < 3; i++)
+ {
+ S1.v[i].x = 3.0 * (S.v[i + 1].x - S.v[i].x);
+ S1.v[i].y = 3.0 * (S.v[i + 1].y - S.v[i].y);
+ }
+ S1.v[i].x = S1.v[i].y = -1.0; /* These will never be accessed. */
+ SPLINE_DEGREE (S1) = QUADRATIC;
+
+ for (i = 0; i < 2; i++)
+ {
+ S2.v[i].x = 2.0 * (S1.v[i + 1].x - S1.v[i].x);
+ S2.v[i].y = 2.0 * (S1.v[i + 1].y - S1.v[i].y);
+ }
+ S2.v[2].x = S2.v[2].y = S2.v[3].x = S2.v[3].y = -1.0;
+ SPLINE_DEGREE (S2) = LINEAR;
+
+ for (p = 0; p < CURVE_LENGTH (curve); p++)
+ {
+ real new_distance, old_distance;
+ real_coordinate_type new_point;
+ real_coordinate_type point = CURVE_POINT (curve, p);
+ real t = CURVE_T (curve, p);
+
+ /* Find the points at this t on S, S', and S''. */
+ real_coordinate_type S_t = evaluate_spline (S, t);
+ real_coordinate_type S1_t = evaluate_spline (S1, t);
+ real_coordinate_type S2_t = evaluate_spline (S2, t);
+
+ /* The differences in x and y (Q1(t) on p.62 of Schneider's thesis). */
+ real_coordinate_type d;
+ real numerator;
+ real denominator;
+
+ d.x = S_t.x - point.x;
+ d.y = S_t.y - point.y;
+
+ /* The step size, f(t)/f'(t). */
+ numerator = d.x * S1_t.x + d.y * S1_t.y;
+ denominator = (SQUARE (S1_t.x) + d.x * S2_t.x
+ + SQUARE (S1_t.y) + d.y * S2_t.y);
+
+ /* We compute the distances only to be able to check that we
+ really are moving closer. I don't know how this condition can
+ be reliably checked for in advance, but I know what it means in
+ practice: the original fit was not good enough for the
+ Newton-Raphson iteration to converge. Therefore, we need to
+ abort the reparameterization, and subdivide. */
+ old_distance = distance (S_t, point);
+ CURVE_T (curve, p) -= numerator / denominator;
+ new_point = evaluate_spline (S, CURVE_T (curve, p));
+ new_distance = distance (new_point, point);
+
+ if (new_distance > old_distance)
+ {
+/* REPORT ("!"); */
+/* LOG3 (" Stopped reparameterizing; %.3f > %.3f at point %u.\n", */
+/* new_distance, old_distance, p); */
+ return false;
+ }
+
+ /* The t value might be negative or > 1, if the choice of control
+ points wasn't so great. We have no difficulty in evaluating
+ a spline at a t outside the range zero to one, though, so it
+ doesn't matter. (Although it is a little unconventional.) */
+ }
+/* LOG (" reparameterized curve:\n "); */
+/* log_curve (curve, true); */
+
+ return true;
+}
+
+/* This routine finds the best place to subdivide the curve CURVE,
+ somewhere near to the point whose index is INITIAL. Originally,
+ curves were always subdivided at the point of worst error, which is
+ intuitively appealing, but doesn't always give the best results. For
+ example, at the end of a serif that tapers into the stem, the best
+ subdivision point is at the point where they join, even if the worst
+ point is a little ways into the serif.
+
+ We return the index of the point at which to subdivide. */
+
+static unsigned
+find_subdivision (curve_type curve, unsigned initial)
+{
+ unsigned i, n_done;
+ int best_point = -1;
+ vector_type best = { FLT_MAX, FLT_MAX };
+ unsigned search = subdivide_search * CURVE_LENGTH (curve);
+
+/* LOG1 (" Number of points to search: %u.\n", search); */
+
+ /* We don't want to find the first (or last) point in the curve. */
+ for (i = initial, n_done = 0; i > 0 && n_done < search;
+ i = CURVE_PREV (curve, i), n_done++)
+ {
+ if (test_subdivision_point (curve, i, &best))
+ {
+ best_point = i;
+/* LOG3 (" Better point: (%.3f,%.3f), #%u.\n", */
+/* CURVE_POINT (curve, i).x, CURVE_POINT (curve, i).y, i); */
+ }
+ }
+
+ /* If we found a good one, let's take it. */
+ if (best_point != -1)
+ return best_point;
+
+ for (i = CURVE_NEXT (curve, initial), n_done = 0;
+ i < CURVE_LENGTH (curve) - 1 && n_done < search;
+ i = CURVE_NEXT (curve, i), n_done++)
+ {
+ if (test_subdivision_point (curve, i, &best))
+ {
+ best_point = i;
+/* LOG3 (" Better point at (%.3f,%.3f), #%u.\n", */
+/* CURVE_POINT (curve, i).x, CURVE_POINT (curve, i).y, i); */
+ }
+ }
+
+ /* If we didn't find any better point, return the original. */
+ return best_point == -1 ? initial : best_point;
+}
+
+
+/* Here are some macros that decide whether or not we're at a
+ ``join point'', as described above. */
+#define ONLY_ONE_LESS(v) \
+ (((v).dx < subdivide_threshold && (v).dy > subdivide_threshold) \
+ || ((v).dy < subdivide_threshold && (v).dx > subdivide_threshold))
+
+#define BOTH_GREATER(v) \
+ ((v).dx > subdivide_threshold && (v).dy > subdivide_threshold)
+
+/* We assume that the vectors V1 and V2 are nonnegative. */
+#define JOIN(v1, v2) \
+ ((ONLY_ONE_LESS (v1) && BOTH_GREATER (v2)) \
+ || (ONLY_ONE_LESS (v2) && BOTH_GREATER (v1)))
+
+/* If the component D of the vector V is smaller than the best so far,
+ update the best point. */
+#define UPDATE_BEST(v, d) \
+ do \
+ { \
+ if ((v).d < subdivide_threshold && (v).d < best->d) \
+ best->d = (v).d; \
+ } \
+ while (0)
+
+
+/* If the point INDEX in the curve CURVE is the best subdivision point
+ we've found so far, update the vector BEST. */
+
+static boolean
+test_subdivision_point (curve_type curve, unsigned index, vector_type *best)
+{
+ unsigned count;
+ vector_type in, out;
+ boolean join = false;
+
+ find_curve_vectors (index, curve, subdivide_surround, &in, &out, &count);
+
+ /* We don't want to subdivide at points which are very close to the
+ endpoints, so if the vectors aren't computed from as many points as
+ we asked for, don't bother checking this point. */
+ if (count == subdivide_surround)
+ {
+ in = Vabs (in);
+ out = Vabs (out);
+
+ join = JOIN (in, out);
+ if (join)
+ {
+ UPDATE_BEST (in, dx);
+ UPDATE_BEST (in, dy);
+ UPDATE_BEST (out, dx);
+ UPDATE_BEST (out, dy);
+ }
+ }
+
+ return join;
+}
+
+/* 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++)
+ {
+ real_coordinate_type point = CURVE_POINT (curve, p),
+ previous_p = CURVE_POINT (curve, p - 1);
+ real d = distance (point, previous_p);
+ CURVE_T (curve, p) = CURVE_T (curve, p - 1) + d;
+ }
+
+ assert (LAST_CURVE_T (curve) != 0.0);
+
+ for (p = 1; p < CURVE_LENGTH (curve); p++)
+ CURVE_T (curve, p) = CURVE_T (curve, p) / LAST_CURVE_T (curve);
+
+/* 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, boolean to_start_point, boolean cross_curve)
+{
+ vector_type tangent;
+ vector_type **curve_tangent = to_start_point ? &(CURVE_START_TANGENT (curve))
+ : &(CURVE_END_TANGENT (curve));
+ unsigned n_points = 0;
+
+/* LOG1 (" tangent to %s: ", to_start_point ? "start" : "end"); */
+
+ if (*curve_tangent == NULL)
+ {
+ *curve_tangent = g_new (vector_type, 1);
+ tangent = find_half_tangent (curve, to_start_point, &n_points);
+
+ if (cross_curve)
+ {
+ curve_type adjacent_curve
+ = to_start_point ? PREVIOUS_CURVE (curve) : NEXT_CURVE (curve);
+ vector_type tangent2
+ = find_half_tangent (adjacent_curve, !to_start_point, &n_points);
+
+/* LOG2 ("(adjacent curve half tangent (%.3f,%.3f)) ", */
+/* tangent2.dx, tangent2.dy); */
+ tangent = Vadd (tangent, tangent2);
+ }
+
+ assert (n_points > 0);
+ **curve_tangent = Vmult_scalar (tangent, 1.0 / n_points);
+ }
+ else
+ {
+/* LOG ("(already computed) "); */
+ }
+
+/* LOG2 ("(%.3f,%.3f).\n", (*curve_tangent)->dx, (*curve_tangent)->dy); */
+}
+
+
+/* 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, boolean to_start_point, unsigned *n_points)
+{
+ unsigned p;
+ int factor = to_start_point ? 1 : -1;
+ unsigned tangent_index = to_start_point ? 0 : c->length - 1;
+ real_coordinate_type tangent_point = CURVE_POINT (c, tangent_index);
+ vector_type tangent;
+
+ tangent.dx = 0.0;
+ tangent.dy = 0.0;
+
+ for (p = 1; p <= tangent_surround; p++)
+ {
+ int this_index = p * factor + tangent_index;
+ real_coordinate_type this_point;
+
+ if (this_index < 0 || this_index >= 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),
+ 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 real
+find_error (curve_type curve, spline_type spline, unsigned *worst_point)
+{
+ unsigned this_point;
+ real total_error = 0.0;
+ real worst_error = FLT_MIN;
+
+ *worst_point = CURVE_LENGTH (curve) + 1; /* A sentinel value. */
+
+ for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+ {
+ real_coordinate_type curve_point = CURVE_POINT (curve, this_point);
+ real t = CURVE_T (curve, this_point);
+ real_coordinate_type spline_point = evaluate_spline (spline, t);
+ real 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
+ printf ("No worst point found; something is wrong");
+ }
+ else
+ {
+/* LOG4 (" Worst error (at (%.3f,%.3f), point #%u) was %.3f.\n", */
+/* CURVE_POINT (curve, *worst_point).x, */
+/* CURVE_POINT (curve, *worst_point).y, *worst_point, worst_error); */
+/* LOG1 (" Total error was %.3f.\n", total_error); */
+/* LOG2 (" 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 boolean
+spline_linear_enough (spline_type *spline, curve_type curve)
+{
+ real A, B, C, slope;
+ unsigned this_point;
+ real distance = 0.0;
+
+/* LOG ("Checking linearity:\n"); */
+
+ /* For a line described by Ax + By + C = 0, the distance d from a
+ point (x0,y0) to that line is:
+
+ d = | Ax0 + By0 + C | / sqrt (A^2 + B^2).
+
+ We can find A, B, and C from the starting and ending points,
+ unless the line defined by those two points is vertical. */
+
+ if (epsilon_equal (START_POINT (*spline).x, END_POINT (*spline).x))
+ {
+ A = 1;
+ B = 0;
+ C = -START_POINT (*spline).x;
+ }
+ else
+ {
+ /* Plug the spline's starting and ending points into the two-point
+ equation for a line, that is,
+
+ (y - y1) = ((y2 - y1)/(x2 - x1)) * (x - x1)
+
+ to get the values for A, B, and C. */
+
+ slope = ((END_POINT (*spline).y - START_POINT (*spline).y)
+ / (END_POINT (*spline).x - START_POINT (*spline).x));
+ A = -slope;
+ B = 1;
+ C = slope * START_POINT (*spline).x - START_POINT (*spline).y;
+ }
+/* LOG3 (" Line is %.3fx + %.3fy + %.3f = 0.\n", A, B, C); */
+
+ for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++)
+ {
+ real t = CURVE_T (curve, this_point);
+ real_coordinate_type spline_point = evaluate_spline (*spline, t);
+
+ distance += fabs (A * spline_point.x + B * spline_point.y + C)
+ / sqrt (A * A + B * B);
+ }
+/* LOG1 (" Total distance is %.3f, ", distance); */
+
+ distance /= CURVE_LENGTH (curve);
+/* LOG1 ("which is %.3f normalized.\n", distance); */
+
+ /* 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) = distance / CURVE_LENGTH (curve);
+/* LOG1 (" Final linearity: %.3f.\n", SPLINE_LINEARITY (*spline)); */
+
+ return distance < line_threshold;
+}
+
+
+/* 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)
+{
+ unsigned this_spline;
+ boolean found_cubic = false;
+ unsigned length = SPLINE_LIST_LENGTH (*spline_list);
+
+/* LOG1 ("\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)) == CUBIC)
+ {
+ 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) == LINEAR)
+ {
+/* LOG1 (" #%u: ", this_spline); */
+ if (SPLINE_LINEARITY (s) > line_reversion_threshold)
+ {
+/* LOG ("reverted, "); */
+ SPLINE_DEGREE (SPLINE_LIST_ELT (*spline_list, this_spline))
+ = CUBIC;
+ }
+/* LOG1 ("linearity %.3f.\n", SPLINE_LINEARITY (s)); */
+ }
+ }
+ else
+ {
+/* LOG (" No lines.\n"); */
+ }
+}
+
+/* When we have finished fitting an entire pixel outline to a spline
+ list L, we go through L to ensure that any endpoints that are ``close
+ enough'' (i.e., within `align_threshold') to being the same really
+ are the same. */
+
+/* This macro adjusts the AXIS axis on the starting and ending points on
+ a particular spline if necessary. */
+#define TRY_AXIS(axis) \
+ do \
+ { \
+ real delta = fabs (end.axis - start.axis); \
+ \
+ if (!epsilon_equal (delta, 0.0) && delta <= align_threshold) \
+ { \
+ spline_type *next = &NEXT_SPLINE_LIST_ELT (*l, this_spline); \
+ spline_type *prev = &PREV_SPLINE_LIST_ELT (*l, this_spline); \
+ \
+ START_POINT (*s).axis = END_POINT (*s).axis \
+ = END_POINT (*prev).axis = START_POINT (*next).axis \
+ = (start.axis + end.axis) / 2; \
+ spline_change = true; \
+ } \
+ } \
+ while (0)
+
+static void
+align (spline_list_type *l)
+{
+ boolean change;
+ unsigned this_spline;
+ unsigned length = SPLINE_LIST_LENGTH (*l);
+
+/* LOG1 ("\nAligning spline list (length %u):\n", length); */
+
+ do
+ {
+ change = false;
+
+/* LOG (" "); */
+
+ for (this_spline = 0; this_spline < length; this_spline++)
+ {
+ boolean spline_change = false;
+ spline_type *s = &SPLINE_LIST_ELT (*l, this_spline);
+ real_coordinate_type start = START_POINT (*s);
+ real_coordinate_type end = END_POINT (*s);
+
+ TRY_AXIS (x);
+ TRY_AXIS (y);
+ if (spline_change)
+ {
+/* LOG1 ("%u ", this_spline); */
+ change |= spline_change;
+ }
+ }
+/* LOG ("\n"); */
+ }
+ while (change);
+}
+
+/* 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)
+ {
+ g_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)++;
+ list->data = (unsigned *)g_realloc(list->data,(INDEX_LIST_LENGTH (*list)) * sizeof(unsigned));
+/* XRETALLOC (list->data, INDEX_LIST_LENGTH (*list), unsigned); */
+ list->data[INDEX_LIST_LENGTH (*list) - 1] = new_index;
+}