From 4f7ac14b8fdafd9fa85e797fb6e4c974788f70fc Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Sat, 8 Mar 2008 19:22:13 -0800 Subject: [PATCH] [quartz] implement REPEAT/REFLECT for gradients Implement REPEAT/REFLECT for gradients in Quartz: for linear gradients, they're implemented natively (by extending the gradient region); for radial gradients, we generate a fallback image using pixman and render that. --- src/cairo-quartz-surface.c | 203 ++++++++++++++++++++++++++++++++----- 1 file changed, 179 insertions(+), 24 deletions(-) diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index b45254957..183347d82 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -376,11 +376,25 @@ _cairo_quartz_cairo_matrix_to_quartz (const cairo_matrix_t *src, static void ComputeGradientValue (void *info, const float *in, float *out) { - float fdist = *in; /* 0.0 .. 1.0 */ - cairo_fixed_t fdist_fix = _cairo_fixed_from_double(*in); + double fdist = *in; + cairo_fixed_t fdist_fix; cairo_gradient_pattern_t *grad = (cairo_gradient_pattern_t*) info; unsigned int i; + /* Put fdist back in the 0.0..1.0 range if we're doing + * REPEAT/REFLECT + */ + if (grad->base.extend == CAIRO_EXTEND_REPEAT) { + fdist = fdist - floor(fdist); + } else if (grad->base.extend == CAIRO_EXTEND_REFLECT) { + fdist = fmod(fabs(fdist), 2.0); + if (fdist > 1.0) { + fdist = 2.0 - fdist; + } + } + + fdist_fix = _cairo_fixed_from_double(fdist); + for (i = 0; i < grad->n_stops; i++) { if (grad->stops[i].x > fdist_fix) break; @@ -417,9 +431,9 @@ ComputeGradientValue (void *info, const float *in, float *out) static CGFunctionRef CreateGradientFunction (cairo_gradient_pattern_t *gpat) { - static const float input_value_range[2] = { 0.f, 1.f }; - static const float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f }; - static const CGFunctionCallbacks callbacks = { + 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 }; @@ -431,6 +445,84 @@ CreateGradientFunction (cairo_gradient_pattern_t *gpat) &callbacks); } +static CGFunctionRef +CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface, + cairo_gradient_pattern_t *gpat, + CGPoint *start, CGPoint *end, + CGAffineTransform matrix) +{ + 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; + + // 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; + + // 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; + + return CGFunctionCreate (gpat, + 1, + input_value_range, + 4, + output_value_ranges, + &callbacks); +} + /* Obtain a CGImageRef from a #cairo_surface_t * */ static CGImageRef @@ -637,6 +729,57 @@ typedef enum { DO_TILED_IMAGE } cairo_quartz_action_t; +static cairo_quartz_action_t +_cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface, + 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; + + 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); + cairo_surface_set_device_offset (fallback, -x0, -y0); + + /* Paint the source onto our temporary */ + fallback_cr = cairo_create (fallback); + cairo_set_operator (fallback_cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source (fallback_cr, source); + cairo_paint (fallback_cr); + cairo_destroy (fallback_cr); + + img = _cairo_surface_to_cgimage ((cairo_surface_t*) surface, fallback); + + surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h); + surface->sourceImage = img; + surface->sourceImageSurface = fallback; + surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0); + + return DO_IMAGE; +} + static cairo_quartz_action_t _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, cairo_linear_pattern_t *lpat) @@ -648,13 +791,6 @@ _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, CGColorSpaceRef rgb; bool extend = abspat->extend == CAIRO_EXTEND_PAD; - /* bandaid for mozilla bug 379321, also visible in the - * linear-gradient-reflect test. - */ - if (abspat->extend == CAIRO_EXTEND_REFLECT || - abspat->extend == CAIRO_EXTEND_REPEAT) - return DO_UNSUPPORTED; - if (lpat->base.n_stops == 0) { CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.); CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.); @@ -672,12 +808,24 @@ _cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface, end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x), _cairo_fixed_to_double (lpat->p2.y)); - cairo_pattern_reference (abspat); - gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) lpat); + // ref will be released by the CGShading's destructor + cairo_pattern_reference ((cairo_pattern_t*) lpat); + + if (abspat->extend == CAIRO_EXTEND_NONE || + abspat->extend == CAIRO_EXTEND_PAD) + { + gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) lpat); + } else { + gradFunc = CreateRepeatingGradientFunction (surface, + (cairo_gradient_pattern_t*) lpat, + &start, &end, surface->sourceTransform); + } + surface->sourceShading = CGShadingCreateAxial (rgb, start, end, gradFunc, extend, extend); + CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); @@ -690,25 +838,28 @@ _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, { cairo_pattern_t *abspat = (cairo_pattern_t *)rpat; cairo_matrix_t mat; - CGAffineTransform cgmat; CGPoint start, end; CGFunctionRef gradFunc; - CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); + CGColorSpaceRef rgb; bool extend = abspat->extend == CAIRO_EXTEND_PAD; - /* bandaid for mozilla bug 379321, also visible in the - * linear-gradient-reflect test. - */ - if (abspat->extend == CAIRO_EXTEND_REFLECT || - abspat->extend == CAIRO_EXTEND_REPEAT) - return DO_UNSUPPORTED; - 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. + */ + return _cairo_quartz_setup_fallback_source (surface, (cairo_pattern_t*) rpat); + } + cairo_pattern_get_matrix (abspat, &mat); cairo_matrix_invert (&mat); _cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform); @@ -720,8 +871,11 @@ _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x), _cairo_fixed_to_double (rpat->c2.y)); - cairo_pattern_reference (abspat); + // ref will be released by the CGShading's destructor + cairo_pattern_reference ((cairo_pattern_t*) rpat); + gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) rpat); + surface->sourceShading = CGShadingCreateRadial (rgb, start, _cairo_fixed_to_double (rpat->r1), @@ -729,6 +883,7 @@ _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, _cairo_fixed_to_double (rpat->r2), gradFunc, extend, extend); + CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc);