|
1 # HG changeset patch |
|
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 |
|
7 |
|
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 |
|
12 1, |
|
13 input_value_range, |
|
14 4, |
|
15 output_value_ranges, |
|
16 &callbacks); |
|
17 } |
|
18 |
|
19 static CGFunctionRef |
|
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) |
|
28 { |
|
29 cairo_pattern_t *pat; |
|
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 |
|
34 }; |
|
35 |
|
36 @@ -791,16 +791,156 @@ CreateRepeatingGradientFunction (cairo_q |
|
37 return CGFunctionCreate (pat, |
|
38 1, |
|
39 input_value_range, |
|
40 4, |
|
41 output_value_ranges, |
|
42 &callbacks); |
|
43 } |
|
44 |
|
45 +static void |
|
46 +UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center, |
|
47 + double dr, double dx, double dy, |
|
48 + double x, double y) |
|
49 +{ |
|
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 |
|
52 + point (x,y). |
|
53 + |
|
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 |
|
57 + |
|
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 |
|
63 + |
|
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.) |
|
68 + Therefore |
|
69 + dx^2 + dy^2 < dr^2 |
|
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. |
|
76 + */ |
|
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; |
|
84 + |
|
85 + if (*max_t < t) { |
|
86 + *max_t = t; |
|
87 + } |
|
88 +} |
|
89 + |
|
90 +/* This must only be called when one of the circles properly contains the other */ |
|
91 +static CGFunctionRef |
|
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) |
|
96 +{ |
|
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 |
|
104 + }; |
|
105 + CGPoint *inner; |
|
106 + double *inner_radius; |
|
107 + CGPoint *outer; |
|
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 */ |
|
113 + double dr, dx, dy; |
|
114 + |
|
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); |
|
118 + |
|
119 + if (*start_radius < *end_radius) { |
|
120 + /* end circle contains start circle */ |
|
121 + inner = start; |
|
122 + outer = end; |
|
123 + inner_radius = start_radius; |
|
124 + outer_radius = end_radius; |
|
125 + } else { |
|
126 + /* start circle contains end circle */ |
|
127 + inner = end; |
|
128 + outer = start; |
|
129 + inner_radius = end_radius; |
|
130 + outer_radius = start_radius; |
|
131 + } |
|
132 + |
|
133 + dr = *outer_radius - *inner_radius; |
|
134 + dx = outer->x - inner->x; |
|
135 + dy = outer->y - inner->y; |
|
136 + |
|
137 + t_min = -(*inner_radius/dr); |
|
138 + inner->x += t_min*dx; |
|
139 + inner->y += t_min*dy; |
|
140 + *inner_radius = 0.; |
|
141 + |
|
142 + t_temp = 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 |
|
155 + OK. */ |
|
156 + t_temp += 1e-6; |
|
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; |
|
161 + |
|
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; |
|
167 + } else { |
|
168 + input_value_range[0] = -t_max; |
|
169 + input_value_range[1] = -t_min; |
|
170 + } |
|
171 + |
|
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 */ |
|
175 + return NULL; |
|
176 + |
|
177 + return CGFunctionCreate (pat, |
|
178 + 1, |
|
179 + input_value_range, |
|
180 + 4, |
|
181 + output_value_ranges, |
|
182 + &callbacks); |
|
183 +} |
|
184 + |
|
185 /* Obtain a CGImageRef from a #cairo_surface_t * */ |
|
186 |
|
187 static void |
|
188 DataProviderReleaseCallback (void *info, const void *data, size_t size) |
|
189 { |
|
190 cairo_surface_t *surface = (cairo_surface_t *) info; |
|
191 cairo_surface_destroy (surface); |
|
192 } |
|
193 @@ -1112,23 +1252,24 @@ _cairo_quartz_setup_linear_source (cairo |
|
194 rgb = CGColorSpaceCreateDeviceRGB(); |
|
195 |
|
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)); |
|
200 |
|
201 if (abspat->extend == CAIRO_EXTEND_NONE || |
|
202 - abspat->extend == CAIRO_EXTEND_PAD) |
|
203 + abspat->extend == CAIRO_EXTEND_PAD) |
|
204 { |
|
205 gradFunc = CreateGradientFunction (&lpat->base); |
|
206 } else { |
|
207 - gradFunc = CreateRepeatingGradientFunction (surface, |
|
208 - &lpat->base, |
|
209 - &start, &end, surface->sourceTransform); |
|
210 + gradFunc = CreateRepeatingLinearGradientFunction (surface, |
|
211 + &lpat->base, |
|
212 + &start, &end, |
|
213 + surface->sourceTransform); |
|
214 } |
|
215 |
|
216 surface->sourceShading = CGShadingCreateAxial (rgb, |
|
217 start, end, |
|
218 gradFunc, |
|
219 extend, extend); |
|
220 |
|
221 CGColorSpaceRelease(rgb); |
|
222 @@ -1142,52 +1283,68 @@ _cairo_quartz_setup_radial_source (cairo |
|
223 const cairo_radial_pattern_t *rpat) |
|
224 { |
|
225 const cairo_pattern_t *abspat = &rpat->base.base; |
|
226 cairo_matrix_t mat; |
|
227 CGPoint start, end; |
|
228 CGFunctionRef gradFunc; |
|
229 CGColorSpaceRef rgb; |
|
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); |
|
240 |
|
241 if (rpat->base.n_stops == 0) { |
|
242 CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); |
|
243 CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); |
|
244 return DO_SOLID; |
|
245 } |
|
246 |
|
247 - if (abspat->extend == CAIRO_EXTEND_REPEAT || |
|
248 - abspat->extend == CAIRO_EXTEND_REFLECT) |
|
249 - { |
|
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. |
|
261 */ |
|
262 - return _cairo_quartz_setup_fallback_source (surface, &rpat->base.base); |
|
263 + return _cairo_quartz_setup_fallback_source (surface, abspat); |
|
264 } |
|
265 |
|
266 mat = abspat->matrix; |
|
267 cairo_matrix_invert (&mat); |
|
268 _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); |
|
269 |
|
270 rgb = CGColorSpaceCreateDeviceRGB(); |
|
271 |
|
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); |
|
278 |
|
279 - gradFunc = CreateGradientFunction (&rpat->base); |
|
280 + if (abspat->extend == CAIRO_EXTEND_NONE || |
|
281 + abspat->extend == CAIRO_EXTEND_PAD) |
|
282 + { |
|
283 + gradFunc = CreateGradientFunction (&rpat->base); |
|
284 + } else { |
|
285 + gradFunc = CreateRepeatingRadialGradientFunction (surface, |
|
286 + &rpat->base, |
|
287 + &start, &r1, |
|
288 + &end, &r2); |
|
289 + } |
|
290 |
|
291 surface->sourceShading = CGShadingCreateRadial (rgb, |
|
292 start, |
|
293 - _cairo_fixed_to_double (rpat->r1), |
|
294 + r1, |
|
295 end, |
|
296 - _cairo_fixed_to_double (rpat->r2), |
|
297 + r2, |
|
298 gradFunc, |
|
299 extend, extend); |
|
300 |
|
301 CGColorSpaceRelease(rgb); |
|
302 CGFunctionRelease(gradFunc); |
|
303 |
|
304 return DO_SHADING; |
|
305 } |