[smooth] Fix and improve spline flattening.

This fixes the flattening of cubic, S-shaped curves and speeds up
the handling of both the conic and cubic arcs.

See the discussions on the freetype-devel mailing list in late
August and September 2010 for details.

* src/smooth/ftgrays.c (FT_MAX_CURVE_DEVIATION): New macro.
(TWorker): Remove `conic_level' and `cubic_level' elements.
(gray_render_conic): Simplify algorithm.
(gray_render_cubic): New algorithm; details are given in the code
comments.
(gray_convert_glyph): Remove heuristics.
This commit is contained in:
Graham Asher 2010-09-20 09:29:23 +02:00 committed by Werner Lemberg
parent d38ba0c92d
commit e0a9a93330
2 changed files with 139 additions and 146 deletions

View File

@ -1,3 +1,21 @@
2010-09-20 Graham Asher <graham.asher@btinternet.com>
David Bevan <david.bevan@pb.com>
[smooth] Fix and improve spline flattening.
This fixes the flattening of cubic, S-shaped curves and speeds up
the handling of both the conic and cubic arcs.
See the discussions on the freetype-devel mailing list in late
August and September 2010 for details.
* src/smooth/ftgrays.c (FT_MAX_CURVE_DEVIATION): New macro.
(TWorker): Remove `conic_level' and `cubic_level' elements.
(gray_render_conic): Simplify algorithm.
(gray_render_cubic): New algorithm; details are given in the code
comments.
(gray_convert_glyph): Remove heuristics.
2010-09-19 Werner Lemberg <wl@gnu.org>
Minor fixes.

View File

