[autofit] Redesign the recognition algorithm of strong points.

In particular, local extrema without horizontal or vertical segments
are better recognized:

  + A                + D
   \                /
    \              /
     \            /
      \          /
       \        + C
        \    /
       B +/

If the distances AB and CD are large, point B wasn't previously
detected as an extremum since the `ft_corner_is_flat' function
`swallowed' BC regardless of its direction, tagging point B as weak.
The next iteration started at B and made `ft_corner_is_flat' swallow
point C, tagging it as weak also, et voilà.

To improve that, another pass gets now performed before calling
`ft_corner_is_flat' to improve the `topology' of an outline: A
sequence of non-horizontal or non-vertical vectors that point into
the same quadrant are handled as a single, large vector.

Additionally, distances of near points are now accumulated, which
makes the auto-hinter handle them as if they were prepended to the
next non-near vector.

This generally improves the auto-hinter's rendering results.

* src/autofit/afhints.c (af_glyph_hints_reload): Implement it.

* src/autofit/afhints.h (AF_FLAGS): Remove no longer used flag
`AF_FLAG_NEAR'.
This commit is contained in:
Werner Lemberg 2014-04-12 20:44:33 +02:00
parent 71f53e122b
commit 8a94b1efd6
3 changed files with 197 additions and 84 deletions

View File

@ -1,3 +1,41 @@
2014-04-12 Werner Lemberg <wl@gnu.org>
[autofit] Redesign the recognition algorithm of strong points.
In particular, local extrema without horizontal or vertical segments
are better recognized:
+ A + D
\ /
\ /
\ /
\ /
\ + C
\ /
B +/
If the distances AB and CD are large, point B wasn't previously
detected as an extremum since the `ft_corner_is_flat' function
`swallowed' BC regardless of its direction, tagging point B as weak.
The next iteration started at B and made `ft_corner_is_flat' swallow
point C, tagging it as weak also, et voilà.
To improve that, another pass gets now performed before calling
`ft_corner_is_flat' to improve the `topology' of an outline: A
sequence of non-horizontal or non-vertical vectors that point into
the same quadrant are handled as a single, large vector.
Additionally, distances of near points are now accumulated, which
makes the auto-hinter handle them as if they were prepended to the
next non-near vector.
This generally improves the auto-hinter's rendering results.
* src/autofit/afhints.c (af_glyph_hints_reload): Implement it.
* src/autofit/afhints.h (AF_FLAGS): Remove no longer used flag
`AF_FLAG_NEAR'.
2014-04-05 Werner Lemberg <wl@gnu.org>
[autofit] Improve scoring algorithm for identifying stems.

View File

@ -698,91 +698,165 @@
}
}
/* compute directions of in & out vectors */
{
AF_Point first = points;
AF_Point prev = NULL;
FT_Pos in_x = 0;
FT_Pos in_y = 0;
AF_Direction in_dir = AF_DIR_NONE;
FT_Pos last_good_in_x = 0;
FT_Pos last_good_in_y = 0;
/*
* Compute directions of `in' and `out' vectors.
*
* Note that distances between points that are very near to each
* other are accumulated. In other words, the auto-hinter
* prepends the small vectors between near points to the first
* non-near vector. All intermediate points are tagged as
* weak; the directions are adjusted also to be equal to the
* accumulated one.
*/
/* value 20 in `near_limit' is heuristic */
FT_UInt units_per_em = hints->metrics->scaler.face->units_per_EM;
FT_Int near_limit = 20 * units_per_em / 2048;
AF_Point* contour;
AF_Point* contour_limit = hints->contours + hints->num_contours;
for ( contour = hints->contours; contour < contour_limit; contour++ )
{
AF_Point first = *contour;
AF_Point next, prev, curr;
FT_Pos out_x, out_y;
FT_Bool is_first;
/* since the first point of a contour could be part of a */
/* series of near points, go backwards to find the first */
/* non-near point and adjust `first' */
point = first;
prev = first->prev;
while ( prev != first )
{
out_x = point->fx - prev->fx;
out_y = point->fy - prev->fy;
/* we use Taxicab metrics to measure the vector length */
if ( FT_ABS( out_x ) + FT_ABS( out_y ) >= near_limit )
break;
point = prev;
prev = prev->prev;
}
/* adjust first point */
first = point;
/* now loop over all points of the contour to get */
/* `in' and `out' vector directions */
curr = first;
out_x = 0;
out_y = 0;
is_first = 1;
for ( point = first;
point != first || is_first;
point = point->next )
{
AF_Direction out_dir;
is_first = 0;
next = point->next;
out_x += next->fx - point->fx;
out_y += next->fy - point->fy;
if ( FT_ABS( out_x ) + FT_ABS( out_y ) < near_limit )
{
next->flags |= AF_FLAG_WEAK_INTERPOLATION;
continue;
}
/* we abuse the `u' and `v' fields to store index deltas */
/* to the next and previous non-near point, respectively */
curr->u = (FT_Pos)( next - curr );
next->v = -curr->u;
out_dir = af_direction_compute( out_x, out_y );
/* adjust directions for all points inbetween; */
/* the loop also updates position of `curr' */
curr->out_dir = (FT_Char)out_dir;
for ( curr = curr->next; curr != next; curr = curr->next )
{
curr->in_dir = (FT_Char)out_dir;
curr->out_dir = (FT_Char)out_dir;
}
next->in_dir = (FT_Char)out_dir;
out_x = 0;
out_y = 0;
}
}
/*
* The next step is to `simplify' an outline's topology so that we
* can identify local extrema more reliably: A series of
* non-horizontal or non-vertical vectors pointing into the same
* quadrant are handled as a single, long vector. From a
* topological point of the view, the intermediate points are of no
* interest and thus tagged as weak.
*/
for ( point = points; point < point_limit; point++ )
{
AF_Point next;
FT_Pos out_x, out_y;
if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
continue;
if ( point == first )
if ( point->in_dir == AF_DIR_NONE &&
point->out_dir == AF_DIR_NONE )
{
prev = first->prev;
/* check whether both vectors point into the same quadrant */
in_x = first->fx - prev->fx;
in_y = first->fy - prev->fy;
FT_Pos in_x, in_y;
FT_Pos out_x, out_y;
last_good_in_x = in_x;
last_good_in_y = in_y;
AF_Point next_u = point + point->u;
AF_Point prev_v = point + point->v;
if ( FT_ABS( in_x ) + FT_ABS( in_y ) < near_limit )
in_x = point->fx - prev_v->fx;
in_y = point->fy - prev_v->fy;
out_x = next_u->fx - point->fx;
out_y = next_u->fy - point->fy;
if ( ( in_x ^ out_x ) >= 0 && ( in_y ^ out_y ) >= 0 )
{
/* search first non-near point to get a good `in_dir' value */
/* yes, so tag current point as weak */
/* and update index deltas */
AF_Point point_ = prev;
point->flags |= AF_FLAG_WEAK_INTERPOLATION;
while ( point_ != first )
{
AF_Point prev_ = point_->prev;
FT_Pos in_x_ = point_->fx - prev_->fx;
FT_Pos in_y_ = point_->fy - prev_->fy;
if ( FT_ABS( in_x_ ) + FT_ABS( in_y_ ) >= near_limit )
{
last_good_in_x = in_x_;
last_good_in_y = in_y_;
break;
}
point_ = prev_;
}
prev_v->u = (FT_Pos)( next_u - prev_v );
next_u->v = -prev_v->u;
}
in_dir = af_direction_compute( in_x, in_y );
first = prev + 1;
}
}
point->in_dir = (FT_Char)in_dir;
/*
* Finally, check for remaining weak points. Everything else not
* collected in edges so far is then implicitly classified as strong
* points.
*/
/* check whether the current point is near to the previous one */
/* (value 20 in `near_limit' is heuristic; we use Taxicab */
/* metrics for the test) */
if ( FT_ABS( in_x ) + FT_ABS( in_y ) < near_limit )
point->flags |= AF_FLAG_NEAR;
else
{
last_good_in_x = in_x;
last_good_in_y = in_y;
}
next = point->next;
out_x = next->fx - point->fx;
out_y = next->fy - point->fy;
in_dir = af_direction_compute( out_x, out_y );
point->out_dir = (FT_Char)in_dir;
/* Check for weak points. The remaining points not collected */
/* in edges are then implicitly classified as strong points. */
for ( point = points; point < point_limit; point++ )
{
if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
continue;
if ( point->flags & AF_FLAG_CONTROL )
{
@ -799,18 +873,25 @@
goto Is_Weak_Point;
}
/* test whether `in' and `out' direction is approximately */
/* the same (and use the last good `in' vector in case */
/* the current point is near to the previous one) */
if ( ft_corner_is_flat(
point->flags & AF_FLAG_NEAR ? last_good_in_x : in_x,
point->flags & AF_FLAG_NEAR ? last_good_in_y : in_y,
out_x,
out_y ) )
{
/* current point lies on a straight, diagonal line */
/* (more or less) */
goto Is_Weak_Point;
AF_Point next_u = point + point->u;
AF_Point prev_v = point + point->v;
if ( ft_corner_is_flat( point->fx - prev_v->fx,
point->fy - prev_v->fy,
next_u->fx - point->fx,
next_u->fy - point->fy ) )
{
/* either the `in' or the `out' vector is much more */
/* dominant than the other one, so tag current point */
/* as weak and update index deltas */
prev_v->u = (FT_Pos)( next_u - prev_v );
next_u->v = -prev_v->u;
goto Is_Weak_Point;
}
}
}
else if ( point->in_dir == -point->out_dir )
@ -818,9 +899,6 @@
/* current point forms a spike */
goto Is_Weak_Point;
}
in_x = out_x;
in_y = out_y;
}
}
}

View File

@ -4,7 +4,7 @@
/* */
/* Auto-fitter hinting routines (specification). */
/* */
/* Copyright 2003-2008, 2010-2012 by */
/* Copyright 2003-2008, 2010-2012, 2014 by */
/* David Turner, Robert Wilhelm, and Werner Lemberg. */
/* */
/* This file is part of the FreeType project, and may only be used, */
@ -236,10 +236,7 @@ FT_BEGIN_HEADER
AF_FLAG_WEAK_INTERPOLATION = 1 << 8,
/* all inflection points in the outline have this flag set */
AF_FLAG_INFLECTION = 1 << 9,
/* the current point is very near to another one */
AF_FLAG_NEAR = 1 << 10
AF_FLAG_INFLECTION = 1 << 9
} AF_Flags;