Add cairo patches for Quartz gradient fixes

--HG--
extra : rebase_source : 7b50d07ec411410c7fda7b8fba423610018fd6cc
This commit is contained in:
Robert O'Callahan 2009-08-26 10:05:44 -07:00
parent 7f9931aed9
commit fef577d20c
5 changed files with 1078 additions and 0 deletions

View File

@ -54,6 +54,14 @@ zombie-face.patch: bug 486974; leak and possible crash with @font-face{src:url()
win32-raster.patch: bug 498689; use scanline rasterizer on win32
quartz-falback.patch: try to fix Quartz fallback-to-pixman path; possiby incorrect and obsoleted by Andrea Canciani patch
quartz-repeating-radial-gradients.patch: use Quartz to render repeating radial gradients instead of falling back
quartz-const-globals.patch: make some Quartz color function data const globals instead of local variables
quartz-minimze-gradient-repeat.patch: reduce the number of gradient stop repetitions we use, to improve quality of Quartz's gradient rendering
==== pixman patches ====
pixman-neon.patch: add ARM NEON optimized compositing functions

View File

@ -0,0 +1,134 @@
# HG changeset patch
# User Robert O'Callahan <robert@ocallahan.org>
# Date 1249558626 -43200
# Node ID 963b9451ad305924738d05d997a640698cd3af91
# Parent e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde
Bug 508730. Clean up Quartz gradient code by moving some local variables to static const globals. 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
@@ -684,51 +684,50 @@ ComputeGradientValue (void *info, const
grad->stops[i-1].color.blue * ap +
grad->stops[i].color.blue * bp;
out[3] =
grad->stops[i-1].color.alpha * ap +
grad->stops[i].color.alpha * bp;
}
}
+static const float gradient_output_value_ranges[8] = {
+ 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f
+};
+static const CGFunctionCallbacks gradient_callbacks = {
+ 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+};
+
static CGFunctionRef
CreateGradientFunction (const cairo_gradient_pattern_t *gpat)
{
cairo_pattern_t *pat;
float input_value_range[2] = { 0.f, 1.f };
- float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
- CGFunctionCallbacks callbacks = {
- 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
- };
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,
input_value_range,
4,
- output_value_ranges,
- &callbacks);
+ gradient_output_value_ranges,
+ &gradient_callbacks);
}
static CGFunctionRef
CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
const cairo_gradient_pattern_t *gpat,
CGPoint *start, CGPoint *end,
CGAffineTransform matrix)
{
cairo_pattern_t *pat;
float input_value_range[2];
- float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
- CGFunctionCallbacks callbacks = {
- 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
- };
CGPoint mstart, mend;
double dx, dy;
int x_rep_start = 0, x_rep_end = 0;
int y_rep_start = 0, y_rep_end = 0;
int rep_start, rep_end;
@@ -787,18 +786,18 @@ CreateRepeatingLinearGradientFunction (c
/* 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,
input_value_range,
4,
- output_value_ranges,
- &callbacks);
+ gradient_output_value_ranges,
+ &gradient_callbacks);
}
static void
UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center,
double dr, double dx, double dy,
double x, double y)
{
/* Compute a parameter t such that a circle centered at
@@ -847,20 +846,16 @@ CreateRepeatingRadialGradientFunction (c
const cairo_gradient_pattern_t *gpat,
CGPoint *start, double *start_radius,
CGPoint *end, double *end_radius)
{
CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
CGAffineTransform transform;
cairo_pattern_t *pat;
float input_value_range[2];
- float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
- CGFunctionCallbacks callbacks = {
- 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
- };
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 */
@@ -927,18 +922,18 @@ CreateRepeatingRadialGradientFunction (c
/* 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,
input_value_range,
4,
- output_value_ranges,
- &callbacks);
+ gradient_output_value_ranges,
+ &gradient_callbacks);
}
/* Obtain a CGImageRef from a #cairo_surface_t * */
static void
DataProviderReleaseCallback (void *info, const void *data, size_t size)
{
cairo_surface_t *surface = (cairo_surface_t *) info;

View File

@ -0,0 +1,70 @@
# HG changeset patch
# User Robert O'Callahan <robert@ocallahan.org>
# Date 1250204857 -43200
# Node ID cc6bebbd93bb9d8606fe06b997f890acc17996fb
# Parent caea8b548962f0df38e8e9032e9f57ef0fd099ec
Bug 507939 - Remove erroneous clip rect fixup which caused repainting errors with repeating radial gradients on Mac. 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
@@ -1033,38 +1033,29 @@ typedef enum {
DO_TILED_IMAGE
} cairo_quartz_action_t;
static cairo_quartz_action_t
_cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface,
const cairo_pattern_t *source)
{
CGRect clipBox = CGContextGetClipBoundingBox (surface->cgContext);
- CGAffineTransform ctm;
double x0, y0, w, h;
cairo_surface_t *fallback;
cairo_t *fallback_cr;
CGImageRef img;
cairo_pattern_t *source_copy;
cairo_status_t status;
if (clipBox.size.width == 0.0f ||
clipBox.size.height == 0.0f)
return DO_NOTHING;
- // the clipBox is in userspace, so:
- ctm = CGContextGetCTM (surface->cgContext);
- ctm = CGAffineTransformInvert (ctm);
- clipBox = CGRectApplyAffineTransform (clipBox, ctm);
-
- // get the Y flip right -- the CTM will always have a Y flip in place
- clipBox.origin.y = surface->extents.height - (clipBox.origin.y + clipBox.size.height);
-
x0 = floor(clipBox.origin.x);
y0 = floor(clipBox.origin.y);
w = ceil(clipBox.origin.x + clipBox.size.width) - x0;
h = ceil(clipBox.origin.y + clipBox.size.height) - y0;
/* Create a temporary the size of the clip surface, and position
* it so that the device origin coincides with the original surface */
fallback = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, (int) w, (int) h);
@@ -1717,18 +1708,20 @@ _cairo_quartz_surface_paint (void *abstr
action = _cairo_quartz_setup_source (surface, source);
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);
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
CGContextDrawShading (surface->cgContext, surface->sourceShading);
+ CGContextRestoreGState (surface->cgContext);
} else if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
CGContextSaveGState (surface->cgContext);
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
CGContextScaleCTM (surface->cgContext, 1, -1);
if (action == DO_IMAGE)

View File

@ -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;

View File

@ -0,0 +1,305 @@
# HG changeset patch
# User Robert O'Callahan <robert@ocallahan.org>
# Date 1249558156 -43200
# Node ID e564f3ab4ea6e3b5dd9c4e9e6042d3a84c229dde
# Parent 6ef9993a30bf2f983c9d64d7441d2e3b6b935de1
Bug 508227. Don't fallback to Quartz for repeating radial gradients. 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
@@ -708,20 +708,20 @@ CreateGradientFunction (const cairo_grad
1,
input_value_range,
4,
output_value_ranges,
&callbacks);
}
static CGFunctionRef
-CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface,
- const cairo_gradient_pattern_t *gpat,
- CGPoint *start, CGPoint *end,
- CGAffineTransform matrix)
+CreateRepeatingLinearGradientFunction (cairo_quartz_surface_t *surface,
+ const cairo_gradient_pattern_t *gpat,
+ CGPoint *start, CGPoint *end,
+ CGAffineTransform matrix)
{
cairo_pattern_t *pat;
float input_value_range[2];
float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
CGFunctionCallbacks callbacks = {
0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
};
@@ -791,16 +791,156 @@ CreateRepeatingGradientFunction (cairo_q
return CGFunctionCreate (pat,
1,
input_value_range,
4,
output_value_ranges,
&callbacks);
}
+static void
+UpdateRadialParameterToIncludePoint(double *max_t, CGPoint *center,
+ double dr, double dx, double dy,
+ double x, double y)
+{
+ /* Compute a parameter t such that a circle centered at
+ (center->x + dx*t, center->y + dy*t) with radius dr*t contains the
+ point (x,y).
+
+ Let px = x - center->x, py = y - center->y.
+ Parameter values for which t is on the circle are given by
+ (px - dx*t)^2 + (py - dy*t)^2 = (t*dr)^2
+
+ Solving for t using the quadratic formula, and simplifying, we get
+ numerator = dx*px + dy*py +-
+ sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
+ denominator = dx^2 + dy^2 - dr^2
+ t = numerator/denominator
+
+ In CreateRepeatingRadialGradientFunction we know the outer circle
+ contains the inner circle. Therefore the distance between the circle
+ centers plus the radius of the inner circle is less than the radius of
+ the outer circle. (This is checked in _cairo_quartz_setup_radial_source.)
+ Therefore
+ dx^2 + dy^2 < dr^2
+ So the denominator is negative and the larger solution for t is given by
+ numerator = dx*px + dy*py -
+ sqrt( dr^2*(px^2 + py^2) - (dx*py - dy*px)^2 )
+ denominator = dx^2 + dy^2 - dr^2
+ t = numerator/denominator
+ dx^2 + dy^2 < dr^2 also ensures that the operand of sqrt is positive.
+ */
+ double px = x - center->x;
+ double py = y - center->y;
+ double dx_py_minus_dy_px = dx*py - dy*px;
+ double numerator = dx*px + dy*py -
+ sqrt (dr*dr*(px*px + py*py) - dx_py_minus_dy_px*dx_py_minus_dy_px);
+ double denominator = dx*dx + dy*dy - dr*dr;
+ double t = numerator/denominator;
+
+ if (*max_t < t) {
+ *max_t = t;
+ }
+}
+
+/* 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)
+{
+ CGRect clip = CGContextGetClipBoundingBox (surface->cgContext);
+ CGAffineTransform transform;
+ cairo_pattern_t *pat;
+ float input_value_range[2];
+ float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
+ CGFunctionCallbacks callbacks = {
+ 0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
+ };
+ 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;
+
+ _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 (*start_radius < *end_radius) {
+ /* end circle contains start circle */
+ inner = start;
+ outer = end;
+ inner_radius = start_radius;
+ outer_radius = end_radius;
+ } else {
+ /* start circle contains end circle */
+ inner = end;
+ outer = start;
+ inner_radius = end_radius;
+ outer_radius = start_radius;
+ }
+
+ dr = *outer_radius - *inner_radius;
+ dx = outer->x - inner->x;
+ dy = outer->y - inner->y;
+
+ 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);
+ UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+ clip.origin.x + clip.size.width, clip.origin.y);
+ UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+ clip.origin.x + clip.size.width, clip.origin.y + clip.size.height);
+ UpdateRadialParameterToIncludePoint(&t_temp, inner, dr, dx, dy,
+ clip.origin.x, clip.origin.y + clip.size.height);
+ /* 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;
+ 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) {
+ input_value_range[0] = t_min;
+ input_value_range[1] = t_max;
+ } else {
+ input_value_range[0] = -t_max;
+ input_value_range[1] = -t_min;
+ }
+
+ 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,
+ input_value_range,
+ 4,
+ output_value_ranges,
+ &callbacks);
+}
+
/* Obtain a CGImageRef from a #cairo_surface_t * */
static void
DataProviderReleaseCallback (void *info, const void *data, size_t size)
{
cairo_surface_t *surface = (cairo_surface_t *) info;
cairo_surface_destroy (surface);
}
@@ -1112,23 +1252,24 @@ _cairo_quartz_setup_linear_source (cairo
rgb = CGColorSpaceCreateDeviceRGB();
start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
_cairo_fixed_to_double (lpat->p1.y));
end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x),
_cairo_fixed_to_double (lpat->p2.y));
if (abspat->extend == CAIRO_EXTEND_NONE ||
- abspat->extend == CAIRO_EXTEND_PAD)
+ abspat->extend == CAIRO_EXTEND_PAD)
{
gradFunc = CreateGradientFunction (&lpat->base);
} else {
- gradFunc = CreateRepeatingGradientFunction (surface,
- &lpat->base,
- &start, &end, surface->sourceTransform);
+ gradFunc = CreateRepeatingLinearGradientFunction (surface,
+ &lpat->base,
+ &start, &end,
+ surface->sourceTransform);
}
surface->sourceShading = CGShadingCreateAxial (rgb,
start, end,
gradFunc,
extend, extend);
CGColorSpaceRelease(rgb);
@@ -1142,52 +1283,68 @@ _cairo_quartz_setup_radial_source (cairo
const cairo_radial_pattern_t *rpat)
{
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);
+ double c1y = _cairo_fixed_to_double (rpat->c1.y);
+ double c2x = _cairo_fixed_to_double (rpat->c2.x);
+ double c2y = _cairo_fixed_to_double (rpat->c2.y);
+ double r1 = _cairo_fixed_to_double (rpat->r1);
+ double r2 = _cairo_fixed_to_double (rpat->r2);
+ double dx = c1x - c2x;
+ double dy = c1y - c2y;
+ double centerDistance = sqrt (dx*dx + dy*dy);
if (rpat->base.n_stops == 0) {
CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
return DO_SOLID;
}
- if (abspat->extend == CAIRO_EXTEND_REPEAT ||
- abspat->extend == CAIRO_EXTEND_REFLECT)
- {
- /* I started trying to map these to Quartz, but it's much harder
- * then the linear case (I think it would involve doing multiple
- * Radial shadings). So, instead, let's just render an image
- * for pixman to draw the shading into, and use that.
+ if (r2 <= centerDistance + r1 + 1e-6 && /* circle 2 doesn't contain circle 1 */
+ r1 <= centerDistance + r2 + 1e-6) { /* circle 1 doesn't contain circle 2 */
+ /* Quartz handles cases where neither circle contains the other very
+ * differently from pixman.
+ * Whatever the correct behaviour is, let's at least have only pixman's
+ * implementation to worry about.
+ * Note that this also catches the cases where r1 == r2.
*/
- return _cairo_quartz_setup_fallback_source (surface, &rpat->base.base);
+ 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 (rpat->c1.x),
- _cairo_fixed_to_double (rpat->c1.y));
- end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x),
- _cairo_fixed_to_double (rpat->c2.y));
+ start = CGPointMake (c1x, c1y);
+ end = CGPointMake (c2x, c2y);
- gradFunc = CreateGradientFunction (&rpat->base);
+ 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);
+ }
surface->sourceShading = CGShadingCreateRadial (rgb,
start,
- _cairo_fixed_to_double (rpat->r1),
+ r1,
end,
- _cairo_fixed_to_double (rpat->r2),
+ r2,
gradFunc,
extend, extend);
CGColorSpaceRelease(rgb);
CGFunctionRelease(gradFunc);
return DO_SHADING;
}