/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright � 2006, 2007 Mozilla Corporation * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is Mozilla Corporation. * * Contributor(s): * Vladimir Vukicevic */ #include "cairoint.h" #include "cairo-quartz-private.h" #include /* The 10.5 SDK includes a funky new definition of FloatToFixed which * causes all sorts of breakage; so reset to old-style definition */ #ifdef FloatToFixed #undef FloatToFixed #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1)) #endif #include #undef QUARTZ_DEBUG #ifdef QUARTZ_DEBUG #define ND(_x) fprintf _x #else #define ND(_x) do {} while(0) #endif #define IS_EMPTY(s) ((s)->extents.width == 0 || (s)->extents.height == 0) /* This method is private, but it exists. Its params are are exposed * as args to the NS* method, but not as CG. */ enum PrivateCGCompositeMode { kPrivateCGCompositeClear = 0, kPrivateCGCompositeCopy = 1, kPrivateCGCompositeSourceOver = 2, kPrivateCGCompositeSourceIn = 3, kPrivateCGCompositeSourceOut = 4, kPrivateCGCompositeSourceAtop = 5, kPrivateCGCompositeDestinationOver = 6, kPrivateCGCompositeDestinationIn = 7, kPrivateCGCompositeDestinationOut = 8, kPrivateCGCompositeDestinationAtop = 9, kPrivateCGCompositeXOR = 10, kPrivateCGCompositePlusDarker = 11, // (max (0, (1-d) + (1-s))) kPrivateCGCompositePlusLighter = 12, // (min (1, s + d)) }; typedef enum PrivateCGCompositeMode PrivateCGCompositeMode; CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); CG_EXTERN void CGContextResetCTM (CGContextRef); CG_EXTERN void CGContextSetCTM (CGContextRef, CGAffineTransform); CG_EXTERN void CGContextResetClip (CGContextRef); CG_EXTERN CGSize CGContextGetPatternPhase (CGContextRef); /* We need to work with the 10.3 SDK as well (and 10.3 machines; luckily, 10.3.9 * has all the stuff we care about, just some of it isn't exported in the SDK. */ #ifndef kCGBitmapByteOrder32Host #define USE_10_3_WORKAROUNDS #define kCGBitmapAlphaInfoMask 0x1F #define kCGBitmapByteOrderMask 0x7000 #define kCGBitmapByteOrder32Host 0 typedef uint32_t CGBitmapInfo; /* public in 10.4, present in 10.3.9 */ CG_EXTERN void CGContextReplacePathWithStrokedPath (CGContextRef); CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef); #endif /* Some of these are present in earlier versions of the OS than where * they are public; others are not public at all (CGContextCopyPath, * CGContextReplacePathWithClipPath, many of the getters, etc.) */ static void (*CGContextClipToMaskPtr) (CGContextRef, CGRect, CGImageRef) = NULL; static void (*CGContextDrawTiledImagePtr) (CGContextRef, CGRect, CGImageRef) = NULL; static unsigned int (*CGContextGetTypePtr) (CGContextRef) = NULL; static void (*CGContextSetShouldAntialiasFontsPtr) (CGContextRef, bool) = NULL; static bool (*CGContextGetShouldAntialiasFontsPtr) (CGContextRef) = NULL; static bool (*CGContextGetShouldSmoothFontsPtr) (CGContextRef) = NULL; static void (*CGContextSetAllowsFontSmoothingPtr) (CGContextRef, bool) = NULL; static bool (*CGContextGetAllowsFontSmoothingPtr) (CGContextRef) = NULL; static CGPathRef (*CGContextCopyPathPtr) (CGContextRef) = NULL; static void (*CGContextReplacePathWithClipPathPtr) (CGContextRef) = NULL; static SInt32 _cairo_quartz_osx_version = 0x0; static cairo_bool_t _cairo_quartz_symbol_lookup_done = FALSE; /* * Utility functions */ #ifdef QUARTZ_DEBUG static void quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest); static void quartz_image_to_png (CGImageRef, char *dest); #endif static cairo_quartz_surface_t * _cairo_quartz_surface_create_internal (CGContextRef cgContext, cairo_content_t content, unsigned int width, unsigned int height); /* Load all extra symbols */ static void quartz_ensure_symbols(void) { if (_cairo_quartz_symbol_lookup_done) return; CGContextClipToMaskPtr = dlsym(RTLD_DEFAULT, "CGContextClipToMask"); CGContextDrawTiledImagePtr = dlsym(RTLD_DEFAULT, "CGContextDrawTiledImage"); CGContextGetTypePtr = dlsym(RTLD_DEFAULT, "CGContextGetType"); CGContextSetShouldAntialiasFontsPtr = dlsym(RTLD_DEFAULT, "CGContextSetShouldAntialiasFonts"); CGContextGetShouldAntialiasFontsPtr = dlsym(RTLD_DEFAULT, "CGContextGetShouldAntialiasFonts"); CGContextGetShouldSmoothFontsPtr = dlsym(RTLD_DEFAULT, "CGContextGetShouldSmoothFonts"); CGContextCopyPathPtr = dlsym(RTLD_DEFAULT, "CGContextCopyPath"); CGContextReplacePathWithClipPathPtr = dlsym(RTLD_DEFAULT, "CGContextReplacePathWithClipPath"); CGContextGetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextGetAllowsFontSmoothing"); CGContextSetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextSetAllowsFontSmoothing"); if (Gestalt(gestaltSystemVersion, &_cairo_quartz_osx_version) != noErr) { // assume 10.4 _cairo_quartz_osx_version = 0x1040; } _cairo_quartz_symbol_lookup_done = TRUE; } CGImageRef _cairo_quartz_create_cgimage (cairo_format_t format, unsigned int width, unsigned int height, unsigned int stride, void *data, cairo_bool_t interpolate, CGColorSpaceRef colorSpaceOverride, CGDataProviderReleaseDataCallback releaseCallback, void *releaseInfo) { CGImageRef image = NULL; CGDataProviderRef dataProvider = NULL; CGColorSpaceRef colorSpace = colorSpaceOverride; CGBitmapInfo bitinfo; int bitsPerComponent, bitsPerPixel; switch (format) { case CAIRO_FORMAT_ARGB32: if (colorSpace == NULL) colorSpace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; break; case CAIRO_FORMAT_RGB24: if (colorSpace == NULL) colorSpace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; break; /* XXX -- should use CGImageMaskCreate! */ case CAIRO_FORMAT_A8: if (colorSpace == NULL) colorSpace = CGColorSpaceCreateDeviceGray(); bitinfo = kCGImageAlphaNone; bitsPerComponent = 8; bitsPerPixel = 8; break; case CAIRO_FORMAT_A1: default: return NULL; } dataProvider = CGDataProviderCreateWithData (releaseInfo, data, height * stride, releaseCallback); if (!dataProvider) { // manually release if (releaseCallback) releaseCallback (releaseInfo, data, height * stride); goto FINISH; } image = CGImageCreate (width, height, bitsPerComponent, bitsPerPixel, stride, colorSpace, bitinfo, dataProvider, NULL, interpolate, kCGRenderingIntentDefault); FINISH: CGDataProviderRelease (dataProvider); if (colorSpace != colorSpaceOverride) CGColorSpaceRelease (colorSpace); return image; } static inline cairo_bool_t _cairo_quartz_is_cgcontext_bitmap_context (CGContextRef cgc) { if (cgc == NULL) return FALSE; if (CGContextGetTypePtr) { /* 4 is the type value of a bitmap context */ if (CGContextGetTypePtr(cgc) == 4) return TRUE; return FALSE; } /* This will cause a (harmless) warning to be printed if called on a non-bitmap context */ return CGBitmapContextGetBitsPerPixel(cgc) != 0; } /* CoreGraphics limitation with flipped CTM surfaces: height must be less than signed 16-bit max */ #define CG_MAX_HEIGHT SHRT_MAX #define CG_MAX_WIDTH USHRT_MAX /* is the desired size of the surface within bounds? */ cairo_bool_t _cairo_quartz_verify_surface_size(int width, int height) { /* hmmm, allow width, height == 0 ? */ if (width < 0 || height < 0) { return FALSE; } if (width > CG_MAX_WIDTH || height > CG_MAX_HEIGHT) { return FALSE; } return TRUE; } /* * Cairo path -> Quartz path conversion helpers */ typedef struct _quartz_stroke { CGContextRef cgContext; cairo_matrix_t *ctm_inverse; } quartz_stroke_t; /* cairo path -> execute in context */ static cairo_status_t _cairo_path_to_quartz_context_move_to (void *closure, cairo_point_t *point) { //ND((stderr, "moveto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y))); quartz_stroke_t *stroke = (quartz_stroke_t *)closure; double x = _cairo_fixed_to_double (point->x); double y = _cairo_fixed_to_double (point->y); if (stroke->ctm_inverse) cairo_matrix_transform_point (stroke->ctm_inverse, &x, &y); CGContextMoveToPoint (stroke->cgContext, x, y); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_line_to (void *closure, cairo_point_t *point) { //ND((stderr, "lineto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y))); quartz_stroke_t *stroke = (quartz_stroke_t *)closure; double x = _cairo_fixed_to_double (point->x); double y = _cairo_fixed_to_double (point->y); if (stroke->ctm_inverse) cairo_matrix_transform_point (stroke->ctm_inverse, &x, &y); if (CGContextIsPathEmpty (stroke->cgContext)) CGContextMoveToPoint (stroke->cgContext, x, y); else CGContextAddLineToPoint (stroke->cgContext, x, y); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2) { //ND( (stderr, "curveto: %f,%f %f,%f %f,%f\n", // _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y), // _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y), // _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y))); quartz_stroke_t *stroke = (quartz_stroke_t *)closure; double x0 = _cairo_fixed_to_double (p0->x); double y0 = _cairo_fixed_to_double (p0->y); double x1 = _cairo_fixed_to_double (p1->x); double y1 = _cairo_fixed_to_double (p1->y); double x2 = _cairo_fixed_to_double (p2->x); double y2 = _cairo_fixed_to_double (p2->y); if (stroke->ctm_inverse) { cairo_matrix_transform_point (stroke->ctm_inverse, &x0, &y0); cairo_matrix_transform_point (stroke->ctm_inverse, &x1, &y1); cairo_matrix_transform_point (stroke->ctm_inverse, &x2, &y2); } CGContextAddCurveToPoint (stroke->cgContext, x0, y0, x1, y1, x2, y2); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_path_to_quartz_context_close_path (void *closure) { //ND((stderr, "closepath\n")); quartz_stroke_t *stroke = (quartz_stroke_t *)closure; CGContextClosePath (stroke->cgContext); return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_quartz_cairo_path_to_quartz_context (cairo_path_fixed_t *path, quartz_stroke_t *stroke) { return _cairo_path_fixed_interpret (path, CAIRO_DIRECTION_FORWARD, _cairo_path_to_quartz_context_move_to, _cairo_path_to_quartz_context_line_to, _cairo_path_to_quartz_context_curve_to, _cairo_path_to_quartz_context_close_path, stroke); } /* * Misc helpers/callbacks */ static PrivateCGCompositeMode _cairo_quartz_cairo_operator_to_quartz (cairo_operator_t op) { switch (op) { case CAIRO_OPERATOR_CLEAR: return kPrivateCGCompositeClear; case CAIRO_OPERATOR_SOURCE: return kPrivateCGCompositeCopy; case CAIRO_OPERATOR_OVER: return kPrivateCGCompositeSourceOver; case CAIRO_OPERATOR_IN: /* XXX This doesn't match image output */ return kPrivateCGCompositeSourceIn; case CAIRO_OPERATOR_OUT: /* XXX This doesn't match image output */ return kPrivateCGCompositeSourceOut; case CAIRO_OPERATOR_ATOP: return kPrivateCGCompositeSourceAtop; case CAIRO_OPERATOR_DEST: /* XXX this is handled specially (noop)! */ return kPrivateCGCompositeCopy; case CAIRO_OPERATOR_DEST_OVER: return kPrivateCGCompositeDestinationOver; case CAIRO_OPERATOR_DEST_IN: /* XXX This doesn't match image output */ return kPrivateCGCompositeDestinationIn; case CAIRO_OPERATOR_DEST_OUT: return kPrivateCGCompositeDestinationOut; case CAIRO_OPERATOR_DEST_ATOP: /* XXX This doesn't match image output */ return kPrivateCGCompositeDestinationAtop; case CAIRO_OPERATOR_XOR: return kPrivateCGCompositeXOR; /* This will generate strange results */ case CAIRO_OPERATOR_ADD: return kPrivateCGCompositePlusLighter; case CAIRO_OPERATOR_SATURATE: /* XXX This doesn't match image output for SATURATE; there's no equivalent */ return kPrivateCGCompositePlusDarker; /* ??? */ } return kPrivateCGCompositeCopy; } static inline CGLineCap _cairo_quartz_cairo_line_cap_to_quartz (cairo_line_cap_t ccap) { switch (ccap) { case CAIRO_LINE_CAP_BUTT: return kCGLineCapButt; break; case CAIRO_LINE_CAP_ROUND: return kCGLineCapRound; break; case CAIRO_LINE_CAP_SQUARE: return kCGLineCapSquare; break; } return kCGLineCapButt; } static inline CGLineJoin _cairo_quartz_cairo_line_join_to_quartz (cairo_line_join_t cjoin) { switch (cjoin) { case CAIRO_LINE_JOIN_MITER: return kCGLineJoinMiter; break; case CAIRO_LINE_JOIN_ROUND: return kCGLineJoinRound; break; case CAIRO_LINE_JOIN_BEVEL: return kCGLineJoinBevel; break; } return kCGLineJoinMiter; } static inline CGInterpolationQuality _cairo_quartz_filter_to_quartz (cairo_filter_t filter) { switch (filter) { case CAIRO_FILTER_NEAREST: return kCGInterpolationNone; case CAIRO_FILTER_FAST: return kCGInterpolationLow; case CAIRO_FILTER_BEST: case CAIRO_FILTER_GOOD: case CAIRO_FILTER_BILINEAR: case CAIRO_FILTER_GAUSSIAN: return kCGInterpolationDefault; } return kCGInterpolationDefault; } static inline void _cairo_quartz_cairo_matrix_to_quartz (const cairo_matrix_t *src, CGAffineTransform *dst) { dst->a = src->xx; dst->b = src->yx; dst->c = src->xy; dst->d = src->yy; dst->tx = src->x0; dst->ty = src->y0; } typedef struct { bool isClipping; CGGlyph *cg_glyphs; CGSize *cg_advances; size_t nglyphs; CGAffineTransform textTransform; CGFontRef font; CGPoint origin; } unbounded_show_glyphs_t; typedef struct { CGPathRef cgPath; cairo_fill_rule_t fill_rule; } unbounded_stroke_fill_t; typedef struct { CGImageRef mask; CGAffineTransform maskTransform; } unbounded_mask_t; typedef enum { UNBOUNDED_STROKE_FILL, UNBOUNDED_SHOW_GLYPHS, UNBOUNDED_MASK } unbounded_op_t; typedef struct { unbounded_op_t op; union { unbounded_stroke_fill_t stroke_fill; unbounded_show_glyphs_t show_glyphs; unbounded_mask_t mask; } u; } unbounded_op_data_t; static void _cairo_quartz_fixup_unbounded_operation (cairo_quartz_surface_t *surface, unbounded_op_data_t *op, cairo_antialias_t antialias) { CGColorSpaceRef gray; CGRect clipBox, clipBoxRound; CGContextRef cgc; CGImageRef maskImage; if (!CGContextClipToMaskPtr) return; clipBox = CGContextGetClipBoundingBox (surface->cgContext); clipBoxRound = CGRectIntegral (clipBox); gray = CGColorSpaceCreateDeviceGray (); cgc = CGBitmapContextCreate (NULL, clipBoxRound.size.width, clipBoxRound.size.height, 8, clipBoxRound.size.width, gray, kCGImageAlphaNone); CGColorSpaceRelease (gray); if (!cgc) return; /* We want to mask out whatever we just rendered, so we fill the * surface with white, and then we'll render with black. */ CGContextSetRGBFillColor (cgc, 1.0f, 1.0f, 1.0f, 1.0f); CGContextFillRect (cgc, CGRectMake (0, 0, clipBoxRound.size.width, clipBoxRound.size.height)); CGContextSetRGBFillColor (cgc, 0.0f, 0.0f, 0.0f, 1.0f); CGContextSetShouldAntialias (cgc, (antialias != CAIRO_ANTIALIAS_NONE)); CGContextTranslateCTM (cgc, -clipBoxRound.origin.x, -clipBoxRound.origin.y); /* We need to either render the path that was given to us, or the glyph op */ if (op->op == UNBOUNDED_STROKE_FILL) { CGContextBeginPath (cgc); CGContextAddPath (cgc, op->u.stroke_fill.cgPath); if (op->u.stroke_fill.fill_rule == CAIRO_FILL_RULE_WINDING) CGContextFillPath (cgc); else CGContextEOFillPath (cgc); } else if (op->op == UNBOUNDED_SHOW_GLYPHS) { CGContextSetFont (cgc, op->u.show_glyphs.font); CGContextSetFontSize (cgc, 1.0); CGContextSetTextMatrix (cgc, op->u.show_glyphs.textTransform); CGContextTranslateCTM (cgc, op->u.show_glyphs.origin.x, op->u.show_glyphs.origin.y); if (op->u.show_glyphs.isClipping) { /* Note that the comment in show_glyphs about kCGTextClip * and the text transform still applies here; however, the * cg_advances we have were already transformed, so we * don't have to do anything. */ CGContextSetTextDrawingMode (cgc, kCGTextClip); CGContextSaveGState (cgc); } CGContextShowGlyphsWithAdvances (cgc, op->u.show_glyphs.cg_glyphs, op->u.show_glyphs.cg_advances, op->u.show_glyphs.nglyphs); if (op->u.show_glyphs.isClipping) { CGContextFillRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height)); CGContextRestoreGState (cgc); } } else if (op->op == UNBOUNDED_MASK) { CGContextSaveGState (cgc); CGContextConcatCTM (cgc, op->u.mask.maskTransform); CGContextClipToMask (cgc, CGRectMake (0.0f, 0.0f, CGImageGetWidth(op->u.mask.mask), CGImageGetHeight(op->u.mask.mask)), op->u.mask.mask); CGContextFillRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height)); CGContextRestoreGState (cgc); } /* Also mask out the portion of the clipbox that we rounded out, if any */ if (!CGRectEqualToRect (clipBox, clipBoxRound)) { CGContextBeginPath (cgc); CGContextAddRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height)); CGContextAddRect (cgc, CGRectMake (clipBoxRound.origin.x - clipBox.origin.x, clipBoxRound.origin.y - clipBox.origin.y, clipBox.size.width, clipBox.size.height)); CGContextEOFillPath (cgc); } maskImage = CGBitmapContextCreateImage (cgc); CGContextRelease (cgc); if (!maskImage) return; /* Then render with the mask */ CGContextSaveGState (surface->cgContext); CGContextSetCompositeOperation (surface->cgContext, kPrivateCGCompositeCopy); CGContextClipToMaskPtr (surface->cgContext, clipBoxRound, maskImage); CGImageRelease (maskImage); /* Finally, clear out the entire clipping region through our mask */ CGContextClearRect (surface->cgContext, clipBoxRound); CGContextRestoreGState (surface->cgContext); } /* * Source -> Quartz setup and finish functions */ static void ComputeGradientValue (void *info, const float *in, float *out) { double fdist = *in; 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; } } for (i = 0; i < grad->n_stops; i++) { if (grad->stops[i].offset > fdist) break; } if (i == 0 || i == grad->n_stops) { if (i == grad->n_stops) --i; out[0] = grad->stops[i].color.red; out[1] = grad->stops[i].color.green; out[2] = grad->stops[i].color.blue; out[3] = grad->stops[i].color.alpha; } else { float ax = grad->stops[i-1].offset; float bx = grad->stops[i].offset - ax; float bp = (fdist - ax)/bx; float ap = 1.0 - bp; out[0] = grad->stops[i-1].color.red * ap + grad->stops[i].color.red * bp; out[1] = grad->stops[i-1].color.green * ap + grad->stops[i].color.green * bp; out[2] = 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 CGFunctionRef CreateGradientFunction (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); } static CGFunctionRef CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface, 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; // 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; 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); } static cairo_status_t _cairo_surface_to_cgimage (cairo_surface_t *target, cairo_surface_t *source, CGImageRef *image_out) { cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_surface_type_t stype = cairo_surface_get_type (source); cairo_image_surface_t *isurf; CGImageRef image; void *image_extra; if (stype == CAIRO_SURFACE_TYPE_QUARTZ_IMAGE) { cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t *) source; *image_out = CGImageRetain (surface->image); return CAIRO_STATUS_SUCCESS; } if (stype == CAIRO_SURFACE_TYPE_QUARTZ) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) source; if (IS_EMPTY(surface)) { *image_out = NULL; return CAIRO_STATUS_SUCCESS; } if (_cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) { *image_out = CGBitmapContextCreateImage (surface->cgContext); if (*image_out) return CAIRO_STATUS_SUCCESS; } } if (stype != CAIRO_SURFACE_TYPE_IMAGE) { status = _cairo_surface_acquire_source_image (source, &isurf, &image_extra); if (status) return status; } else { isurf = (cairo_image_surface_t *) source; } if (isurf->width == 0 || isurf->height == 0) { *image_out = NULL; } else { cairo_image_surface_t *isurf_snap = NULL; isurf_snap = (cairo_image_surface_t*) _cairo_surface_snapshot ((cairo_surface_t*) isurf); if (isurf_snap == NULL) return CAIRO_STATUS_NO_MEMORY; if (isurf_snap->base.type != CAIRO_SURFACE_TYPE_IMAGE) return CAIRO_STATUS_SURFACE_TYPE_MISMATCH; image = _cairo_quartz_create_cgimage (isurf_snap->format, isurf_snap->width, isurf_snap->height, isurf_snap->stride, isurf_snap->data, TRUE, NULL, DataProviderReleaseCallback, isurf_snap); *image_out = image; } if ((cairo_surface_t*) isurf != source) _cairo_surface_release_source_image (source, isurf, image_extra); return status; } /* Generic #cairo_pattern_t -> CGPattern function */ typedef struct { CGImageRef image; CGRect imageBounds; cairo_bool_t do_reflect; } SurfacePatternDrawInfo; static void SurfacePatternDrawFunc (void *ainfo, CGContextRef context) { SurfacePatternDrawInfo *info = (SurfacePatternDrawInfo*) ainfo; CGContextTranslateCTM (context, 0, info->imageBounds.size.height); CGContextScaleCTM (context, 1, -1); CGContextDrawImage (context, info->imageBounds, info->image); if (info->do_reflect) { /* draw 3 more copies of the image, flipped. * DrawImage draws the image according to the current Y-direction into the rectangle given * (imageBounds); at the time of the first DrawImage above, the origin is at the bottom left * of the base image position, and the Y axis is extending upwards. */ /* Make the y axis extend downwards, and draw a flipped image below */ CGContextScaleCTM (context, 1, -1); CGContextDrawImage (context, info->imageBounds, info->image); /* Shift over to the right, and flip vertically (translation is 2x, * since we'll be flipping and thus rendering the rectangle "backwards" */ CGContextTranslateCTM (context, 2 * info->imageBounds.size.width, 0); CGContextScaleCTM (context, -1, 1); CGContextDrawImage (context, info->imageBounds, info->image); /* Then unflip the Y-axis again, and draw the image above the point. */ CGContextScaleCTM (context, 1, -1); CGContextDrawImage (context, info->imageBounds, info->image); } } static void SurfacePatternReleaseInfoFunc (void *ainfo) { SurfacePatternDrawInfo *info = (SurfacePatternDrawInfo*) ainfo; CGImageRelease (info->image); free (info); } static cairo_int_status_t _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t *dest, cairo_pattern_t *apattern, CGPatternRef *cgpat) { cairo_surface_pattern_t *spattern; cairo_surface_t *pat_surf; cairo_rectangle_int_t extents; CGImageRef image; CGRect pbounds; CGAffineTransform ptransform, stransform; CGPatternCallbacks cb = { 0, SurfacePatternDrawFunc, SurfacePatternReleaseInfoFunc }; SurfacePatternDrawInfo *info; float rw, rh; cairo_status_t status; cairo_matrix_t m; /* SURFACE is the only type we'll handle here */ if (apattern->type != CAIRO_PATTERN_TYPE_SURFACE) return CAIRO_INT_STATUS_UNSUPPORTED; spattern = (cairo_surface_pattern_t *) apattern; pat_surf = spattern->surface; status = _cairo_surface_get_extents (pat_surf, &extents); if (status) return status; status = _cairo_surface_to_cgimage ((cairo_surface_t*) dest, pat_surf, &image); if (status != CAIRO_STATUS_SUCCESS) return CAIRO_INT_STATUS_UNSUPPORTED; if (image == NULL) return CAIRO_INT_STATUS_NOTHING_TO_DO; info = malloc(sizeof(SurfacePatternDrawInfo)); if (!info) return CAIRO_STATUS_NO_MEMORY; /* XXX -- if we're printing, we may need to call CGImageCreateCopy to make sure * that the data will stick around for this image when the printer gets to it. * Otherwise, the underlying data store may disappear from under us! * * _cairo_surface_to_cgimage will copy when it converts non-Quartz surfaces, * since the Quartz surfaces have a higher chance of sticking around. If the * source is a quartz image surface, then it's set up to retain a ref to the * image surface that it's backed by. */ info->image = image; info->imageBounds = CGRectMake (0, 0, extents.width, extents.height); info->do_reflect = FALSE; pbounds.origin.x = 0; pbounds.origin.y = 0; if (spattern->base.extend == CAIRO_EXTEND_REFLECT) { pbounds.size.width = 2.0 * extents.width; pbounds.size.height = 2.0 * extents.height; info->do_reflect = TRUE; } else { pbounds.size.width = extents.width; pbounds.size.height = extents.height; } rw = pbounds.size.width; rh = pbounds.size.height; m = spattern->base.matrix; cairo_matrix_invert(&m); _cairo_quartz_cairo_matrix_to_quartz (&m, &stransform); /* The pattern matrix is relative to the bottom left, again; the * incoming cairo pattern matrix is relative to the upper left. * So we take the pattern matrix and the original context matrix, * which gives us the correct base translation/y flip. */ ptransform = CGAffineTransformConcat(stransform, dest->cgContextBaseCTM); #ifdef QUARTZ_DEBUG ND((stderr, " pbounds: %f %f %f %f\n", pbounds.origin.x, pbounds.origin.y, pbounds.size.width, pbounds.size.height)); ND((stderr, " pattern xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", ptransform.tx, ptransform.ty, ptransform.a, ptransform.b, ptransform.c, ptransform.d)); CGAffineTransform xform = CGContextGetCTM(dest->cgContext); ND((stderr, " context xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", xform.tx, xform.ty, xform.a, xform.b, xform.c, xform.d)); #endif *cgpat = CGPatternCreate (info, pbounds, ptransform, rw, rh, kCGPatternTilingConstantSpacing, /* kCGPatternTilingNoDistortion, */ TRUE, &cb); return CAIRO_STATUS_SUCCESS; } typedef enum { DO_SOLID, DO_SHADING, DO_PATTERN, DO_IMAGE, DO_UNSUPPORTED, DO_NOTHING, 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; 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); 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); status = _cairo_surface_to_cgimage ((cairo_surface_t*) surface, fallback, &img); if (status == CAIRO_STATUS_SUCCESS && img == NULL) return DO_NOTHING; if (status) return DO_UNSUPPORTED; 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) { cairo_pattern_t *abspat = (cairo_pattern_t *) lpat; 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; } cairo_pattern_get_matrix (abspat, &mat); 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)); end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x), _cairo_fixed_to_double (lpat->p2.y)); // 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); return DO_SHADING; } static cairo_quartz_action_t _cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface, cairo_radial_pattern_t *rpat) { cairo_pattern_t *abspat = (cairo_pattern_t *)rpat; cairo_matrix_t mat; CGPoint start, end; CGFunctionRef gradFunc; CGColorSpaceRef rgb; bool extend = abspat->extend == CAIRO_EXTEND_PAD; 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); 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)); // 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), end, _cairo_fixed_to_double (rpat->r2), gradFunc, extend, extend); CGColorSpaceRelease(rgb); CGFunctionRelease(gradFunc); return DO_SHADING; } static cairo_quartz_action_t _cairo_quartz_setup_source (cairo_quartz_surface_t *surface, cairo_pattern_t *source) { 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; CGContextSetRGBStrokeColor (surface->cgContext, solid->color.red, solid->color.green, solid->color.blue, solid->color.alpha); CGContextSetRGBFillColor (surface->cgContext, solid->color.red, solid->color.green, solid->color.blue, solid->color.alpha); return DO_SOLID; } if (source->type == CAIRO_PATTERN_TYPE_LINEAR) { cairo_linear_pattern_t *lpat = (cairo_linear_pattern_t *)source; return _cairo_quartz_setup_linear_source (surface, lpat); } if (source->type == CAIRO_PATTERN_TYPE_RADIAL) { cairo_radial_pattern_t *rpat = (cairo_radial_pattern_t *)source; return _cairo_quartz_setup_radial_source (surface, rpat); } if (source->type == CAIRO_PATTERN_TYPE_SURFACE && (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT))) { cairo_surface_pattern_t *spat = (cairo_surface_pattern_t *) source; cairo_surface_t *pat_surf = spat->surface; CGImageRef img; cairo_matrix_t m = spat->base.matrix; cairo_rectangle_int_t extents; cairo_status_t status; CGAffineTransform xform; CGRect srcRect; cairo_fixed_t fw, fh; status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img); if (status == CAIRO_STATUS_SUCCESS && img == NULL) return DO_NOTHING; if (status) return DO_UNSUPPORTED; surface->sourceImage = img; cairo_matrix_invert(&m); _cairo_quartz_cairo_matrix_to_quartz (&m, &surface->sourceTransform); status = _cairo_surface_get_extents (pat_surf, &extents); if (status) return DO_UNSUPPORTED; if (source->extend == CAIRO_EXTEND_NONE) { surface->sourceImageRect = CGRectMake (0, 0, extents.width, extents.height); return DO_IMAGE; } /* Quartz seems to tile images at pixel-aligned regions only -- this * leads to seams if the image doesn't end up scaling to fill the * space exactly. The CGPattern tiling approach doesn't have this * problem. Check if we're going to fill up the space (within some * epsilon), and if not, fall back to the CGPattern type. */ xform = CGAffineTransformConcat (CGContextGetCTM (surface->cgContext), surface->sourceTransform); srcRect = CGRectMake (0, 0, extents.width, extents.height); srcRect = CGRectApplyAffineTransform (srcRect, xform); fw = _cairo_fixed_from_double (srcRect.size.width); fh = _cairo_fixed_from_double (srcRect.size.height); if ((fw & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON && (fh & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON) { /* We're good to use DrawTiledImage, but ensure that * the math works out */ srcRect.size.width = round(srcRect.size.width); srcRect.size.height = round(srcRect.size.height); xform = CGAffineTransformInvert (xform); srcRect = CGRectApplyAffineTransform (srcRect, xform); surface->sourceImageRect = srcRect; return DO_TILED_IMAGE; } /* Fall through to generic SURFACE case */ } if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { float patternAlpha = 1.0f; CGColorSpaceRef patternSpace; CGPatternRef pattern; cairo_int_status_t status; status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, &pattern); if (status == CAIRO_INT_STATUS_NOTHING_TO_DO) return DO_NOTHING; if (status) return DO_UNSUPPORTED; // Save before we change the pattern, colorspace, etc. so that // we can restore and make sure that quartz releases our // pattern (which may be stack allocated) CGContextSaveGState(surface->cgContext); patternSpace = CGColorSpaceCreatePattern(NULL); CGContextSetFillColorSpace (surface->cgContext, patternSpace); CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha); CGContextSetStrokeColorSpace (surface->cgContext, patternSpace); CGContextSetStrokePattern (surface->cgContext, pattern, &patternAlpha); CGColorSpaceRelease (patternSpace); /* Quartz likes to munge the pattern phase (as yet unexplained * why); force it to 0,0 as we've already baked in the correct * pattern translation into the pattern matrix */ CGContextSetPatternPhase (surface->cgContext, CGSizeMake(0,0)); surface->sourcePattern = pattern; return DO_PATTERN; } return DO_UNSUPPORTED; } static void _cairo_quartz_teardown_source (cairo_quartz_surface_t *surface, cairo_pattern_t *source) { CGContextSetInterpolationQuality (surface->cgContext, surface->oldInterpolationQuality); if (surface->sourceImage) { CGImageRelease(surface->sourceImage); surface->sourceImage = NULL; cairo_surface_destroy(surface->sourceImageSurface); surface->sourceImageSurface = NULL; } if (surface->sourceShading) { CGShadingRelease(surface->sourceShading); surface->sourceShading = NULL; } if (surface->sourcePattern) { CGPatternRelease(surface->sourcePattern); // To tear down the pattern and colorspace CGContextRestoreGState(surface->cgContext); surface->sourcePattern = NULL; } } /* * get source/dest image implementation */ /* Read the image from the surface's front buffer */ static cairo_int_status_t _cairo_quartz_get_image (cairo_quartz_surface_t *surface, cairo_image_surface_t **image_out) { unsigned char *imageData; cairo_image_surface_t *isurf; if (IS_EMPTY(surface)) { *image_out = (cairo_image_surface_t*) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); return CAIRO_STATUS_SUCCESS; } if (surface->imageSurfaceEquiv) { *image_out = (cairo_image_surface_t*) cairo_surface_reference(surface->imageSurfaceEquiv); return CAIRO_STATUS_SUCCESS; } if (_cairo_quartz_is_cgcontext_bitmap_context(surface->cgContext)) { unsigned int stride; unsigned int bitinfo; unsigned int bpc, bpp; CGColorSpaceRef colorspace; unsigned int color_comps; imageData = (unsigned char *) CGBitmapContextGetData(surface->cgContext); #ifdef USE_10_3_WORKAROUNDS bitinfo = CGBitmapContextGetAlphaInfo (surface->cgContext); #else bitinfo = CGBitmapContextGetBitmapInfo (surface->cgContext); #endif stride = CGBitmapContextGetBytesPerRow (surface->cgContext); bpp = CGBitmapContextGetBitsPerPixel (surface->cgContext); bpc = CGBitmapContextGetBitsPerComponent (surface->cgContext); // let's hope they don't add YUV under us colorspace = CGBitmapContextGetColorSpace (surface->cgContext); color_comps = CGColorSpaceGetNumberOfComponents(colorspace); // XXX TODO: We can handle all of these by converting to // pixman masks, including non-native-endian masks if (bpc != 8) return CAIRO_INT_STATUS_UNSUPPORTED; if (bpp != 32 && bpp != 8) return CAIRO_INT_STATUS_UNSUPPORTED; if (color_comps != 3 && color_comps != 1) return CAIRO_INT_STATUS_UNSUPPORTED; if (bpp == 32 && color_comps == 3 && (bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst && (bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_ARGB32, surface->extents.width, surface->extents.height, stride); } else if (bpp == 32 && color_comps == 3 && (bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst && (bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_RGB24, surface->extents.width, surface->extents.height, stride); } else if (bpp == 8 && color_comps == 1) { isurf = (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, CAIRO_FORMAT_A8, surface->extents.width, surface->extents.height, stride); } else { return CAIRO_INT_STATUS_UNSUPPORTED; } } else { return CAIRO_INT_STATUS_UNSUPPORTED; } *image_out = isurf; return CAIRO_STATUS_SUCCESS; } /* * Cairo surface backend implementations */ static cairo_status_t _cairo_quartz_surface_finish (void *abstract_surface) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; ND((stderr, "_cairo_quartz_surface_finish[%p] cgc: %p\n", surface, surface->cgContext)); if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; /* Restore our saved gstate that we use to reset clipping */ CGContextRestoreGState (surface->cgContext); CGContextRelease (surface->cgContext); surface->cgContext = NULL; if (surface->imageSurfaceEquiv) { cairo_surface_destroy (surface->imageSurfaceEquiv); surface->imageSurfaceEquiv = NULL; } if (surface->imageData) { free (surface->imageData); surface->imageData = NULL; } return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_quartz_surface_acquire_source_image (void *abstract_surface, cairo_image_surface_t **image_out, void **image_extra) { cairo_int_status_t status; cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; //ND((stderr, "%p _cairo_quartz_surface_acquire_source_image\n", surface)); status = _cairo_quartz_get_image (surface, image_out); if (status) return _cairo_error (CAIRO_STATUS_NO_MEMORY); *image_extra = NULL; return CAIRO_STATUS_SUCCESS; } static void _cairo_quartz_surface_release_source_image (void *abstract_surface, cairo_image_surface_t *image, void *image_extra) { cairo_surface_destroy ((cairo_surface_t *) image); } static cairo_status_t _cairo_quartz_surface_acquire_dest_image (void *abstract_surface, cairo_rectangle_int_t *interest_rect, cairo_image_surface_t **image_out, cairo_rectangle_int_t *image_rect, void **image_extra) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t status; ND((stderr, "%p _cairo_quartz_surface_acquire_dest_image\n", surface)); status = _cairo_quartz_get_image (surface, image_out); if (status) return _cairo_error (CAIRO_STATUS_NO_MEMORY); *image_rect = surface->extents; *image_extra = NULL; return CAIRO_STATUS_SUCCESS; } static void _cairo_quartz_surface_release_dest_image (void *abstract_surface, cairo_rectangle_int_t *interest_rect, cairo_image_surface_t *image, cairo_rectangle_int_t *image_rect, void *image_extra) { //cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; //ND((stderr, "%p _cairo_quartz_surface_release_dest_image\n", surface)); cairo_surface_destroy ((cairo_surface_t *) image); } static cairo_surface_t * _cairo_quartz_surface_create_similar (void *abstract_surface, cairo_content_t content, int width, int height) { /*cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;*/ cairo_format_t format; if (content == CAIRO_CONTENT_COLOR_ALPHA) format = CAIRO_FORMAT_ARGB32; else if (content == CAIRO_CONTENT_COLOR) format = CAIRO_FORMAT_RGB24; else if (content == CAIRO_CONTENT_ALPHA) format = CAIRO_FORMAT_A8; else return NULL; // verify width and height of surface if (!_cairo_quartz_verify_surface_size(width, height)) { return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } return cairo_quartz_surface_create (format, width, height); } static cairo_status_t _cairo_quartz_surface_clone_similar (void *abstract_surface, cairo_surface_t *src, int src_x, int src_y, int width, int height, int *clone_offset_x, int *clone_offset_y, cairo_surface_t **clone_out) { cairo_quartz_surface_t *new_surface = NULL; cairo_format_t new_format; CGImageRef quartz_image = NULL; cairo_status_t status; *clone_out = NULL; // verify width and height of surface if (!_cairo_quartz_verify_surface_size(width, height)) { return CAIRO_INT_STATUS_UNSUPPORTED; } if (width == 0 || height == 0) { *clone_out = (cairo_surface_t*) _cairo_quartz_surface_create_internal (NULL, CAIRO_CONTENT_COLOR_ALPHA, width, height); *clone_offset_x = 0; *clone_offset_y = 0; return CAIRO_STATUS_SUCCESS; } if (src->backend->type == CAIRO_SURFACE_TYPE_QUARTZ) { cairo_quartz_surface_t *qsurf = (cairo_quartz_surface_t *) src; if (IS_EMPTY(qsurf)) { *clone_out = (cairo_surface_t*) _cairo_quartz_surface_create_internal (NULL, CAIRO_CONTENT_COLOR_ALPHA, qsurf->extents.width, qsurf->extents.height); *clone_offset_x = 0; *clone_offset_y = 0; return CAIRO_STATUS_SUCCESS; } } status = _cairo_surface_to_cgimage ((cairo_surface_t*) abstract_surface, src, &quartz_image); if (status) return CAIRO_INT_STATUS_UNSUPPORTED; new_format = CAIRO_FORMAT_ARGB32; /* assumed */ if (_cairo_surface_is_image (src)) { new_format = ((cairo_image_surface_t *) src)->format; } new_surface = (cairo_quartz_surface_t *) cairo_quartz_surface_create (new_format, width, height); if (quartz_image == NULL) goto FINISH; if (!new_surface || new_surface->base.status) { CGImageRelease (quartz_image); return CAIRO_INT_STATUS_UNSUPPORTED; } CGContextSaveGState (new_surface->cgContext); CGContextSetCompositeOperation (new_surface->cgContext, kPrivateCGCompositeCopy); CGContextTranslateCTM (new_surface->cgContext, -src_x, -src_y); CGContextDrawImage (new_surface->cgContext, CGRectMake (0, 0, CGImageGetWidth(quartz_image), CGImageGetHeight(quartz_image)), quartz_image); CGContextRestoreGState (new_surface->cgContext); CGImageRelease (quartz_image); FINISH: *clone_offset_x = src_x; *clone_offset_y = src_y; *clone_out = (cairo_surface_t*) new_surface; return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_quartz_surface_get_extents (void *abstract_surface, cairo_rectangle_int_t *extents) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; *extents = surface->extents; return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_quartz_surface_paint (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_quartz_action_t action; ND((stderr, "%p _cairo_quartz_surface_paint op %d source->type %d\n", surface, op, source->type)); 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); 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) { CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextDrawShading (surface->cgContext, surface->sourceShading); } 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) CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); else CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); CGContextRestoreGState (surface->cgContext); } else if (action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; } _cairo_quartz_teardown_source (surface, source); ND((stderr, "-- paint\n")); return rv; } static cairo_int_status_t _cairo_quartz_surface_fill (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_quartz_action_t action; quartz_stroke_t stroke; cairo_box_t box; CGPathRef path_for_unbounded = NULL; ND((stderr, "%p _cairo_quartz_surface_fill op %d source->type %d\n", surface, op, source->type)); if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; /* Check whether the path would be a no-op */ /* XXX handle unbounded ops */ if (_cairo_path_fixed_is_empty(path) || (_cairo_path_fixed_is_box(path, &box) && box.p1.x == box.p2.x && box.p1.y == box.p2.y)) { 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); 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; if (!_cairo_operator_bounded_by_mask(op) && CGContextCopyPathPtr) path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); if (action == DO_SOLID || action == DO_PATTERN) { if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextFillPath (surface->cgContext); else CGContextEOFillPath (surface->cgContext); } else if (action == DO_SHADING) { // we have to clip and then paint the shading; we can't fill // with the shading if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextClip (surface->cgContext); else CGContextEOClip (surface->cgContext); CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextDrawShading (surface->cgContext, surface->sourceShading); } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextClip (surface->cgContext); else CGContextEOClip (surface->cgContext); CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height); CGContextScaleCTM (surface->cgContext, 1, -1); if (action == DO_IMAGE) CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); else CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); } else if (action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; } BAIL: _cairo_quartz_teardown_source (surface, source); CGContextRestoreGState (surface->cgContext); if (path_for_unbounded) { unbounded_op_data_t ub; ub.op = UNBOUNDED_STROKE_FILL; ub.u.stroke_fill.cgPath = path_for_unbounded; ub.u.stroke_fill.fill_rule = fill_rule; _cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias); CGPathRelease (path_for_unbounded); } ND((stderr, "-- fill\n")); return rv; } static cairo_int_status_t _cairo_quartz_surface_stroke (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_path_fixed_t *path, cairo_stroke_style_t *style, cairo_matrix_t *ctm, cairo_matrix_t *ctm_inverse, double tolerance, cairo_antialias_t antialias) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_quartz_action_t action; quartz_stroke_t stroke; CGAffineTransform origCTM, strokeTransform; CGPathRef path_for_unbounded = NULL; ND((stderr, "%p _cairo_quartz_surface_stroke op %d source->type %d\n", surface, op, source->type)); if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; if (op == CAIRO_OPERATOR_DEST) return CAIRO_STATUS_SUCCESS; CGContextSaveGState (surface->cgContext); // Turning antialiasing off used to cause misrendering with // single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels). // That's been since fixed in at least 10.5, and in the latest 10.4 dot releases. CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); CGContextSetLineWidth (surface->cgContext, style->line_width); CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); CGContextSetMiterLimit (surface->cgContext, style->miter_limit); origCTM = CGContextGetCTM (surface->cgContext); _cairo_quartz_cairo_matrix_to_quartz (ctm, &strokeTransform); CGContextConcatCTM (surface->cgContext, strokeTransform); if (style->dash && style->num_dashes) { #define STATIC_DASH 32 float sdash[STATIC_DASH]; float *fdash = sdash; unsigned int max_dashes = style->num_dashes; unsigned int k; if (style->num_dashes%2) max_dashes *= 2; if (max_dashes > STATIC_DASH) fdash = _cairo_malloc_ab (max_dashes, sizeof (float)); if (fdash == NULL) return _cairo_error (CAIRO_STATUS_NO_MEMORY); for (k = 0; k < max_dashes; k++) fdash[k] = (float) style->dash[k % style->num_dashes]; 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); 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; if (!_cairo_operator_bounded_by_mask (op) && CGContextCopyPathPtr) path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); if (action == DO_SOLID || action == DO_PATTERN) { CGContextStrokePath (surface->cgContext); } else if (action == DO_IMAGE || action == DO_TILED_IMAGE) { CGContextReplacePathWithStrokedPath (surface->cgContext); CGContextClip (surface->cgContext); CGContextSetCTM (surface->cgContext, origCTM); CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height); CGContextScaleCTM (surface->cgContext, 1, -1); if (action == DO_IMAGE) CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); else CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); } else if (action == DO_SHADING) { CGContextReplacePathWithStrokedPath (surface->cgContext); CGContextClip (surface->cgContext); CGContextSetCTM (surface->cgContext, origCTM); CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextDrawShading (surface->cgContext, surface->sourceShading); } else if (action != DO_NOTHING) { rv = CAIRO_INT_STATUS_UNSUPPORTED; } BAIL: _cairo_quartz_teardown_source (surface, source); CGContextRestoreGState (surface->cgContext); if (path_for_unbounded) { unbounded_op_data_t ub; CGContextBeginPath (surface->cgContext); /* recreate the stroke state, but without the CTM, as it's been already baked * into the path. */ CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); CGContextSetLineWidth (surface->cgContext, style->line_width); CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap)); CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join)); CGContextSetMiterLimit (surface->cgContext, style->miter_limit); CGContextAddPath (surface->cgContext, path_for_unbounded); CGPathRelease (path_for_unbounded); CGContextReplacePathWithStrokedPath (surface->cgContext); path_for_unbounded = CGContextCopyPathPtr (surface->cgContext); ub.op = UNBOUNDED_STROKE_FILL; ub.u.stroke_fill.cgPath = path_for_unbounded; ub.u.stroke_fill.fill_rule = CAIRO_FILL_RULE_WINDING; _cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias); CGPathRelease (path_for_unbounded); } ND((stderr, "-- stroke\n")); return rv; } #if CAIRO_HAS_QUARTZ_FONT static cairo_int_status_t _cairo_quartz_surface_show_glyphs (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_glyph_t *glyphs, int num_glyphs, cairo_scaled_font_t *scaled_font, int *remaining_glyphs) { CGAffineTransform textTransform, ctm; #define STATIC_BUF_SIZE 64 CGGlyph glyphs_static[STATIC_BUF_SIZE]; CGSize cg_advances_static[STATIC_BUF_SIZE]; CGGlyph *cg_glyphs = &glyphs_static[0]; CGSize *cg_advances = &cg_advances_static[0]; cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; cairo_quartz_action_t action; float xprev, yprev; int i; CGFontRef cgfref = NULL; cairo_bool_t isClipping = FALSE; cairo_bool_t didForceFontSmoothing = FALSE; if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; if (num_glyphs <= 0) return CAIRO_STATUS_SUCCESS; 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 (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; goto BAIL; } CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op)); /* this doesn't addref */ cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font); CGContextSetFont (surface->cgContext, cgfref); CGContextSetFontSize (surface->cgContext, 1.0); switch (scaled_font->options.antialias) { case CAIRO_ANTIALIAS_SUBPIXEL: CGContextSetShouldAntialias (surface->cgContext, TRUE); CGContextSetShouldSmoothFonts (surface->cgContext, TRUE); if (CGContextSetAllowsFontSmoothingPtr && !CGContextGetAllowsFontSmoothingPtr (surface->cgContext)) { didForceFontSmoothing = TRUE; CGContextSetAllowsFontSmoothingPtr (surface->cgContext, TRUE); } break; case CAIRO_ANTIALIAS_NONE: CGContextSetShouldAntialias (surface->cgContext, FALSE); break; case CAIRO_ANTIALIAS_GRAY: CGContextSetShouldAntialias (surface->cgContext, TRUE); CGContextSetShouldSmoothFonts (surface->cgContext, FALSE); break; case CAIRO_ANTIALIAS_DEFAULT: /* Don't do anything */ break; } if (num_glyphs > STATIC_BUF_SIZE) { cg_glyphs = (CGGlyph*) _cairo_malloc_ab (num_glyphs, sizeof(CGGlyph)); if (cg_glyphs == NULL) { rv = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto BAIL; } cg_advances = (CGSize*) _cairo_malloc_ab (num_glyphs, sizeof(CGSize)); if (cg_advances == NULL) { rv = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto BAIL; } } textTransform = CGAffineTransformMake (scaled_font->font_matrix.xx, scaled_font->font_matrix.yx, scaled_font->font_matrix.xy, scaled_font->font_matrix.yy, 0., 0.); textTransform = CGAffineTransformScale (textTransform, 1.0, -1.0); textTransform = CGAffineTransformConcat (CGAffineTransformMake(scaled_font->ctm.xx, -scaled_font->ctm.yx, -scaled_font->ctm.xy, scaled_font->ctm.yy, 0., 0.), textTransform); CGContextSetTextMatrix (surface->cgContext, textTransform); /* Convert our glyph positions to glyph advances. We need n-1 advances, * since the advance at index 0 is applied after glyph 0. */ xprev = glyphs[0].x; yprev = glyphs[0].y; cg_glyphs[0] = glyphs[0].index; for (i = 1; i < num_glyphs; i++) { float xf = glyphs[i].x; float yf = glyphs[i].y; cg_glyphs[i] = glyphs[i].index; cg_advances[i-1].width = xf - xprev; cg_advances[i-1].height = yf - yprev; xprev = xf; yprev = yf; } if (_cairo_quartz_osx_version >= 0x1050 && isClipping) { /* If we're clipping, OSX 10.5 (at least as of 10.5.2) has a * bug (apple bug ID #5834794) where the glyph * advances/positions are not transformed by the text matrix * if kCGTextClip is being used. So, we pre-transform here. * 10.4 does not have this problem (as of 10.4.11). */ for (i = 0; i < num_glyphs - 1; i++) cg_advances[i] = CGSizeApplyAffineTransform(cg_advances[i], textTransform); } #if 0 for (i = 0; i < num_glyphs; i++) { ND((stderr, "[%d: %d %f,%f]\n", i, cg_glyphs[i], cg_advances[i].width, cg_advances[i].height)); } #endif /* Translate to the first glyph's position before drawing */ ctm = CGContextGetCTM (surface->cgContext); CGContextTranslateCTM (surface->cgContext, glyphs[0].x, glyphs[0].y); CGContextShowGlyphsWithAdvances (surface->cgContext, cg_glyphs, cg_advances, num_glyphs); CGContextSetCTM (surface->cgContext, ctm); if (action == DO_IMAGE || action == DO_TILED_IMAGE) { CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height); CGContextScaleCTM (surface->cgContext, 1, -1); if (action == DO_IMAGE) CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage); else CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage); } else if (action == DO_SHADING) { CGContextConcatCTM (surface->cgContext, surface->sourceTransform); CGContextDrawShading (surface->cgContext, surface->sourceShading); } BAIL: _cairo_quartz_teardown_source (surface, source); if (didForceFontSmoothing) CGContextSetAllowsFontSmoothingPtr (surface->cgContext, FALSE); CGContextRestoreGState (surface->cgContext); if (rv == CAIRO_STATUS_SUCCESS && cgfref && !_cairo_operator_bounded_by_mask (op)) { unbounded_op_data_t ub; ub.op = UNBOUNDED_SHOW_GLYPHS; ub.u.show_glyphs.isClipping = isClipping; ub.u.show_glyphs.cg_glyphs = cg_glyphs; ub.u.show_glyphs.cg_advances = cg_advances; ub.u.show_glyphs.nglyphs = num_glyphs; ub.u.show_glyphs.textTransform = textTransform; ub.u.show_glyphs.font = cgfref; ub.u.show_glyphs.origin = CGPointMake (glyphs[0].x, glyphs[0].y); _cairo_quartz_fixup_unbounded_operation (surface, &ub, scaled_font->options.antialias); } if (cg_advances != &cg_advances_static[0]) { free (cg_advances); } if (cg_glyphs != &glyphs_static[0]) { free (cg_glyphs); } return rv; } #endif /* CAIRO_HAS_QUARTZ_FONT */ static cairo_int_status_t _cairo_quartz_surface_mask_with_surface (cairo_quartz_surface_t *surface, cairo_operator_t op, cairo_pattern_t *source, cairo_surface_pattern_t *mask) { cairo_rectangle_int_t extents; CGRect rect; CGImageRef img; cairo_surface_t *pat_surf = mask->surface; cairo_status_t status = CAIRO_STATUS_SUCCESS; CGAffineTransform ctm, mask_matrix; status = _cairo_surface_get_extents (pat_surf, &extents); if (status) return status; // everything would be masked out, so do nothing if (extents.width == 0 || extents.height == 0) return CAIRO_STATUS_SUCCESS; status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img); if (status == CAIRO_STATUS_SUCCESS && img == NULL) return CAIRO_STATUS_SUCCESS; if (status) return status; rect = CGRectMake (0.0f, 0.0f, extents.width, extents.height); CGContextSaveGState (surface->cgContext); /* ClipToMask is essentially drawing an image, so we need to flip the CTM * to get the image to appear oriented the right way */ ctm = CGContextGetCTM (surface->cgContext); _cairo_quartz_cairo_matrix_to_quartz (&mask->base.matrix, &mask_matrix); CGContextConcatCTM (surface->cgContext, CGAffineTransformInvert(mask_matrix)); CGContextTranslateCTM (surface->cgContext, 0.0f, rect.size.height); CGContextScaleCTM (surface->cgContext, 1.0f, -1.0f); CGContextClipToMaskPtr (surface->cgContext, rect, img); CGContextSetCTM (surface->cgContext, ctm); status = _cairo_quartz_surface_paint (surface, op, source); CGContextRestoreGState (surface->cgContext); if (!_cairo_operator_bounded_by_mask (op)) { unbounded_op_data_t ub; ub.op = UNBOUNDED_MASK; ub.u.mask.mask = img; ub.u.mask.maskTransform = CGAffineTransformInvert(mask_matrix); _cairo_quartz_fixup_unbounded_operation (surface, &ub, CAIRO_ANTIALIAS_NONE); } CGImageRelease (img); return status; } /* This is somewhat less than ideal, but it gets the job done; * it would be better to avoid calling back into cairo. This * creates a temporary surface to use as the mask. */ static cairo_int_status_t _cairo_quartz_surface_mask_with_generic (cairo_quartz_surface_t *surface, cairo_operator_t op, cairo_pattern_t *source, cairo_pattern_t *mask) { int width = surface->extents.width - surface->extents.x; int height = surface->extents.height - surface->extents.y; cairo_surface_t *gradient_surf = NULL; cairo_t *gradient_surf_cr = NULL; cairo_surface_pattern_t surface_pattern; cairo_int_status_t status; /* Render the gradient to a surface */ gradient_surf = cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32, width, height); gradient_surf_cr = cairo_create(gradient_surf); cairo_set_source (gradient_surf_cr, mask); cairo_set_operator (gradient_surf_cr, CAIRO_OPERATOR_SOURCE); cairo_paint (gradient_surf_cr); status = cairo_status (gradient_surf_cr); cairo_destroy (gradient_surf_cr); if (status) goto BAIL; _cairo_pattern_init_for_surface (&surface_pattern, gradient_surf); status = _cairo_quartz_surface_mask_with_surface (surface, op, source, &surface_pattern); _cairo_pattern_fini (&surface_pattern.base); BAIL: if (gradient_surf) cairo_surface_destroy (gradient_surf); return status; } static cairo_int_status_t _cairo_quartz_surface_mask (void *abstract_surface, cairo_operator_t op, cairo_pattern_t *source, cairo_pattern_t *mask) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; cairo_int_status_t rv = CAIRO_STATUS_SUCCESS; ND((stderr, "%p _cairo_quartz_surface_mask op %d source->type %d mask->type %d\n", surface, op, source->type, mask->type)); if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; if (mask->type == CAIRO_PATTERN_TYPE_SOLID) { /* This is easy; we just need to paint with the alpha. */ cairo_solid_pattern_t *solid_mask = (cairo_solid_pattern_t *) mask; CGContextSetAlpha (surface->cgContext, solid_mask->color.alpha); rv = _cairo_quartz_surface_paint (surface, op, source); CGContextSetAlpha (surface->cgContext, 1.0); return rv; } /* If we have CGContextClipToMask, we can do more complex masks */ if (CGContextClipToMaskPtr) { /* For these, we can skip creating a temporary surface, since we already have one */ if (mask->type == CAIRO_PATTERN_TYPE_SURFACE && mask->extend == CAIRO_EXTEND_NONE) return _cairo_quartz_surface_mask_with_surface (surface, op, source, (cairo_surface_pattern_t *) mask); return _cairo_quartz_surface_mask_with_generic (surface, op, source, mask); } /* So, CGContextClipToMask is not present in 10.3.9, so we're * doomed; if we have imageData, we can do fallback, otherwise * just pretend success. */ if (surface->imageData) return CAIRO_INT_STATUS_UNSUPPORTED; return CAIRO_STATUS_SUCCESS; } static cairo_int_status_t _cairo_quartz_surface_intersect_clip_path (void *abstract_surface, cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, double tolerance, cairo_antialias_t antialias) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; quartz_stroke_t stroke; cairo_status_t status; ND((stderr, "%p _cairo_quartz_surface_intersect_clip_path path: %p\n", surface, path)); if (IS_EMPTY(surface)) return CAIRO_STATUS_SUCCESS; if (path == NULL) { /* If we're being asked to reset the clip, we can only do it * by restoring the gstate to our previous saved one, and * saving it again. * * Note that this assumes that ALL quartz surface creation * functions will do a SaveGState first; we do this in create_internal. */ CGContextRestoreGState (surface->cgContext); CGContextSaveGState (surface->cgContext); } else { CGContextBeginPath (surface->cgContext); stroke.cgContext = surface->cgContext; stroke.ctm_inverse = NULL; CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE)); /* path must not be empty. */ CGContextMoveToPoint (surface->cgContext, 0, 0); status = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke); if (status) return status; if (fill_rule == CAIRO_FILL_RULE_WINDING) CGContextClip (surface->cgContext); else CGContextEOClip (surface->cgContext); } ND((stderr, "-- intersect_clip_path\n")); return CAIRO_STATUS_SUCCESS; } // XXXtodo implement show_page; need to figure out how to handle begin/end static const struct _cairo_surface_backend cairo_quartz_surface_backend = { CAIRO_SURFACE_TYPE_QUARTZ, _cairo_quartz_surface_create_similar, _cairo_quartz_surface_finish, _cairo_quartz_surface_acquire_source_image, _cairo_quartz_surface_release_source_image, _cairo_quartz_surface_acquire_dest_image, _cairo_quartz_surface_release_dest_image, _cairo_quartz_surface_clone_similar, NULL, /* composite */ NULL, /* fill_rectangles */ NULL, /* composite_trapezoids */ NULL, /* copy_page */ NULL, /* show_page */ NULL, /* set_clip_region */ _cairo_quartz_surface_intersect_clip_path, _cairo_quartz_surface_get_extents, NULL, /* old_show_glyphs */ NULL, /* get_font_options */ NULL, /* flush */ NULL, /* mark_dirty_rectangle */ NULL, /* scaled_font_fini */ NULL, /* scaled_glyph_fini */ _cairo_quartz_surface_paint, _cairo_quartz_surface_mask, _cairo_quartz_surface_stroke, _cairo_quartz_surface_fill, #if CAIRO_HAS_QUARTZ_FONT _cairo_quartz_surface_show_glyphs, #else NULL, /* show_glyphs */ #endif NULL, /* snapshot */ NULL, /* is_similar */ NULL, /* reset */ NULL /* fill_stroke */ }; cairo_quartz_surface_t * _cairo_quartz_surface_create_internal (CGContextRef cgContext, cairo_content_t content, unsigned int width, unsigned int height) { cairo_quartz_surface_t *surface; quartz_ensure_symbols(); /* Init the base surface */ surface = malloc(sizeof(cairo_quartz_surface_t)); if (surface == NULL) return (cairo_quartz_surface_t*) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); memset(surface, 0, sizeof(cairo_quartz_surface_t)); _cairo_surface_init(&surface->base, &cairo_quartz_surface_backend, content); /* Save our extents */ surface->extents.x = surface->extents.y = 0; surface->extents.width = width; surface->extents.height = height; if (IS_EMPTY(surface)) { surface->cgContext = NULL; surface->cgContextBaseCTM = CGAffineTransformIdentity; surface->imageData = NULL; return surface; } /* Save so we can always get back to a known-good CGContext -- this is * required for proper behaviour of intersect_clip_path(NULL) */ CGContextSaveGState (cgContext); surface->cgContext = cgContext; surface->cgContextBaseCTM = CGContextGetCTM (cgContext); surface->imageData = NULL; surface->imageSurfaceEquiv = NULL; return surface; } /** * cairo_quartz_surface_create_for_cg_context * @cgContext: the existing CGContext for which to create the surface * @width: width of the surface, in pixels * @height: height of the surface, in pixels * * Creates a Quartz surface that wraps the given CGContext. The * CGContext is assumed to be in the standard Cairo coordinate space * (that is, with the origin at the upper left and the Y axis * increasing downward). If the CGContext is in the Quartz coordinate * space (with the origin at the bottom left), then it should be * flipped before this function is called. The flip can be accomplished * using a translate and a scale; for example: * * * CGContextTranslateCTM (cgContext, 0.0, height); * CGContextScaleCTM (cgContext, 1.0, -1.0); * * * All Cairo operations are implemented in terms of Quartz operations, * as long as Quartz-compatible elements are used (such as Quartz fonts). * * Return value: the newly created Cairo surface. * * Since: 1.4 **/ cairo_surface_t * cairo_quartz_surface_create_for_cg_context (CGContextRef cgContext, unsigned int width, unsigned int height) { cairo_quartz_surface_t *surf; CGContextRetain (cgContext); surf = _cairo_quartz_surface_create_internal (cgContext, CAIRO_CONTENT_COLOR_ALPHA, width, height); if (surf->base.status) { CGContextRelease (cgContext); // create_internal will have set an error return (cairo_surface_t*) surf; } return (cairo_surface_t *) surf; } /** * cairo_quartz_surface_create * @format: format of pixels in the surface to create * @width: width of the surface, in pixels * @height: height of the surface, in pixels * * Creates a Quartz surface backed by a CGBitmap. The surface is * created using the Device RGB (or Device Gray, for A8) color space. * All Cairo operations, including those that require software * rendering, will succeed on this surface. * * Return value: the newly created surface. * * Since: 1.4 **/ cairo_surface_t * cairo_quartz_surface_create (cairo_format_t format, unsigned int width, unsigned int height) { cairo_quartz_surface_t *surf; CGContextRef cgc; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; void *imageData; int stride; int bitsPerComponent; // verify width and height of surface if (!_cairo_quartz_verify_surface_size(width, height)) return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); if (width == 0 || height == 0) { return (cairo_surface_t*) _cairo_quartz_surface_create_internal (NULL, _cairo_content_from_format (format), width, height); } if (format == CAIRO_FORMAT_ARGB32 || format == CAIRO_FORMAT_RGB24) { cgColorspace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGBitmapByteOrder32Host; if (format == CAIRO_FORMAT_ARGB32) bitinfo |= kCGImageAlphaPremultipliedFirst; else bitinfo |= kCGImageAlphaNoneSkipFirst; bitsPerComponent = 8; /* The Apple docs say that for best performance, the stride and the data * pointer should be 16-byte aligned. malloc already aligns to 16-bytes, * so we don't have to anything special on allocation. */ stride = width * 4; stride += (16 - (stride & 15)) & 15; } else if (format == CAIRO_FORMAT_A8) { cgColorspace = CGColorSpaceCreateDeviceGray(); if (width % 4 == 0) stride = width; else stride = (width & ~3) + 4; bitinfo = kCGImageAlphaNone; bitsPerComponent = 8; } else if (format == CAIRO_FORMAT_A1) { /* I don't think we can usefully support this, as defined by * cairo_format_t -- these are 1-bit pixels stored in 32-bit * quantities. */ return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); } else { return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); } imageData = _cairo_malloc_ab (height, stride); if (!imageData) { CGColorSpaceRelease (cgColorspace); return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } /* zero the memory to match the image surface behaviour */ memset (imageData, 0, height * stride); cgc = CGBitmapContextCreate (imageData, width, height, bitsPerComponent, stride, cgColorspace, bitinfo); CGColorSpaceRelease (cgColorspace); if (!cgc) { free (imageData); return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } /* flip the Y axis */ CGContextTranslateCTM (cgc, 0.0, height); CGContextScaleCTM (cgc, 1.0, -1.0); surf = _cairo_quartz_surface_create_internal (cgc, _cairo_content_from_format (format), width, height); if (surf->base.status) { CGContextRelease (cgc); free (imageData); // create_internal will have set an error return (cairo_surface_t*) surf; } surf->imageData = imageData; surf->imageSurfaceEquiv = cairo_image_surface_create_for_data (imageData, format, width, height, stride); return (cairo_surface_t *) surf; } /** * cairo_quartz_surface_get_cg_context * @surface: the Cairo Quartz surface * * Returns the CGContextRef that the given Quartz surface is backed * by. * * Return value: the CGContextRef for the given surface. * * Since: 1.4 **/ CGContextRef cairo_quartz_surface_get_cg_context (cairo_surface_t *surface) { cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t*)surface; if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_QUARTZ) return NULL; return quartz->cgContext; } /* Debug stuff */ #ifdef QUARTZ_DEBUG #include void ExportCGImageToPNGFile(CGImageRef inImageRef, char* dest) { Handle dataRef = NULL; OSType dataRefType; CFStringRef inPath = CFStringCreateWithCString(NULL, dest, kCFStringEncodingASCII); GraphicsExportComponent grex = 0; unsigned long sizeWritten; ComponentResult result; // create the data reference result = QTNewDataReferenceFromFullPathCFString(inPath, kQTNativeDefaultPathStyle, 0, &dataRef, &dataRefType); if (NULL != dataRef && noErr == result) { // get the PNG exporter result = OpenADefaultComponent(GraphicsExporterComponentType, kQTFileTypePNG, &grex); if (grex) { // tell the exporter where to find its source image result = GraphicsExportSetInputCGImage(grex, inImageRef); if (noErr == result) { // tell the exporter where to save the exporter image result = GraphicsExportSetOutputDataReference(grex, dataRef, dataRefType); if (noErr == result) { // write the PNG file result = GraphicsExportDoExport(grex, &sizeWritten); } } // remember to close the component CloseComponent(grex); } // remember to dispose of the data reference handle DisposeHandle(dataRef); } } void quartz_image_to_png (CGImageRef imgref, char *dest) { static int sctr = 0; char sptr[] = "/Users/vladimir/Desktop/barXXXXX.png"; if (dest == NULL) { fprintf (stderr, "** Writing %p to bar%d\n", imgref, sctr); sprintf (sptr, "/Users/vladimir/Desktop/bar%d.png", sctr); sctr++; dest = sptr; } ExportCGImageToPNGFile(imgref, dest); } void quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest) { static int sctr = 0; char sptr[] = "/Users/vladimir/Desktop/fooXXXXX.png"; if (nq->base.type != CAIRO_SURFACE_TYPE_QUARTZ) { fprintf (stderr, "** quartz_surface_to_png: surface %p isn't quartz!\n", nq); return; } if (dest == NULL) { fprintf (stderr, "** Writing %p to foo%d\n", nq, sctr); sprintf (sptr, "/Users/vladimir/Desktop/foo%d.png", sctr); sctr++; dest = sptr; } CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext); if (imgref == NULL) { fprintf (stderr, "quartz surface at %p is not a bitmap context!\n", nq); return; } ExportCGImageToPNGFile(imgref, dest); CGImageRelease(imgref); } #endif /* QUARTZ_DEBUG */