2 # User Robert O'Callahan <robert@ocallahan.org>
3 # Date 1249558156 -43200
4 # Node ID e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde
5 # Parent 6ef9993a30bf2f983c9d64d7441d2e3b6b935de1
6 Bug 508227. Don't fallback to Quartz for repeating radial gradients. r=jmuizelaar
8 diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c
9 --- a/gfx/cairo/cairo/src/cairo-quartz-surface.c
10 +++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c
11 @@ -708,20 +708,20 @@ CreateGradientFunction (const cairo_grad
20 -CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface,
21 - const cairo_gradient_pattern_t *gpat,
22 - CGPoint *start, CGPoint *end,
23 - CGAffineTransform matrix)
24 +CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
25 + const cairo_gradient_pattern_t *gpat,
26 + CGPoint *start, CGPoint *end,
27 + CGAffineTransform matrix)
30 float input_value_range[2];
31 float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
32 CGFunctionCallbacks callbacks = {
33 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
36 @@ -791,16 +791,156 @@ CreateRepeatingGradientFunction (cairo_q
37 return CGFunctionCreate (pat,
46 +UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center,
47 + double dr, double dx, double dy,
50 + /* Compute a parameter t such that a circle centered at
51 + (center->x + dx*t, center->y + dy*t) with radius dr*t contains the
54 + Let px = x - center->x, py = y - center->y.
55 + Parameter values for which t is on the circle are given by
56 + (px - dx*t)^2 + (py - dy*t)^2 = (t*dr)^2
58 + Solving for t using the quadratic formula, and simplifying, we get
59 + numerator = dx*px + dy*py +-
60 + sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
61 + denominator = dx^2 + dy^2 - dr^2
62 + t = numerator/denominator
64 + In CreateRepeatingRadialGradientFunction we know the outer circle
65 + contains the inner circle. Therefore the distance between the circle
66 + centers plus the radius of the inner circle is less than the radius of
67 + the outer circle. (This is checked in _cairo_quartz_setup_radial_source.)
70 + So the denominator is negative and the larger solution for t is given by
71 + numerator = dx*px + dy*py -
72 + sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
73 + denominator = dx^2 + dy^2 - dr^2
74 + t = numerator/denominator
75 + dx^2 + dy^2 < dr^2 also ensures that the operand of sqrt is positive.
77 + double px = x - center->x;
78 + double py = y - center->y;
79 + double dx_py_minus_dy_px = dx*py - dy*px;
80 + double numerator = dx*px + dy*py -
81 + sqrt (dr*dr*(px*px + py*py) - dx_py_minus_dy_px*dx_py_minus_dy_px);
82 + double denominator = dx*dx + dy*dy - dr*dr;
83 + double t = numerator/denominator;
90 +/* This must only be called when one of the circles properly contains the other */
92 +CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
93 + const cairo_gradient_pattern_t *gpat,
94 + CGPoint *start, double *start_radius,
95 + CGPoint *end, double *end_radius)
97 + CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
98 + CGAffineTransform transform;
99 + cairo_pattern_t *pat;
100 + float input_value_range[2];
101 + float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
102 + CGFunctionCallbacks callbacks = {
103 + 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
106 + double *inner_radius;
108 + double *outer_radius;
109 + /* minimum and maximum t-parameter values that will make our gradient
110 + cover the clipBox */
111 + double t_min, t_max, t_temp;
112 + /* outer minus inner */
115 + _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
116 + /* clip is in cairo device coordinates; get it into cairo user space */
117 + clip = CGRectApplyAffineTransform (clip, transform);
119 + if (*start_radius < *end_radius) {
120 + /* end circle contains start circle */
123 + inner_radius = start_radius;
124 + outer_radius = end_radius;
126 + /* start circle contains end circle */
129 + inner_radius = end_radius;
130 + outer_radius = start_radius;
133 + dr = *outer_radius - *inner_radius;
134 + dx = outer->x - inner->x;
135 + dy = outer->y - inner->y;
137 + t_min = -(*inner_radius/dr);
138 + inner->x += t_min*dx;
139 + inner->y += t_min*dy;
140 + *inner_radius = 0.;
143 + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
144 + clip.origin.x, clip.origin.y);
145 + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
146 + clip.origin.x + clip.size.width, clip.origin.y);
147 + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
148 + clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
149 + UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
150 + clip.origin.x, clip.origin.y + clip.size.height);
151 + /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
152 + But for the parameter values we use with Quartz, t_min means radius 0.
153 + Also, add a small fudge factor to avoid rounding issues. Since the
154 + circles are alway expanding and containing the earlier circles, this is
157 + t_max = t_min + t_temp;
158 + outer->x = inner->x + t_temp*dx;
159 + outer->y = inner->y + t_temp*dy;
160 + *outer_radius = t_temp*dr;
162 + /* set the input range for the function -- the function knows how to
163 + map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
164 + if (*start_radius < *end_radius) {
165 + input_value_range[0] = t_min;
166 + input_value_range[1] = t_max;
168 + input_value_range[0] = -t_max;
169 + input_value_range[1] = -t_min;
172 + if (_cairo_pattern_create_copy (&pat, &gpat->base))
173 + /* quartz doesn't deal very well with malloc failing, so there's
174 + * not much point in us trying either */
177 + return CGFunctionCreate (pat,
181 + output_value_ranges,
185 /* Obtain a CGImageRef from a #cairo_surface_t * */
188 DataProviderReleaseCallback (void *info, const void *data, size_t size)
190 cairo_surface_t *surface = (cairo_surface_t *) info;
191 cairo_surface_destroy (surface);
193 @@ -1112,23 +1252,24 @@ _cairo_quartz_setup_linear_source (cairo
194 rgb = CGColorSpaceCreateDeviceRGB();
196 start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
197 _cairo_fixed_to_double (lpat->p1.y));
198 end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x),
199 _cairo_fixed_to_double (lpat->p2.y));
201 if (abspat->extend == CAIRO_EXTEND_NONE ||
202 - abspat->extend == CAIRO_EXTEND_PAD)
203 + abspat->extend == CAIRO_EXTEND_PAD)
205 gradFunc = CreateGradientFunction (&lpat->base);
207 - gradFunc = CreateRepeatingGradientFunction (surface,
209 - &start, &end, surface->sourceTransform);
210 + gradFunc = CreateRepeatingLinearGradientFunction (surface,
213 + surface->sourceTransform);
216 surface->sourceShading = CGShadingCreateAxial (rgb,
221 CGColorSpaceRelease(rgb);
222 @@ -1142,52 +1283,68 @@ _cairo_quartz_setup_radial_source (cairo
223 const cairo_radial_pattern_t *rpat)
225 const cairo_pattern_t *abspat = &rpat->base.base;
228 CGFunctionRef gradFunc;
230 bool extend = abspat->extend == CAIRO_EXTEND_PAD;
231 + double c1x = _cairo_fixed_to_double (rpat->c1.x);
232 + double c1y = _cairo_fixed_to_double (rpat->c1.y);
233 + double c2x = _cairo_fixed_to_double (rpat->c2.x);
234 + double c2y = _cairo_fixed_to_double (rpat->c2.y);
235 + double r1 = _cairo_fixed_to_double (rpat->r1);
236 + double r2 = _cairo_fixed_to_double (rpat->r2);
237 + double dx = c1x - c2x;
238 + double dy = c1y - c2y;
239 + double centerDistance = sqrt (dx*dx + dy*dy);
241 if (rpat->base.n_stops == 0) {
242 CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
243 CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
247 - if (abspat->extend == CAIRO_EXTEND_REPEAT ||
248 - abspat->extend == CAIRO_EXTEND_REFLECT)
250 - /* I started trying to map these to Quartz, but it's much harder
251 - * then the linear case (I think it would involve doing multiple
252 - * Radial shadings). So, instead, let's just render an image
253 - * for pixman to draw the shading into, and use that.
254 + if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */
255 + r1 <= centerDistance + r2 + 1e-6) { /* circle 1 doesn't contain circle 2 */
256 + /* Quartz handles cases where neither circle contains the other very
257 + * differently from pixman.
258 + * Whatever the correct behaviour is, let's at least have only pixman's
259 + * implementation to worry about.
260 + * Note that this also catches the cases where r1 == r2.
262 - return _cairo_quartz_setup_fallback_source (surface, &rpat->base.base);
263 + return _cairo_quartz_setup_fallback_source (surface, abspat);
266 mat = abspat->matrix;
267 cairo_matrix_invert (&mat);
268 _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
270 rgb = CGColorSpaceCreateDeviceRGB();
272 - start = CGPointMake (_cairo_fixed_to_double (rpat->c1.x),
273 - _cairo_fixed_to_double (rpat->c1.y));
274 - end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x),
275 - _cairo_fixed_to_double (rpat->c2.y));
276 + start = CGPointMake (c1x, c1y);
277 + end = CGPointMake (c2x, c2y);
279 - gradFunc = CreateGradientFunction (&rpat->base);
280 + if (abspat->extend == CAIRO_EXTEND_NONE ||
281 + abspat->extend == CAIRO_EXTEND_PAD)
283 + gradFunc = CreateGradientFunction (&rpat->base);
285 + gradFunc = CreateRepeatingRadialGradientFunction (surface,
291 surface->sourceShading = CGShadingCreateRadial (rgb,
293 - _cairo_fixed_to_double (rpat->r1),
296 - _cairo_fixed_to_double (rpat->r2),
301 CGColorSpaceRelease(rgb);
302 CGFunctionRelease(gradFunc);