michael@0: # HG changeset patch michael@0: # User Robert O'Callahan michael@0: # Date 1249558156 -43200 michael@0: # Node ID e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde michael@0: # Parent 6ef9993a30bf2f983c9d64d7441d2e3b6b935de1 michael@0: Bug 508227. Don't fallback to Quartz for repeating radial gradients. r=jmuizelaar michael@0: michael@0: diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: --- a/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: +++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c michael@0: @@ -708,20 +708,20 @@ CreateGradientFunction (const cairo_grad michael@0: 1, michael@0: input_value_range, michael@0: 4, michael@0: output_value_ranges, michael@0: &callbacks); michael@0: } michael@0: michael@0: static CGFunctionRef michael@0: -CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface, michael@0: - const cairo_gradient_pattern_t *gpat, michael@0: - CGPoint *start, CGPoint *end, michael@0: - CGAffineTransform matrix) michael@0: +CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface, michael@0: + const cairo_gradient_pattern_t *gpat, michael@0: + CGPoint *start, CGPoint *end, michael@0: + CGAffineTransform matrix) michael@0: { michael@0: cairo_pattern_t *pat; michael@0: float input_value_range[2]; michael@0: float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f }; michael@0: CGFunctionCallbacks callbacks = { michael@0: 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy michael@0: }; michael@0: michael@0: @@ -791,16 +791,156 @@ CreateRepeatingGradientFunction (cairo_q michael@0: return CGFunctionCreate (pat, michael@0: 1, michael@0: input_value_range, michael@0: 4, michael@0: output_value_ranges, michael@0: &callbacks); michael@0: } michael@0: michael@0: +static void michael@0: +UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center, michael@0: + double dr, double dx, double dy, michael@0: + double x, double y) michael@0: +{ michael@0: + /* Compute a parameter t such that a circle centered at michael@0: + (center->x + dx*t, center->y + dy*t) with radius dr*t contains the michael@0: + point (x,y). michael@0: + michael@0: + Let px = x - center->x, py = y - center->y. michael@0: + Parameter values for which t is on the circle are given by michael@0: + (px - dx*t)^2 + (py - dy*t)^2 = (t*dr)^2 michael@0: + michael@0: + Solving for t using the quadratic formula, and simplifying, we get michael@0: + numerator = dx*px + dy*py +- michael@0: + sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 ) michael@0: + denominator = dx^2 + dy^2 - dr^2 michael@0: + t = numerator/denominator michael@0: + michael@0: + In CreateRepeatingRadialGradientFunction we know the outer circle michael@0: + contains the inner circle. Therefore the distance between the circle michael@0: + centers plus the radius of the inner circle is less than the radius of michael@0: + the outer circle. (This is checked in _cairo_quartz_setup_radial_source.) michael@0: + Therefore michael@0: + dx^2 + dy^2 < dr^2 michael@0: + So the denominator is negative and the larger solution for t is given by michael@0: + numerator = dx*px + dy*py - michael@0: + sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 ) michael@0: + denominator = dx^2 + dy^2 - dr^2 michael@0: + t = numerator/denominator michael@0: + dx^2 + dy^2 < dr^2 also ensures that the operand of sqrt is positive. michael@0: + */ michael@0: + double px = x - center->x; michael@0: + double py = y - center->y; michael@0: + double dx_py_minus_dy_px = dx*py - dy*px; michael@0: + double numerator = dx*px + dy*py - michael@0: + sqrt (dr*dr*(px*px + py*py) - dx_py_minus_dy_px*dx_py_minus_dy_px); michael@0: + double denominator = dx*dx + dy*dy - dr*dr; michael@0: + double t = numerator/denominator; michael@0: + michael@0: + if (*max_t < t) { michael@0: + *max_t = t; michael@0: + } michael@0: +} michael@0: + michael@0: +/* This must only be called when one of the circles properly contains the other */ michael@0: +static CGFunctionRef michael@0: +CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface, michael@0: + const cairo_gradient_pattern_t *gpat, michael@0: + CGPoint *start, double *start_radius, michael@0: + CGPoint *end, double *end_radius) michael@0: +{ michael@0: + CGRect clip = CGContextGetClipBoundingBox (surface->cgContext); michael@0: + CGAffineTransform transform; michael@0: + cairo_pattern_t *pat; michael@0: + float input_value_range[2]; michael@0: + float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f }; michael@0: + CGFunctionCallbacks callbacks = { michael@0: + 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy michael@0: + }; michael@0: + CGPoint *inner; michael@0: + double *inner_radius; michael@0: + CGPoint *outer; michael@0: + double *outer_radius; michael@0: + /* minimum and maximum t-parameter values that will make our gradient michael@0: + cover the clipBox */ michael@0: + double t_min, t_max, t_temp; michael@0: + /* outer minus inner */ michael@0: + double dr, dx, dy; michael@0: + michael@0: + _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform); michael@0: + /* clip is in cairo device coordinates; get it into cairo user space */ michael@0: + clip = CGRectApplyAffineTransform (clip, transform); michael@0: + michael@0: + if (*start_radius < *end_radius) { michael@0: + /* end circle contains start circle */ michael@0: + inner = start; michael@0: + outer = end; michael@0: + inner_radius = start_radius; michael@0: + outer_radius = end_radius; michael@0: + } else { michael@0: + /* start circle contains end circle */ michael@0: + inner = end; michael@0: + outer = start; michael@0: + inner_radius = end_radius; michael@0: + outer_radius = start_radius; michael@0: + } michael@0: + michael@0: + dr = *outer_radius - *inner_radius; michael@0: + dx = outer->x - inner->x; michael@0: + dy = outer->y - inner->y; michael@0: + michael@0: + t_min = -(*inner_radius/dr); michael@0: + inner->x += t_min*dx; michael@0: + inner->y += t_min*dy; michael@0: + *inner_radius = 0.; michael@0: + michael@0: + t_temp = 0.; michael@0: + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: + clip.origin.x, clip.origin.y); michael@0: + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: + clip.origin.x + clip.size.width, clip.origin.y); michael@0: + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: + clip.origin.x + clip.size.width, clip.origin.y + clip.size.height); michael@0: + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy, michael@0: + clip.origin.x, clip.origin.y + clip.size.height); michael@0: + /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0. michael@0: + But for the parameter values we use with Quartz, t_min means radius 0. michael@0: + Also, add a small fudge factor to avoid rounding issues. Since the michael@0: + circles are alway expanding and containing the earlier circles, this is michael@0: + OK. */ michael@0: + t_temp += 1e-6; michael@0: + t_max = t_min + t_temp; michael@0: + outer->x = inner->x + t_temp*dx; michael@0: + outer->y = inner->y + t_temp*dy; michael@0: + *outer_radius = t_temp*dr; michael@0: + michael@0: + /* set the input range for the function -- the function knows how to michael@0: + map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */ michael@0: + if (*start_radius < *end_radius) { michael@0: + input_value_range[0] = t_min; michael@0: + input_value_range[1] = t_max; michael@0: + } else { michael@0: + input_value_range[0] = -t_max; michael@0: + input_value_range[1] = -t_min; michael@0: + } michael@0: + michael@0: + if (_cairo_pattern_create_copy (&pat, &gpat->base)) michael@0: + /* quartz doesn't deal very well with malloc failing, so there's michael@0: + * not much point in us trying either */ michael@0: + return NULL; michael@0: + michael@0: + return CGFunctionCreate (pat, michael@0: + 1, michael@0: + input_value_range, michael@0: + 4, michael@0: + output_value_ranges, michael@0: + &callbacks); michael@0: +} michael@0: + michael@0: /* Obtain a CGImageRef from a #cairo_surface_t * */ michael@0: michael@0: static void michael@0: DataProviderReleaseCallback (void *info, const void *data, size_t size) michael@0: { michael@0: cairo_surface_t *surface = (cairo_surface_t *) info; michael@0: cairo_surface_destroy (surface); michael@0: } michael@0: @@ -1112,23 +1252,24 @@ _cairo_quartz_setup_linear_source (cairo michael@0: rgb = CGColorSpaceCreateDeviceRGB(); michael@0: michael@0: start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x), michael@0: _cairo_fixed_to_double (lpat->p1.y)); michael@0: end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x), michael@0: _cairo_fixed_to_double (lpat->p2.y)); michael@0: michael@0: if (abspat->extend == CAIRO_EXTEND_NONE || michael@0: - abspat->extend == CAIRO_EXTEND_PAD) michael@0: + abspat->extend == CAIRO_EXTEND_PAD) michael@0: { michael@0: gradFunc = CreateGradientFunction (&lpat->base); michael@0: } else { michael@0: - gradFunc = CreateRepeatingGradientFunction (surface, michael@0: - &lpat->base, michael@0: - &start, &end, surface->sourceTransform); michael@0: + gradFunc = CreateRepeatingLinearGradientFunction (surface, michael@0: + &lpat->base, michael@0: + &start, &end, michael@0: + surface->sourceTransform); michael@0: } michael@0: michael@0: surface->sourceShading = CGShadingCreateAxial (rgb, michael@0: start, end, michael@0: gradFunc, michael@0: extend, extend); michael@0: michael@0: CGColorSpaceRelease(rgb); michael@0: @@ -1142,52 +1283,68 @@ _cairo_quartz_setup_radial_source (cairo michael@0: const cairo_radial_pattern_t *rpat) michael@0: { michael@0: const cairo_pattern_t *abspat = &rpat->base.base; michael@0: cairo_matrix_t mat; michael@0: CGPoint start, end; michael@0: CGFunctionRef gradFunc; michael@0: CGColorSpaceRef rgb; michael@0: bool extend = abspat->extend == CAIRO_EXTEND_PAD; michael@0: + double c1x = _cairo_fixed_to_double (rpat->c1.x); michael@0: + double c1y = _cairo_fixed_to_double (rpat->c1.y); michael@0: + double c2x = _cairo_fixed_to_double (rpat->c2.x); michael@0: + double c2y = _cairo_fixed_to_double (rpat->c2.y); michael@0: + double r1 = _cairo_fixed_to_double (rpat->r1); michael@0: + double r2 = _cairo_fixed_to_double (rpat->r2); michael@0: + double dx = c1x - c2x; michael@0: + double dy = c1y - c2y; michael@0: + double centerDistance = sqrt (dx*dx + dy*dy); michael@0: michael@0: if (rpat->base.n_stops == 0) { michael@0: CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); michael@0: CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); michael@0: return DO_SOLID; michael@0: } michael@0: michael@0: - if (abspat->extend == CAIRO_EXTEND_REPEAT || michael@0: - abspat->extend == CAIRO_EXTEND_REFLECT) michael@0: - { michael@0: - /* I started trying to map these to Quartz, but it's much harder michael@0: - * then the linear case (I think it would involve doing multiple michael@0: - * Radial shadings). So, instead, let's just render an image michael@0: - * for pixman to draw the shading into, and use that. michael@0: + if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */ michael@0: + r1 <= centerDistance + r2 + 1e-6) { /* circle 1 doesn't contain circle 2 */ michael@0: + /* Quartz handles cases where neither circle contains the other very michael@0: + * differently from pixman. michael@0: + * Whatever the correct behaviour is, let's at least have only pixman's michael@0: + * implementation to worry about. michael@0: + * Note that this also catches the cases where r1 == r2. michael@0: */ michael@0: - return _cairo_quartz_setup_fallback_source (surface, &rpat->base.base); michael@0: + return _cairo_quartz_setup_fallback_source (surface, abspat); michael@0: } michael@0: michael@0: mat = abspat->matrix; michael@0: cairo_matrix_invert (&mat); michael@0: _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); michael@0: michael@0: rgb = CGColorSpaceCreateDeviceRGB(); michael@0: michael@0: - start = CGPointMake (_cairo_fixed_to_double (rpat->c1.x), michael@0: - _cairo_fixed_to_double (rpat->c1.y)); michael@0: - end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x), michael@0: - _cairo_fixed_to_double (rpat->c2.y)); michael@0: + start = CGPointMake (c1x, c1y); michael@0: + end = CGPointMake (c2x, c2y); michael@0: michael@0: - gradFunc = CreateGradientFunction (&rpat->base); michael@0: + if (abspat->extend == CAIRO_EXTEND_NONE || michael@0: + abspat->extend == CAIRO_EXTEND_PAD) michael@0: + { michael@0: + gradFunc = CreateGradientFunction (&rpat->base); michael@0: + } else { michael@0: + gradFunc = CreateRepeatingRadialGradientFunction (surface, michael@0: + &rpat->base, michael@0: + &start, &r1, michael@0: + &end, &r2); michael@0: + } michael@0: michael@0: surface->sourceShading = CGShadingCreateRadial (rgb, michael@0: start, michael@0: - _cairo_fixed_to_double (rpat->r1), michael@0: + r1, michael@0: end, michael@0: - _cairo_fixed_to_double (rpat->r2), michael@0: + r2, michael@0: gradFunc, michael@0: extend, extend); michael@0: michael@0: CGColorSpaceRelease(rgb); michael@0: CGFunctionRelease(gradFunc); michael@0: michael@0: return DO_SHADING; michael@0: }