diff options
Diffstat (limited to 'gfx/cairo/quartz-minimize-gradient-repeat.patch')
-rw-r--r-- | gfx/cairo/quartz-minimize-gradient-repeat.patch | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/gfx/cairo/quartz-minimize-gradient-repeat.patch b/gfx/cairo/quartz-minimize-gradient-repeat.patch new file mode 100644 index 0000000000..9782bef11e --- /dev/null +++ b/gfx/cairo/quartz-minimize-gradient-repeat.patch @@ -0,0 +1,561 @@ +# HG changeset patch +# User Robert O'Callahan <robert@ocallahan.org> +# Date 1249558989 -43200 +# Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105 +# Parent 963b9451ad305924738d05d997a640698cd3af91 +Bug 508730. Don't repeat a Quartz gradient more times than necessary, to avoid Quartz quality problems when there are lots of repeated color stops. r=jmuizelaar + +diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c +--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c ++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c +@@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad + return CGFunctionCreate (pat, + 1, + input_value_range, + 4, + gradient_output_value_ranges, + &gradient_callbacks); + } + ++static void ++UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start, ++ double dx, double dy, ++ double x, double y) ++{ ++ /* Compute a parameter t such that a line perpendicular to the (dx,dy) ++ vector, passing through (start->x + dx*t, start->y + dy*t), also ++ passes through (x,y). ++ ++ Let px = x - start->x, py = y - start->y. ++ t is given by ++ (px - dx*t)*dx + (py - dy*t)*dy = 0 ++ ++ Solving for t we get ++ numerator = dx*px + dy*py ++ denominator = dx^2 + dy^2 ++ t = numerator/denominator ++ ++ In CreateRepeatingLinearGradientFunction we know the length of (dx,dy) ++ is not zero. (This is checked in _cairo_quartz_setup_linear_source.) ++ */ ++ double px = x - start->x; ++ double py = y - start->y; ++ double numerator = dx*px + dy*py; ++ double denominator = dx*dx + dy*dy; ++ double t = numerator/denominator; ++ ++ if (*min_t > t) { ++ *min_t = t; ++ } ++ if (*max_t < t) { ++ *max_t = t; ++ } ++} ++ + static CGFunctionRef + CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface, + const cairo_gradient_pattern_t *gpat, + CGPoint *start, CGPoint *end, +- CGAffineTransform matrix) ++ cairo_rectangle_int_t *extents) + { + cairo_pattern_t *pat; + float input_value_range[2]; ++ double t_min = 0.; ++ double t_max = 0.; ++ double dx = end->x - start->x; ++ double dy = end->y - start->y; ++ double bounds_x1, bounds_x2, bounds_y1, bounds_y2; + +- CGPoint mstart, mend; ++ if (!extents) { ++ extents = &surface->extents; ++ } ++ bounds_x1 = extents->x; ++ bounds_y1 = extents->y; ++ bounds_x2 = extents->x + extents->width; ++ bounds_y2 = extents->y + extents->height; ++ _cairo_matrix_transform_bounding_box (&gpat->base.matrix, ++ &bounds_x1, &bounds_y1, ++ &bounds_x2, &bounds_y2, ++ NULL); + +- double dx, dy; +- int x_rep_start = 0, x_rep_end = 0; +- int y_rep_start = 0, y_rep_end = 0; ++ UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, ++ bounds_x1, bounds_y1); ++ UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, ++ bounds_x2, bounds_y1); ++ UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, ++ bounds_x2, bounds_y2); ++ UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy, ++ bounds_x1, bounds_y2); + +- int rep_start, rep_end; +- +- // figure out how many times we'd need to repeat the gradient pattern +- // to cover the whole (transformed) surface area +- mstart = CGPointApplyAffineTransform (*start, matrix); +- mend = CGPointApplyAffineTransform (*end, matrix); +- +- dx = fabs (mend.x - mstart.x); +- dy = fabs (mend.y - mstart.y); +- +- if (dx > 1e-6) { +- x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx); +- x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx); +- +- if (mend.x < mstart.x) { +- int swap = x_rep_end; +- x_rep_end = x_rep_start; +- x_rep_start = swap; +- } +- } +- +- if (dy > 1e-6) { +- y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy); +- y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy); +- +- if (mend.y < mstart.y) { +- int swap = y_rep_end; +- y_rep_end = y_rep_start; +- y_rep_start = swap; +- } +- } +- +- rep_start = MAX(x_rep_start, y_rep_start); +- rep_end = MAX(x_rep_end, y_rep_end); +- +- // extend the line between start and end by rep_start times from the start +- // and rep_end times from the end +- +- dx = end->x - start->x; +- dy = end->y - start->y; +- +- start->x = start->x - dx * rep_start; +- start->y = start->y - dy * rep_start; +- +- end->x = end->x + dx * rep_end; +- end->y = end->y + dy * rep_end; ++ /* Move t_min and t_max to the nearest usable integer to try to avoid ++ subtle variations due to numerical instability, especially accidentally ++ cutting off a pixel. Extending the gradient repetitions is always safe. */ ++ t_min = floor (t_min); ++ t_max = ceil (t_max); ++ end->x = start->x + dx*t_max; ++ end->y = start->y + dy*t_max; ++ start->x = start->x + dx*t_min; ++ start->y = start->y + dy*t_min; + + // set the input range for the function -- the function knows how to + // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. +- input_value_range[0] = 0.0 - 1.0 * rep_start; +- input_value_range[1] = 1.0 + 1.0 * rep_end; ++ input_value_range[0] = t_min; ++ input_value_range[1] = t_max; + + if (_cairo_pattern_create_copy (&pat, &gpat->base)) + /* quartz doesn't deal very well with malloc failing, so there's + * not much point in us trying either */ + return NULL; + + return CGFunctionCreate (pat, + 1, +@@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub + } + } + + /* This must only be called when one of the circles properly contains the other */ + static CGFunctionRef + CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface, + const cairo_gradient_pattern_t *gpat, + CGPoint *start, double *start_radius, +- CGPoint *end, double *end_radius) ++ CGPoint *end, double *end_radius, ++ cairo_rectangle_int_t *extents) + { +- CGRect clip = CGContextGetClipBoundingBox (surface->cgContext); +- CGAffineTransform transform; + cairo_pattern_t *pat; + float input_value_range[2]; + CGPoint *inner; + double *inner_radius; + CGPoint *outer; + double *outer_radius; + /* minimum and maximum t-parameter values that will make our gradient + cover the clipBox */ + double t_min, t_max, t_temp; + /* outer minus inner */ + double dr, dx, dy; ++ double bounds_x1, bounds_x2, bounds_y1, bounds_y2; + +- _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform); +- /* clip is in cairo device coordinates; get it into cairo user space */ +- clip = CGRectApplyAffineTransform (clip, transform); ++ if (!extents) { ++ extents = &surface->extents; ++ } ++ bounds_x1 = extents->x; ++ bounds_y1 = extents->y; ++ bounds_x2 = extents->x + extents->width; ++ bounds_y2 = extents->y + extents->height; ++ _cairo_matrix_transform_bounding_box (&gpat->base.matrix, ++ &bounds_x1, &bounds_y1, ++ &bounds_x2, &bounds_y2, ++ NULL); + + if (*start_radius < *end_radius) { + /* end circle contains start circle */ + inner = start; + outer = end; + inner_radius = start_radius; + outer_radius = end_radius; + } else { +@@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c + inner_radius = end_radius; + outer_radius = start_radius; + } + + dr = *outer_radius - *inner_radius; + dx = outer->x - inner->x; + dy = outer->y - inner->y; + ++ /* We can't round or fudge t_min here, it has to be as accurate as possible. */ + t_min = -(*inner_radius/dr); + inner->x += t_min*dx; + inner->y += t_min*dy; + *inner_radius = 0.; + + t_temp = 0.; + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, +- clip.origin.x, clip.origin.y); ++ bounds_x1, bounds_y1); + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, +- clip.origin.x + clip.size.width, clip.origin.y); ++ bounds_x2, bounds_y1); + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, +- clip.origin.x + clip.size.width, clip.origin.y + clip.size.height); ++ bounds_x2, bounds_y2); + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, +- clip.origin.x, clip.origin.y + clip.size.height); ++ bounds_x1, bounds_y2); + /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0. + But for the parameter values we use with Quartz, t_min means radius 0. +- Also, add a small fudge factor to avoid rounding issues. Since the +- circles are alway expanding and containing the earlier circles, this is +- OK. */ +- t_temp += 1e-6; ++ Since the circles are alway expanding and contain the earlier circles, ++ it's safe to extend t_max/t_temp as much as we want, so round t_temp up ++ to the nearest integer. This may help us give stable results. */ ++ t_temp = ceil (t_temp); + t_max = t_min + t_temp; + outer->x = inner->x + t_temp*dx; + outer->y = inner->y + t_temp*dy; + *outer_radius = t_temp*dr; + + /* set the input range for the function -- the function knows how to + map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */ + if (*start_radius < *end_radius) { +@@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai + surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h); + surface->sourceImage = img; + surface->sourceImageSurface = fallback; + surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0); + + return DO_IMAGE; + } + ++/* ++Quartz does not support repeating radients. We handle repeating gradients ++by manually extending the gradient and repeating color stops. We need to ++minimize the number of repetitions since Quartz seems to sample our color ++function across the entire range, even if part of that range is not needed ++for the visible area of the gradient, and it samples with some fixed resolution, ++so if the gradient range is too large it samples with very low resolution and ++the gradient is very coarse. CreateRepeatingLinearGradientFunction and ++CreateRepeatingRadialGradientFunction compute the number of repetitions needed ++based on the extents of the object (the clip region cannot be used here since ++we don't want the rasterization of the entire gradient to depend on the ++clip region). ++*/ + static cairo_quartz_action_t + _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, +- const cairo_linear_pattern_t *lpat) ++ const cairo_linear_pattern_t *lpat, ++ cairo_rectangle_int_t *extents) + { + const cairo_pattern_t *abspat = &lpat->base.base; + cairo_matrix_t mat; + CGPoint start, end; + CGFunctionRef gradFunc; + CGColorSpaceRef rgb; + bool extend = abspat->extend == CAIRO_EXTEND_PAD; + + if (lpat->base.n_stops == 0) { + CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); + CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); + return DO_SOLID; + } + ++ if (lpat->p1.x == lpat->p2.x && ++ lpat->p1.y == lpat->p2.y) { ++ /* Quartz handles cases where the vector has no length very ++ * differently from pixman. ++ * Whatever the correct behaviour is, let's at least have only pixman's ++ * implementation to worry about. ++ */ ++ return _cairo_quartz_setup_fallback_source (surface, abspat); ++ } ++ + mat = abspat->matrix; + cairo_matrix_invert (&mat); + _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); + + rgb = CGColorSpaceCreateDeviceRGB(); + + start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x), + _cairo_fixed_to_double (lpat->p1.y)); +@@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo + if (abspat->extend == CAIRO_EXTEND_NONE || + abspat->extend == CAIRO_EXTEND_PAD) + { + gradFunc = CreateGradientFunction (&lpat->base); + } else { + gradFunc = CreateRepeatingLinearGradientFunction (surface, + &lpat->base, + &start, &end, +- surface->sourceTransform); ++ extents); + } + + surface->sourceShading = CGShadingCreateAxial (rgb, + start, end, + gradFunc, + extend, extend); + + CGColorSpaceRelease(rgb); + CGFunctionRelease(gradFunc); + + return DO_SHADING; + } + + static cairo_quartz_action_t + _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, +- const cairo_radial_pattern_t *rpat) ++ const cairo_radial_pattern_t *rpat, ++ cairo_rectangle_int_t *extents) + { + const cairo_pattern_t *abspat = &rpat->base.base; + cairo_matrix_t mat; + CGPoint start, end; + CGFunctionRef gradFunc; + CGColorSpaceRef rgb; + bool extend = abspat->extend == CAIRO_EXTEND_PAD; + double c1x = _cairo_fixed_to_double (rpat->c1.x); +@@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo + if (abspat->extend == CAIRO_EXTEND_NONE || + abspat->extend == CAIRO_EXTEND_PAD) + { + gradFunc = CreateGradientFunction (&rpat->base); + } else { + gradFunc = CreateRepeatingRadialGradientFunction (surface, + &rpat->base, + &start, &r1, +- &end, &r2); ++ &end, &r2, ++ extents); + } + + surface->sourceShading = CGShadingCreateRadial (rgb, + start, + r1, + end, + r2, + gradFunc, +@@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo + CGColorSpaceRelease(rgb); + CGFunctionRelease(gradFunc); + + return DO_SHADING; + } + + static cairo_quartz_action_t + _cairo_quartz_setup_source (cairo_quartz_surface_t *surface, +- const cairo_pattern_t *source) ++ const cairo_pattern_t *source, ++ cairo_rectangle_int_t *extents) + { + assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern)); + + surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext); + CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter)); + + if (source->type == CAIRO_PATTERN_TYPE_SOLID) { + cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source; +@@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz + solid->color.blue, + solid->color.alpha); + + return DO_SOLID; + } + + if (source->type == CAIRO_PATTERN_TYPE_LINEAR) { + const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source; +- return _cairo_quartz_setup_linear_source (surface, lpat); +- ++ return _cairo_quartz_setup_linear_source (surface, lpat, extents); + } + + if (source->type == CAIRO_PATTERN_TYPE_RADIAL) { + const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source; +- return _cairo_quartz_setup_radial_source (surface, rpat); +- ++ return _cairo_quartz_setup_radial_source (surface, rpat, extents); + } + + if (source->type == CAIRO_PATTERN_TYPE_SURFACE && + (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT))) + { + const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; + cairo_surface_t *pat_surf = spat->surface; + CGImageRef img; +@@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr + if (IS_EMPTY(surface)) + return CAIRO_STATUS_SUCCESS; + + if (op == CAIRO_OPERATOR_DEST) + return CAIRO_STATUS_SUCCESS; + + CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); + +- action = _cairo_quartz_setup_source (surface, source); ++ action = _cairo_quartz_setup_source (surface, source, NULL); + + if (action == DO_SOLID || action == DO_PATTERN) { + CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x, + surface->extents.y, + surface->extents.width, + surface->extents.height)); + } else if (action == DO_SHADING) { + CGContextSaveGState (surface->cgContext); +@@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr + } + + _cairo_quartz_teardown_source (surface, source); + + ND((stderr, "-- paint\n")); + return rv; + } + ++static cairo_bool_t ++_cairo_quartz_source_needs_extents (const cairo_pattern_t *source) ++{ ++ /* For repeating gradients we need to manually extend the gradient and ++ repeat stops, since Quartz doesn't support repeating gradients natively. ++ We need to minimze the number of repeated stops, and since rasterization ++ depends on the number of repetitions we use (even if some of the ++ repetitions go beyond the extents of the object or outside the clip ++ region), it's important to use the same number of repetitions when ++ rendering an object no matter what the clip region is. So the ++ computation of the repetition count cannot depended on the clip region, ++ and should only depend on the object extents, so we need to compute ++ the object extents for repeating gradients. */ ++ return (source->type == CAIRO_PATTERN_TYPE_LINEAR || ++ source->type == CAIRO_PATTERN_TYPE_RADIAL) && ++ (source->extend == CAIRO_EXTEND_REPEAT || ++ source->extend == CAIRO_EXTEND_REFLECT); ++} ++ + static cairo_int_status_t + _cairo_quartz_surface_fill (void *abstract_surface, + cairo_operator_t op, + const cairo_pattern_t *source, + cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_antialias_t antialias, +@@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra + return CAIRO_STATUS_SUCCESS; + } + + CGContextSaveGState (surface->cgContext); + + CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); + CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); + +- action = _cairo_quartz_setup_source (surface, source); ++ if (_cairo_quartz_source_needs_extents (source)) ++ { ++ /* We don't need precise extents since these are only used to ++ compute the number of gradient reptitions needed to cover the ++ object. */ ++ cairo_rectangle_int_t path_extents; ++ _cairo_path_fixed_approximate_fill_extents (path, &path_extents); ++ action = _cairo_quartz_setup_source (surface, source, &path_extents); ++ } else { ++ action = _cairo_quartz_setup_source (surface, source, NULL); ++ } + + CGContextBeginPath (surface->cgContext); + + stroke.cgContext = surface->cgContext; + stroke.ctm_inverse = NULL; + rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); + if (rv) + goto BAIL; +@@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst + + CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes); + if (fdash != sdash) + free (fdash); + } + + CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); + +- action = _cairo_quartz_setup_source (surface, source); ++ if (_cairo_quartz_source_needs_extents (source)) ++ { ++ cairo_rectangle_int_t path_extents; ++ _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents); ++ action = _cairo_quartz_setup_source (surface, source, &path_extents); ++ } else { ++ action = _cairo_quartz_setup_source (surface, source, NULL); ++ } + + CGContextBeginPath (surface->cgContext); + + stroke.cgContext = surface->cgContext; + stroke.ctm_inverse = ctm_inverse; + rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); + if (rv) + goto BAIL; +@@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void + if (op == CAIRO_OPERATOR_DEST) + return CAIRO_STATUS_SUCCESS; + + if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ) + return CAIRO_INT_STATUS_UNSUPPORTED; + + CGContextSaveGState (surface->cgContext); + +- action = _cairo_quartz_setup_source (surface, source); ++ if (_cairo_quartz_source_needs_extents (source)) ++ { ++ cairo_rectangle_int_t glyph_extents; ++ _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs, ++ &glyph_extents); ++ action = _cairo_quartz_setup_source (surface, source, &glyph_extents); ++ } else { ++ action = _cairo_quartz_setup_source (surface, source, NULL); ++ } ++ + if (action == DO_SOLID || action == DO_PATTERN) { + CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill); + } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) { + CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip); + isClipping = TRUE; + } else { + if (action != DO_NOTHING) + rv = CAIRO_INT_STATUS_UNSUPPORTED; |