Improve stroking of densely dashed styles

Add some auxiliary functions to cairo-stroke-style to compute
properties of the dashed patterns (period, "on" coverage) and to
generate approximations when the dash pattern is sub-tolerance.
These functions are used in cairo-path-stroke to simplify dash
patterns stroked by cairo.
Fixes dash-infinite-loop
See http://bugs.freedesktop.org/show_bug.cgi?id=24702
This commit is contained in:
Andrea Canciani 2009-11-10 23:09:56 +01:00
parent 9c24288c82
commit 26e9f14906
3 changed files with 157 additions and 6 deletions

View file

@ -51,6 +51,7 @@ typedef struct _cairo_stroker_dash {
double dash_offset;
const double *dashes;
double approximate_dashes[2];
unsigned int num_dashes;
} cairo_stroker_dash_t;
@ -137,15 +138,25 @@ _cairo_stroker_dash_step (cairo_stroker_dash_t *dash, double step)
static void
_cairo_stroker_dash_init (cairo_stroker_dash_t *dash,
const cairo_stroke_style_t *style)
const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double tolerance)
{
dash->dashed = style->dash != NULL;
if (! dash->dashed)
return;
dash->dashes = style->dash;
dash->num_dashes = style->num_dashes;
dash->dash_offset = style->dash_offset;
if (_cairo_stroke_style_dash_can_approximate (style, ctm, tolerance)) {
_cairo_stroke_style_dash_approximate (style, ctm, tolerance,
&dash->dash_offset,
dash->approximate_dashes,
&dash->num_dashes);
dash->dashes = dash->approximate_dashes;
} else {
dash->dashes = style->dash;
dash->num_dashes = style->num_dashes;
dash->dash_offset = style->dash_offset;
}
_cairo_stroker_dash_start (dash);
}
@ -179,7 +190,7 @@ _cairo_stroker_init (cairo_stroker_t *stroker,
stroker->has_first_face = FALSE;
stroker->has_initial_sub_path = FALSE;
_cairo_stroker_dash_init (&stroker->dash, stroke_style);
_cairo_stroker_dash_init (&stroker->dash, stroke_style, ctm, tolerance);
stroker->add_external_edge = NULL;
@ -1490,7 +1501,8 @@ _cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t *stroker,
stroker->segments_size = ARRAY_LENGTH (stroker->segments_embedded);
stroker->num_segments = 0;
_cairo_stroker_dash_init (&stroker->dash, stroke_style);
/* Assume 2*EPSILON tolerance */
_cairo_stroker_dash_init (&stroker->dash, stroke_style, ctm, _cairo_fixed_to_double (2 * CAIRO_FIXED_EPSILON));
stroker->has_bounds = FALSE;
}

View file

@ -120,3 +120,122 @@ _cairo_stroke_style_max_distance_from_path (const cairo_stroke_style_t *style,
*dx = style_expansion * hypot (ctm->xx, ctm->xy);
*dy = style_expansion * hypot (ctm->yy, ctm->yx);
}
/*
* Computes the period of a dashed stroke style.
* Returns 0 for non-dashed styles.
*/
double
_cairo_stroke_style_dash_period (const cairo_stroke_style_t *style)
{
double period;
unsigned int i;
period = 0.0;
for (i = 0; i < style->num_dashes; i++)
period += style->dash[i];
if (style->num_dashes & 1)
period *= 2.0;
return period;
}
/*
* Coefficient of the linear approximation (minimizing square difference)
* of the surface covered by round caps
*/
#define ROUND_MINSQ_APPROXIMATION (9*M_PI/32)
/*
* Computes the length of the "on" part of a dashed stroke style,
* taking into account also line caps.
* Returns 0 for non-dashed styles.
*/
double
_cairo_stroke_style_dash_stroked (const cairo_stroke_style_t *style)
{
double stroked, cap_scale;
unsigned int i;
switch (style->line_cap) {
default: ASSERT_NOT_REACHED;
case CAIRO_LINE_CAP_BUTT: cap_scale = 0.0; break;
case CAIRO_LINE_CAP_ROUND: cap_scale = ROUND_MINSQ_APPROXIMATION; break;
case CAIRO_LINE_CAP_SQUARE: cap_scale = 1.0; break;
}
stroked = 0.0;
if (style->num_dashes & 1) {
/* Each dash element is used both as on and as off. The order in which they are summed is
* irrelevant, so sum the coverage of one dash element, taken both on and off at each iteration */
for (i = 0; i < style->num_dashes; i++)
stroked += style->dash[i] + cap_scale * MIN (style->dash[i], style->line_width);
} else {
/* Even (0, 2, ...) dashes are on and simply counted for the coverage, odd dashes are off, thus
* their coverage is approximated based on the area covered by the caps of adjacent on dases. */
for (i = 0; i < style->num_dashes; i+=2)
stroked += style->dash[i] + cap_scale * MIN (style->dash[i+1], style->line_width);
}
return stroked;
}
/*
* Verifies if _cairo_stroke_style_dash_approximate should be used to generate
* an approximation of the dash pattern in the specified style, when used for
* stroking a path with the given CTM and tolerance.
* Always FALSE for non-dashed styles.
*/
cairo_bool_t
_cairo_stroke_style_dash_can_approximate (const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double tolerance)
{
double period;
if (! style->num_dashes)
return FALSE;
period = _cairo_stroke_style_dash_period (style);
return _cairo_matrix_transformed_circle_major_axis (ctm, period) < tolerance;
}
/*
* Create a 2-dashes approximation of a dashed style, by making the "on" and "off"
* parts respect the original ratio.
*/
void
_cairo_stroke_style_dash_approximate (const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double tolerance,
double *dash_offset,
double *dashes,
unsigned int *num_dashes)
{
double coverage, scale, offset;
cairo_bool_t on = TRUE;
unsigned int i = 0;
coverage = _cairo_stroke_style_dash_stroked (style) / _cairo_stroke_style_dash_period (style);
coverage = MIN (coverage, 1.0);
scale = tolerance / _cairo_matrix_transformed_circle_major_axis (ctm, 1.0);
/* We stop searching for a starting point as soon as the
offset reaches zero. Otherwise when an initial dash
segment shrinks to zero it will be skipped over. */
offset = style->dash_offset;
while (offset > 0.0 && offset >= style->dash[i]) {
offset -= style->dash[i];
on = !on;
if (++i == style->num_dashes)
i = 0;
}
*num_dashes = 2;
dashes[0] = scale * coverage;
dashes[1] = scale * (1.0 - coverage);
*dash_offset = on ? 0.0 : dashes[0];
}

View file

@ -1749,6 +1749,26 @@ _cairo_stroke_style_max_distance_from_path (const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double *dx, double *dy);
cairo_private double
_cairo_stroke_style_dash_period (const cairo_stroke_style_t *style);
cairo_private double
_cairo_stroke_style_dash_stroked (const cairo_stroke_style_t *style);
cairo_private cairo_bool_t
_cairo_stroke_style_dash_can_approximate (const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double tolerance);
cairo_private void
_cairo_stroke_style_dash_approximate (const cairo_stroke_style_t *style,
const cairo_matrix_t *ctm,
double tolerance,
double *dash_offset,
double *dashes,
unsigned int *num_dashes);
/* cairo-surface.c */
cairo_private cairo_surface_t *