summaryrefslogtreecommitdiff
path: root/src/cairo-quartz-surface.c
diff options
context:
space:
mode:
authorAndrea Canciani <ranma42@gmail.com>2010-11-17 22:07:09 +0100
committerAndrea Canciani <ranma42@gmail.com>2010-12-13 09:46:09 +0100
commitca7f141dd7931041887dc96a542c2a47da25e12f (patch)
tree107a2db570f43af8ae3e7d87f78a535658fa2be8 /src/cairo-quartz-surface.c
parent341e5b3246f785a4791606ea62873cfb180efae6 (diff)
quartz: Unify gradient construction and fix radial gradients.
Share code between linear and radial gradients, using _cairo_gradient_pattern_box_to_parameter() instead of open coding the parameter range computation. As a side effect this fixes parameter range computation for radial gradients, because the previous code assumed that the focal point was inside the circles. Reviewed-by: M Joonas Pihlaja <jpihlaja@cc.helsinki.fi>
Diffstat (limited to 'src/cairo-quartz-surface.c')
-rw-r--r--src/cairo-quartz-surface.c400
1 files changed, 68 insertions, 332 deletions
diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c
index a3408653..4a22221c 100644
--- a/src/cairo-quartz-surface.c
+++ b/src/cairo-quartz-surface.c
@@ -811,258 +811,45 @@ static const cairo_quartz_float_t gradient_output_value_ranges[8] = {
static const CGFunctionCallbacks gradient_callbacks = {
0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
};
-/* Quartz will clamp input values to the input range.
-
- Our stops are all in the range 0.0 to 1.0. However, the color before the
- beginning of the gradient line is obtained by Quartz computing a negative
- position on the gradient line, clamping it to the input range we specified
- for our color function, and then calling our color function (actually it
- pre-samples the color function into an array, but that doesn't matter just
- here). Therefore if we set the lower bound to 0.0, a negative position
- on the gradient line will pass 0.0 to ComputeGradientValue, which will
- select the last color stop with position 0, although it should select
- the first color stop (this matters when there are multiple color stops with
- position 0).
-
- Therefore we pass a small negative number as the lower bound of the input
- range, so this value gets passed into ComputeGradientValue, which will
- return the color of the first stop. The number should be small because
- as far as I can tell, Quartz pre-samples the entire input range of the color
- function into an array of fixed size, so if the input range is larger
- than needed, the resolution of the gradient will be unnecessarily low.
-*/
-static const cairo_quartz_float_t nonrepeating_gradient_input_value_range[2] = { 0., 1. };
-
-static CGFunctionRef
-CreateGradientFunction (const cairo_gradient_pattern_t *gpat)
-{
- cairo_pattern_t *pat;
-
- 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,
- nonrepeating_gradient_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 (const cairo_gradient_pattern_t *gpat,
- CGPoint *start, CGPoint *end,
- const cairo_rectangle_int_t *extents)
+_cairo_quartz_create_gradient_function (const cairo_gradient_pattern_t *gradient,
+ const cairo_rectangle_int_t *extents,
+ cairo_circle_double_t *start,
+ cairo_circle_double_t *end)
{
cairo_pattern_t *pat;
cairo_quartz_float_t 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;
-
- 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);
-
- 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);
-
- 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] = 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,
- input_value_range,
- 4,
- 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
- (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 (const cairo_gradient_pattern_t *gpat,
- CGPoint *start, double *start_radius,
- CGPoint *end, double *end_radius,
- const cairo_rectangle_int_t *extents)
-{
- cairo_pattern_t *pat;
- cairo_quartz_float_t 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;
-
- 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;
+ if (gradient->base.extend != CAIRO_EXTEND_NONE) {
+ double bounds_x1, bounds_x2, bounds_y1, bounds_y2;
+ double t[2];
+
+ 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 (&gradient->base.matrix,
+ &bounds_x1, &bounds_y1,
+ &bounds_x2, &bounds_y2,
+ NULL);
+
+ _cairo_gradient_pattern_box_to_parameter (gradient, bounds_x1, bounds_y1,
+ bounds_x2, bounds_y2, 1, t);
+
+ /* set the input range for the function -- the function knows how
+ to map values outside of 0.0 .. 1.0 to the correct color */
+ input_value_range[0] = t[0];
+ input_value_range[1] = t[1];
} 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;
-
- /* 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,
- bounds_x1, bounds_y1);
- UpdateRadialParameterToIncludePoint (&t_temp, inner, dr, dx, dy,
- bounds_x2, bounds_y1);
- UpdateRadialParameterToIncludePoint (&t_temp, inner, dr, dx, dy,
- bounds_x2, bounds_y2);
- UpdateRadialParameterToIncludePoint (&t_temp, inner, dr, dx, dy,
- 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. */
- 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] = 1 - t_max;
- input_value_range[1] = 1 - t_min;
+ input_value_range[0] = 0;
+ input_value_range[1] = 1;
}
- if (_cairo_pattern_create_copy (&pat, &gpat->base))
+ _cairo_gradient_pattern_interpolate (gradient, input_value_range[0], start);
+ _cairo_gradient_pattern_interpolate (gradient, input_value_range[1], end);
+
+ if (_cairo_pattern_create_copy (&pat, &gradient->base))
/* quartz doesn't deal very well with malloc failing, so there's
* not much point in us trying either */
return NULL;
@@ -1324,100 +1111,52 @@ 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).
+the gradient is very coarse. _cairo_quartz_create_gradient_function computes
+the number of repetitions needed based on the extents.
*/
static cairo_int_status_t
-_cairo_quartz_setup_linear_source (cairo_quartz_drawing_state_t *state,
- const cairo_linear_pattern_t *lpat,
- const cairo_rectangle_int_t *extents)
+_cairo_quartz_setup_gradient_source (cairo_quartz_drawing_state_t *state,
+ const cairo_gradient_pattern_t *gradient,
+ const cairo_rectangle_int_t *extents)
{
- const cairo_pattern_t *abspat = &lpat->base.base;
cairo_matrix_t mat;
- CGPoint start, end;
+ cairo_circle_double_t start, end;
CGFunctionRef gradFunc;
CGColorSpaceRef rgb;
- bool extend = abspat->extend != CAIRO_EXTEND_NONE;
+ bool extend = gradient->base.extend != CAIRO_EXTEND_NONE;
- assert (lpat->base.n_stops > 0);
+ assert (gradient->n_stops > 0);
- mat = abspat->matrix;
+ mat = gradient->base.matrix;
cairo_matrix_invert (&mat);
_cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform);
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 (!extend)
- gradFunc = CreateGradientFunction (&lpat->base);
- else
- gradFunc = CreateRepeatingLinearGradientFunction (&lpat->base,
- &start, &end,
- extents);
-
- state->shading = CGShadingCreateAxial (rgb,
- start, end,
- gradFunc,
- extend, extend);
-
- CGColorSpaceRelease (rgb);
- CGFunctionRelease (gradFunc);
-
- state->action = DO_SHADING;
- return CAIRO_STATUS_SUCCESS;
-}
-
-static cairo_int_status_t
-_cairo_quartz_setup_radial_source (cairo_quartz_drawing_state_t *state,
- const cairo_radial_pattern_t *rpat,
- const 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_NONE;
- 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);
-
- assert (rpat->base.n_stops > 0);
-
- mat = abspat->matrix;
- cairo_matrix_invert (&mat);
- _cairo_quartz_cairo_matrix_to_quartz (&mat, &state->transform);
-
- rgb = CGColorSpaceCreateDeviceRGB ();
-
- start = CGPointMake (c1x, c1y);
- end = CGPointMake (c2x, c2y);
-
- if (!extend)
- gradFunc = CreateGradientFunction (&rpat->base);
- else
- gradFunc = CreateRepeatingRadialGradientFunction (&rpat->base,
- &start, &r1,
- &end, &r2,
- extents);
-
- state->shading = CGShadingCreateRadial (rgb,
- start,
- r1,
- end,
- r2,
- gradFunc,
- extend, extend);
+ gradFunc = _cairo_quartz_create_gradient_function (gradient,
+ extents,
+ &start,
+ &end);
+
+ if (gradient->base.type == CAIRO_PATTERN_TYPE_LINEAR) {
+ state->shading = CGShadingCreateAxial (rgb,
+ CGPointMake (start.center.x,
+ start.center.y),
+ CGPointMake (end.center.x,
+ end.center.y),
+ gradFunc,
+ extend, extend);
+ } else {
+ state->shading = CGShadingCreateRadial (rgb,
+ CGPointMake (start.center.x,
+ start.center.y),
+ MAX (start.radius, 0),
+ CGPointMake (end.center.x,
+ end.center.y),
+ MAX (end.radius, 0),
+ gradFunc,
+ extend, extend);
+ }
CGColorSpaceRelease (rgb);
CGFunctionRelease (gradFunc);
@@ -1469,14 +1208,11 @@ _cairo_quartz_setup_source (cairo_quartz_drawing_state_t *state,
return CAIRO_STATUS_SUCCESS;
}
- 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 (state, 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 (state, rpat, extents);
+ if (source->type == CAIRO_PATTERN_TYPE_LINEAR ||
+ source->type == CAIRO_PATTERN_TYPE_RADIAL)
+ {
+ const cairo_gradient_pattern_t *gpat = (const cairo_gradient_pattern_t *)source;
+ return _cairo_quartz_setup_gradient_source (state, gpat, extents);
}
if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&