Bug 545892 - Always pass WM_NCPAINT events to the default event procedure. r=bent...
[mozilla-central.git] / gfx / cairo / quartz-minimize-gradient-repeat.patch
blob9782bef11eb9cb85e97912bc4f364c8c50b2f53f
1 # HG changeset patch
2 # User Robert O'Callahan <robert@ocallahan.org>
3 # Date 1249558989 -43200
4 # Node ID 0bac4c903d2bb1d5c0d5426209001fc2a77cc105
5 # Parent 963b9451ad305924738d05d997a640698cd3af91
6 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
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 @@ -710,82 +710,100 @@ CreateGradientFunction (const cairo_grad
12 return CGFunctionCreate (pat,
14 input_value_range,
16 gradient_output_value_ranges,
17 &gradient_callbacks);
20 +static void
21 +UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, CGPoint *start,
22 + double dx, double dy,
23 + double x, double y)
25 + /* Compute a parameter t such that a line perpendicular to the (dx,dy)
26 + vector, passing through (start->x + dx*t, start->y + dy*t), also
27 + passes through (x,y).
29 + Let px = x - start->x, py = y - start->y.
30 + t is given by
31 + (px - dx*t)*dx + (py - dy*t)*dy = 0
33 + Solving for t we get
34 + numerator = dx*px + dy*py
35 + denominator = dx^2 + dy^2
36 + t = numerator/denominator
38 + In CreateRepeatingLinearGradientFunction we know the length of (dx,dy)
39 + is not zero. (This is checked in _cairo_quartz_setup_linear_source.)
40 + */
41 + double px = x - start->x;
42 + double py = y - start->y;
43 + double numerator = dx*px + dy*py;
44 + double denominator = dx*dx + dy*dy;
45 + double t = numerator/denominator;
47 + if (*min_t > t) {
48 + *min_t = t;
49 + }
50 + if (*max_t < t) {
51 + *max_t = t;
52 + }
55 static CGFunctionRef
56 CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
57 const cairo_gradient_pattern_t *gpat,
58 CGPoint *start, CGPoint *end,
59 - CGAffineTransform matrix)
60 + cairo_rectangle_int_t *extents)
62 cairo_pattern_t *pat;
63 float input_value_range[2];
64 + double t_min = 0.;
65 + double t_max = 0.;
66 + double dx = end->x - start->x;
67 + double dy = end->y - start->y;
68 + double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
70 - CGPoint mstart, mend;
71 + if (!extents) {
72 + extents = &surface->extents;
73 + }
74 + bounds_x1 = extents->x;
75 + bounds_y1 = extents->y;
76 + bounds_x2 = extents->x + extents->width;
77 + bounds_y2 = extents->y + extents->height;
78 + _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
79 + &bounds_x1, &bounds_y1,
80 + &bounds_x2, &bounds_y2,
81 + NULL);
83 - double dx, dy;
84 - int x_rep_start = 0, x_rep_end = 0;
85 - int y_rep_start = 0, y_rep_end = 0;
86 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
87 + bounds_x1, bounds_y1);
88 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
89 + bounds_x2, bounds_y1);
90 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
91 + bounds_x2, bounds_y2);
92 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, start, dx, dy,
93 + bounds_x1, bounds_y2);
95 - int rep_start, rep_end;
97 - // figure out how many times we'd need to repeat the gradient pattern
98 - // to cover the whole (transformed) surface area
99 - mstart = CGPointApplyAffineTransform (*start, matrix);
100 - mend = CGPointApplyAffineTransform (*end, matrix);
102 - dx = fabs (mend.x - mstart.x);
103 - dy = fabs (mend.y - mstart.y);
105 - if (dx > 1e-6) {
106 - x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx);
107 - x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx);
109 - if (mend.x < mstart.x) {
110 - int swap = x_rep_end;
111 - x_rep_end = x_rep_start;
112 - x_rep_start = swap;
116 - if (dy > 1e-6) {
117 - y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy);
118 - y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy);
120 - if (mend.y < mstart.y) {
121 - int swap = y_rep_end;
122 - y_rep_end = y_rep_start;
123 - y_rep_start = swap;
127 - rep_start = MAX(x_rep_start, y_rep_start);
128 - rep_end = MAX(x_rep_end, y_rep_end);
130 - // extend the line between start and end by rep_start times from the start
131 - // and rep_end times from the end
133 - dx = end->x - start->x;
134 - dy = end->y - start->y;
136 - start->x = start->x - dx * rep_start;
137 - start->y = start->y - dy * rep_start;
139 - end->x = end->x + dx * rep_end;
140 - end->y = end->y + dy * rep_end;
141 + /* Move t_min and t_max to the nearest usable integer to try to avoid
142 + subtle variations due to numerical instability, especially accidentally
143 + cutting off a pixel. Extending the gradient repetitions is always safe. */
144 + t_min = floor (t_min);
145 + t_max = ceil (t_max);
146 + end->x = start->x + dx*t_max;
147 + end->y = start->y + dy*t_max;
148 + start->x = start->x + dx*t_min;
149 + start->y = start->y + dy*t_min;
151 // set the input range for the function -- the function knows how to
152 // map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT.
153 - input_value_range[0] = 0.0 - 1.0 * rep_start;
154 - input_value_range[1] = 1.0 + 1.0 * rep_end;
155 + input_value_range[0] = t_min;
156 + input_value_range[1] = t_max;
158 if (_cairo_pattern_create_copy (&pat, &gpat->base))
159 /* quartz doesn't deal very well with malloc failing, so there's
160 * not much point in us trying either */
161 return NULL;
163 return CGFunctionCreate (pat,
165 @@ -840,35 +858,43 @@ UpdateRadialParameterToIncludePoint(doub
169 /* This must only be called when one of the circles properly contains the other */
170 static CGFunctionRef
171 CreateRepeatingRadialGradientFunction (cairo_quartz_surface_t *surface,
172 const cairo_gradient_pattern_t *gpat,
173 CGPoint *start, double *start_radius,
174 - CGPoint *end, double *end_radius)
175 + CGPoint *end, double *end_radius,
176 + cairo_rectangle_int_t *extents)
178 - CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
179 - CGAffineTransform transform;
180 cairo_pattern_t *pat;
181 float input_value_range[2];
182 CGPoint *inner;
183 double *inner_radius;
184 CGPoint *outer;
185 double *outer_radius;
186 /* minimum and maximum t-parameter values that will make our gradient
187 cover the clipBox */
188 double t_min, t_max, t_temp;
189 /* outer minus inner */
190 double dr, dx, dy;
191 + double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
193 - _cairo_quartz_cairo_matrix_to_quartz (&gpat->base.matrix, &transform);
194 - /* clip is in cairo device coordinates; get it into cairo user space */
195 - clip = CGRectApplyAffineTransform (clip, transform);
196 + if (!extents) {
197 + extents = &surface->extents;
199 + bounds_x1 = extents->x;
200 + bounds_y1 = extents->y;
201 + bounds_x2 = extents->x + extents->width;
202 + bounds_y2 = extents->y + extents->height;
203 + _cairo_matrix_transform_bounding_box (&gpat->base.matrix,
204 + &bounds_x1, &bounds_y1,
205 + &bounds_x2, &bounds_y2,
206 + NULL);
208 if (*start_radius < *end_radius) {
209 /* end circle contains start circle */
210 inner = start;
211 outer = end;
212 inner_radius = start_radius;
213 outer_radius = end_radius;
214 } else {
215 @@ -878,36 +904,37 @@ CreateRepeatingRadialGradientFunction (c
216 inner_radius = end_radius;
217 outer_radius = start_radius;
220 dr = *outer_radius - *inner_radius;
221 dx = outer->x - inner->x;
222 dy = outer->y - inner->y;
224 + /* We can't round or fudge t_min here, it has to be as accurate as possible. */
225 t_min = -(*inner_radius/dr);
226 inner->x += t_min*dx;
227 inner->y += t_min*dy;
228 *inner_radius = 0.;
230 t_temp = 0.;
231 UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
232 - clip.origin.x, clip.origin.y);
233 + bounds_x1, bounds_y1);
234 UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
235 - clip.origin.x + clip.size.width, clip.origin.y);
236 + bounds_x2, bounds_y1);
237 UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
238 - clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
239 + bounds_x2, bounds_y2);
240 UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
241 - clip.origin.x, clip.origin.y + clip.size.height);
242 + bounds_x1, bounds_y2);
243 /* UpdateRadialParameterToIncludePoint assumes t=0 means radius 0.
244 But for the parameter values we use with Quartz, t_min means radius 0.
245 - Also, add a small fudge factor to avoid rounding issues. Since the
246 - circles are alway expanding and containing the earlier circles, this is
247 - OK. */
248 - t_temp += 1e-6;
249 + Since the circles are alway expanding and contain the earlier circles,
250 + it's safe to extend t_max/t_temp as much as we want, so round t_temp up
251 + to the nearest integer. This may help us give stable results. */
252 + t_temp = ceil (t_temp);
253 t_max = t_min + t_temp;
254 outer->x = inner->x + t_temp*dx;
255 outer->y = inner->y + t_temp*dy;
256 *outer_radius = t_temp*dr;
258 /* set the input range for the function -- the function knows how to
259 map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT. */
260 if (*start_radius < *end_radius) {
261 @@ -1218,33 +1245,57 @@ _cairo_quartz_setup_fallback_source (cai
262 surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h);
263 surface->sourceImage = img;
264 surface->sourceImageSurface = fallback;
265 surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0);
267 return DO_IMAGE;
271 +Quartz does not support repeating radients. We handle repeating gradients
272 +by manually extending the gradient and repeating color stops. We need to
273 +minimize the number of repetitions since Quartz seems to sample our color
274 +function across the entire range, even if part of that range is not needed
275 +for the visible area of the gradient, and it samples with some fixed resolution,
276 +so if the gradient range is too large it samples with very low resolution and
277 +the gradient is very coarse. CreateRepeatingLinearGradientFunction and
278 +CreateRepeatingRadialGradientFunction compute the number of repetitions needed
279 +based on the extents of the object (the clip region cannot be used here since
280 +we don't want the rasterization of the entire gradient to depend on the
281 +clip region).
283 static cairo_quartz_action_t
284 _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface,
285 - const cairo_linear_pattern_t *lpat)
286 + const cairo_linear_pattern_t *lpat,
287 + cairo_rectangle_int_t *extents)
289 const cairo_pattern_t *abspat = &lpat->base.base;
290 cairo_matrix_t mat;
291 CGPoint start, end;
292 CGFunctionRef gradFunc;
293 CGColorSpaceRef rgb;
294 bool extend = abspat->extend == CAIRO_EXTEND_PAD;
296 if (lpat->base.n_stops == 0) {
297 CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
298 CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
299 return DO_SOLID;
302 + if (lpat->p1.x == lpat->p2.x &&
303 + lpat->p1.y == lpat->p2.y) {
304 + /* Quartz handles cases where the vector has no length very
305 + * differently from pixman.
306 + * Whatever the correct behaviour is, let's at least have only pixman's
307 + * implementation to worry about.
308 + */
309 + return _cairo_quartz_setup_fallback_source (surface, abspat);
312 mat = abspat->matrix;
313 cairo_matrix_invert (&mat);
314 _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
316 rgb = CGColorSpaceCreateDeviceRGB();
318 start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
319 _cairo_fixed_to_double (lpat->p1.y));
320 @@ -1254,33 +1305,34 @@ _cairo_quartz_setup_linear_source (cairo
321 if (abspat->extend == CAIRO_EXTEND_NONE ||
322 abspat->extend == CAIRO_EXTEND_PAD)
324 gradFunc = CreateGradientFunction (&lpat->base);
325 } else {
326 gradFunc = CreateRepeatingLinearGradientFunction (surface,
327 &lpat->base,
328 &start, &end,
329 - surface->sourceTransform);
330 + extents);
333 surface->sourceShading = CGShadingCreateAxial (rgb,
334 start, end,
335 gradFunc,
336 extend, extend);
338 CGColorSpaceRelease(rgb);
339 CGFunctionRelease(gradFunc);
341 return DO_SHADING;
344 static cairo_quartz_action_t
345 _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface,
346 - const cairo_radial_pattern_t *rpat)
347 + const cairo_radial_pattern_t *rpat,
348 + cairo_rectangle_int_t *extents)
350 const cairo_pattern_t *abspat = &rpat->base.base;
351 cairo_matrix_t mat;
352 CGPoint start, end;
353 CGFunctionRef gradFunc;
354 CGColorSpaceRef rgb;
355 bool extend = abspat->extend == CAIRO_EXTEND_PAD;
356 double c1x = _cairo_fixed_to_double (rpat->c1.x);
357 @@ -1322,17 +1374,18 @@ _cairo_quartz_setup_radial_source (cairo
358 if (abspat->extend == CAIRO_EXTEND_NONE ||
359 abspat->extend == CAIRO_EXTEND_PAD)
361 gradFunc = CreateGradientFunction (&rpat->base);
362 } else {
363 gradFunc = CreateRepeatingRadialGradientFunction (surface,
364 &rpat->base,
365 &start, &r1,
366 - &end, &r2);
367 + &end, &r2,
368 + extents);
371 surface->sourceShading = CGShadingCreateRadial (rgb,
372 start,
374 end,
376 gradFunc,
377 @@ -1341,17 +1394,18 @@ _cairo_quartz_setup_radial_source (cairo
378 CGColorSpaceRelease(rgb);
379 CGFunctionRelease(gradFunc);
381 return DO_SHADING;
384 static cairo_quartz_action_t
385 _cairo_quartz_setup_source (cairo_quartz_surface_t *surface,
386 - const cairo_pattern_t *source)
387 + const cairo_pattern_t *source,
388 + cairo_rectangle_int_t *extents)
390 assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
392 surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext);
393 CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter));
395 if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
396 cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
397 @@ -1367,24 +1421,22 @@ _cairo_quartz_setup_source (cairo_quartz
398 solid->color.blue,
399 solid->color.alpha);
401 return DO_SOLID;
404 if (source->type == CAIRO_PATTERN_TYPE_LINEAR) {
405 const cairo_linear_pattern_t *lpat = (const cairo_linear_pattern_t *)source;
406 - return _cairo_quartz_setup_linear_source (surface, lpat);
408 + return _cairo_quartz_setup_linear_source (surface, lpat, extents);
411 if (source->type == CAIRO_PATTERN_TYPE_RADIAL) {
412 const cairo_radial_pattern_t *rpat = (const cairo_radial_pattern_t *)source;
413 - return _cairo_quartz_setup_radial_source (surface, rpat);
415 + return _cairo_quartz_setup_radial_source (surface, rpat, extents);
418 if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
419 (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
421 const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source;
422 cairo_surface_t *pat_surf = spat->surface;
423 CGImageRef img;
424 @@ -1852,17 +1904,17 @@ _cairo_quartz_surface_paint (void *abstr
425 if (IS_EMPTY(surface))
426 return CAIRO_STATUS_SUCCESS;
428 if (op == CAIRO_OPERATOR_DEST)
429 return CAIRO_STATUS_SUCCESS;
431 CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
433 - action = _cairo_quartz_setup_source (surface, source);
434 + action = _cairo_quartz_setup_source (surface, source, NULL);
436 if (action == DO_SOLID || action == DO_PATTERN) {
437 CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
438 surface->extents.y,
439 surface->extents.width,
440 surface->extents.height));
441 } else if (action == DO_SHADING) {
442 CGContextSaveGState (surface->cgContext);
443 @@ -1886,16 +1938,35 @@ _cairo_quartz_surface_paint (void *abstr
446 _cairo_quartz_teardown_source (surface, source);
448 ND((stderr, "-- paint\n"));
449 return rv;
452 +static cairo_bool_t
453 +_cairo_quartz_source_needs_extents (const cairo_pattern_t *source)
455 + /* For repeating gradients we need to manually extend the gradient and
456 + repeat stops, since Quartz doesn't support repeating gradients natively.
457 + We need to minimze the number of repeated stops, and since rasterization
458 + depends on the number of repetitions we use (even if some of the
459 + repetitions go beyond the extents of the object or outside the clip
460 + region), it's important to use the same number of repetitions when
461 + rendering an object no matter what the clip region is. So the
462 + computation of the repetition count cannot depended on the clip region,
463 + and should only depend on the object extents, so we need to compute
464 + the object extents for repeating gradients. */
465 + return (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
466 + source->type == CAIRO_PATTERN_TYPE_RADIAL) &&
467 + (source->extend == CAIRO_EXTEND_REPEAT ||
468 + source->extend == CAIRO_EXTEND_REFLECT);
471 static cairo_int_status_t
472 _cairo_quartz_surface_fill (void *abstract_surface,
473 cairo_operator_t op,
474 const cairo_pattern_t *source,
475 cairo_path_fixed_t *path,
476 cairo_fill_rule_t fill_rule,
477 double tolerance,
478 cairo_antialias_t antialias,
479 @@ -1926,17 +1997,27 @@ _cairo_quartz_surface_fill (void *abstra
480 return CAIRO_STATUS_SUCCESS;
483 CGContextSaveGState (surface->cgContext);
485 CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
486 CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
488 - action = _cairo_quartz_setup_source (surface, source);
489 + if (_cairo_quartz_source_needs_extents (source))
491 + /* We don't need precise extents since these are only used to
492 + compute the number of gradient reptitions needed to cover the
493 + object. */
494 + cairo_rectangle_int_t path_extents;
495 + _cairo_path_fixed_approximate_fill_extents (path, &path_extents);
496 + action = _cairo_quartz_setup_source (surface, source, &path_extents);
497 + } else {
498 + action = _cairo_quartz_setup_source (surface, source, NULL);
501 CGContextBeginPath (surface->cgContext);
503 stroke.cgContext = surface->cgContext;
504 stroke.ctm_inverse = NULL;
505 rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
506 if (rv)
507 goto BAIL;
508 @@ -2059,17 +2140,24 @@ _cairo_quartz_surface_stroke (void *abst
510 CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes);
511 if (fdash != sdash)
512 free (fdash);
515 CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
517 - action = _cairo_quartz_setup_source (surface, source);
518 + if (_cairo_quartz_source_needs_extents (source))
520 + cairo_rectangle_int_t path_extents;
521 + _cairo_path_fixed_approximate_stroke_extents (path, style, ctm, &path_extents);
522 + action = _cairo_quartz_setup_source (surface, source, &path_extents);
523 + } else {
524 + action = _cairo_quartz_setup_source (surface, source, NULL);
527 CGContextBeginPath (surface->cgContext);
529 stroke.cgContext = surface->cgContext;
530 stroke.ctm_inverse = ctm_inverse;
531 rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
532 if (rv)
533 goto BAIL;
534 @@ -2180,17 +2268,26 @@ _cairo_quartz_surface_show_glyphs (void
535 if (op == CAIRO_OPERATOR_DEST)
536 return CAIRO_STATUS_SUCCESS;
538 if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ)
539 return CAIRO_INT_STATUS_UNSUPPORTED;
541 CGContextSaveGState (surface->cgContext);
543 - action = _cairo_quartz_setup_source (surface, source);
544 + if (_cairo_quartz_source_needs_extents (source))
546 + cairo_rectangle_int_t glyph_extents;
547 + _cairo_scaled_font_glyph_device_extents (scaled_font, glyphs, num_glyphs,
548 + &glyph_extents);
549 + action = _cairo_quartz_setup_source (surface, source, &glyph_extents);
550 + } else {
551 + action = _cairo_quartz_setup_source (surface, source, NULL);
554 if (action == DO_SOLID || action == DO_PATTERN) {
555 CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
556 } else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) {
557 CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
558 isClipping = TRUE;
559 } else {
560 if (action != DO_NOTHING)
561 rv = CAIRO_INT_STATUS_UNSUPPORTED;