home *** CD-ROM | disk | FTP | other *** search
- /* 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 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. */
-
- #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) */
- 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 ();
- 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.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;
-
- /* 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);
- 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 ()
- {
- 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;
- }
-