@ -91,6 +91,11 @@
#define FT_COMPONENT trace_smooth
/* The maximum distance of a curve from the chord, in 64ths of a pixel; */
/* used when flattening curves. */
#define FT_MAX_CURVE_DEVIATION 16
#ifdef _STANDALONE_
@ -187,7 +192,7 @@ typedef ptrdiff_t FT_PtrDist;
shift_, \
delta_ \
};
#define FT_DEFINE_RASTER_FUNCS( class_, glyph_format_, \
raster_new_, raster_reset_, \
raster_set_mode_, raster_render_, \
@ -354,8 +359,6 @@ typedef ptrdiff_t FT_PtrDist;
int band_size;
int band_shoot;
int conic_level;
int cubic_level;
ft_jmp_buf jump_buffer;
@ -888,33 +891,20 @@ typedef ptrdiff_t FT_PtrDist;
if ( dx < dy )
dx = dy;
if ( dx <= FT_MAX_CURVE_DEVIATION )
{
gray_render_line( RAS_VAR_ UPSCALE( to->x ), UPSCALE( to->y ) );
return;
}
level = 1;
dx = dx / ras.conic_level;
while ( dx > 0 )
dx /= FT_MAX_CURVE_DEVIATION;
while ( dx > 1 )
{
dx >>= 2;
level++;
}
/* a shortcut to speed things up */
if ( level <= 1 )
{
/* we compute the mid-point directly in order to avoid */
/* calling gray_split_conic() */
TPos to_x, to_y, mid_x, mid_y;
to_x = UPSCALE( to->x );
to_y = UPSCALE( to->y );
mid_x = ( ras.x + to_x + 2 * UPSCALE( control->x ) ) / 4;
mid_y = ( ras.y + to_y + 2 * UPSCALE( control->y ) ) / 4;
gray_render_line( RAS_VAR_ mid_x, mid_y );
gray_render_line( RAS_VAR_ to_x, to_y );
return;
}
arc = ras.bez_stack;
levels = ras.lev_stack;
top = 0;
@ -957,21 +947,9 @@ typedef ptrdiff_t FT_PtrDist;
}
Draw:
{
TPos to_x, to_y, mid_x, mid_y;
to_x = arc[0].x;
to_y = arc[0].y;
mid_x = ( ras.x + to_x + 2 * arc[1].x ) / 4;
mid_y = ( ras.y + to_y + 2 * arc[1].y ) / 4;
gray_render_line( RAS_VAR_ mid_x, mid_y );
gray_render_line( RAS_VAR_ to_x, to_y );
top--;
arc -= 2;
}
gray_render_line( RAS_VAR_ arc[0].x, arc[0].y );
top--;
arc -= 2;
}
return;
@ -1011,56 +989,9 @@ typedef ptrdiff_t FT_PtrDist;
const FT_Vector* control2,
const FT_Vector* to )
{
int top, level;
int* levels;
FT_Vector* arc;
int mid_x = ( DOWNSCALE( ras.x ) + to->x +
3 * (control1->x + control2->x ) ) / 8;
int mid_y = ( DOWNSCALE( ras.y ) + to->y +
3 * (control1->y + control2->y ) ) / 8;
TPos dx = DOWNSCALE( ras.x ) + to->x - ( mid_x << 1 );
TPos dy = DOWNSCALE( ras.y ) + to->y - ( mid_y << 1 );
if ( dx < 0 )
dx = -dx;
if ( dy < 0 )
dy = -dy;
if ( dx < dy )
dx = dy;
level = 1;
dx /= ras.cubic_level;
while ( dx > 0 )
{
dx >>= 2;
level++;
}
if ( level <= 1 )
{
TPos to_x, to_y;
to_x = UPSCALE( to->x );
to_y = UPSCALE( to->y );
/* Recalculation of midpoint is needed only if */
/* UPSCALE and DOWNSCALE have any effect. */
#if ( PIXEL_BITS != 6 )
mid_x = ( ras.x + to_x +
3 * UPSCALE( control1->x + control2->x ) ) / 8;
mid_y = ( ras.y + to_y +
3 * UPSCALE( control1->y + control2->y ) ) / 8;
#endif
gray_render_line( RAS_VAR_ mid_x, mid_y );
gray_render_line( RAS_VAR_ to_x, to_y );
return;
}
arc = ras.bez_stack;
arc[0].x = UPSCALE( to->x );
arc[0].y = UPSCALE( to->y );
@ -1071,60 +1002,123 @@ typedef ptrdiff_t FT_PtrDist;
arc[3].x = ras.x;
arc[3].y = ras.y;
levels = ras.lev_stack;
top = 0;
levels[0] = level;
while ( top >= 0 )
for (;;)
{
level = levels[top];
if ( level > 1 )
/* Check that the arc crosses the current band. */
TPos min, max, y;
min = max = arc[0].y;
y = arc[1].y;
if ( y < min )
min = y;
if ( y > max )
max = y;
y = arc[2].y;
if ( y < min )
min = y;
if ( y > max )
max = y;
y = arc[3].y;
if ( y < min )
min = y;
if ( y > max )
max = y;
if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < 0 )
goto Draw;
/* Decide whether to split or draw. See `Rapid Termination */
/* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */
/* F. Hain, at */
/* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */
{
/* check that the arc crosses the current band */
TPos min, max, y;
TPos dx, dy, dx_, dy_;
TPos dx1, dy1, dx2, dy2;
TPos L, s, s_limit;
min = max = arc[0].y;
y = arc[1].y;
if ( y < min ) min = y;
if ( y > max ) max = y;
y = arc[2].y;
if ( y < min ) min = y;
if ( y > max ) max = y;
y = arc[3].y;
if ( y < min ) min = y;
if ( y > max ) max = y;
if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < 0 )
goto Draw;
gray_split_cubic( arc );
arc += 3;
top ++;
levels[top] = levels[top - 1] = level - 1;
continue;
/* dx and dy are x and y components of the P0-P3 chord vector. */
dx = arc[3].x - arc[0].x;
dy = arc[3].y - arc[0].y;
/* L is an (under)estimate of the Euclidean distance P0-P3. */
/* */
/* If dx >= dy, then r = sqrt(dx^2 + dy^2) can be overestimated */
/* with least maximum error by */
/* */
/* r_upperbound = dx + (sqrt(2) - 1) * dy , */
/* */
/* where sqrt(2) - 1 can be (over)estimated by 107/256, giving an */
/* error of no more than 8.4%. */
/* */
/* Similarly, some elementary calculus shows that r can be */
/* underestimated with least maximum error by */
/* */
/* r_lowerbound = sqrt(2 + sqrt(2)) / 2 * dx */
/* + sqrt(2 - sqrt(2)) / 2 * dy . */
/* */
/* 236/256 and 97/256 are (under)estimates of the two algebraic */
/* numbers, giving an error of no more than 8.1%. */
dx_ = FT_ABS( dx );
dy_ = FT_ABS( dy );
L = ( 236 * FT_MAX( dx_, dy_ ) + 97 * FT_MIN( dx_, dy_ ) ) >> 8;
/* Avoid possible arithmetic overflow below by splitting. */
if ( L > 32767 )
goto Split;
/* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */
s_limit = L * (TPos)( FT_MAX_CURVE_DEVIATION / 0.75 );
/* s is L * the perpendicular distance from P1 to the line P0-P3. */
dx1 = arc[1].x - arc[0].x;
dy1 = arc[1].y - arc[0].y;
s = FT_ABS( dy * dx1 - dx * dy1 );
if ( s > s_limit )
goto Split;
/* s is L * the perpendicular distance from P2 to the line P0-P3. */
dx2 = arc[2].x - arc[0].x;
dy2 = arc[2].y - arc[0].y;
s = FT_ABS( dy * dx2 - dx * dy2 );
if ( s > s_limit )
goto Split;
/* If P1 or P2 is outside P0-P3, split the curve. */
if ( dy * dy1 + dx * dx1 < 0 ||
dy * dy2 + dx * dx2 < 0 ||
dy * (arc[3].y - arc[1].y) + dx * (arc[3].x - arc[1].x) < 0 ||
dy * (arc[3].y - arc[2].y) + dx * (arc[3].x - arc[2].x) < 0 )
goto Split;
/* No reason to split. */
goto Draw;
}
Split:
gray_split_cubic( arc );
arc += 3;
continue;
Draw:
{
TPos to_x, to_y;
gray_render_line( RAS_VAR_ arc[0].x, arc[0].y );
if ( arc == ras.bez_stack )
return;
to_x = arc[0].x;
to_y = arc[0].y;
mid_x = ( ras.x + to_x + 3 * ( arc[1].x + arc[2].x ) ) / 8;
mid_y = ( ras.y + to_y + 3 * ( arc[1].y + arc[2].y ) ) / 8;
gray_render_line( RAS_VAR_ mid_x, mid_y );
gray_render_line( RAS_VAR_ to_x, to_y );
top --;
arc -= 3;
}
arc -= 3;
}
return;
}
static int
gray_move_to( const FT_Vector* to,
PWorker worker )
@ -1760,25 +1754,6 @@ typedef ptrdiff_t FT_PtrDist;
ras.count_ex = ras.max_ex - ras.min_ex;
ras.count_ey = ras.max_ey - ras.min_ey;
/* simple heuristic used to speed up the bezier decomposition -- see */
/* the code in gray_render_conic() and gray_render_cubic() for more */
/* details */
ras.conic_level = 32;
ras.cubic_level = 16;
{
int level = 0;
if ( ras.count_ex > 24 || ras.count_ey > 24 )
level++;
if ( ras.count_ex > 120 || ras.count_ey > 120 )
level++;
ras.conic_level <<= level;
ras.cubic_level <<= level;
}
/* set up vertical bands */
num_bands = (int)( ( ras.max_ey - ras.min_ey ) / ras.band_size );
if ( num_bands == 0 )