1
/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
2
/* cairo - a vector graphics library with display and print output
4
* Copyright ļæ½ 2006, 2007 Mozilla Corporation
6
* This library is free software; you can redistribute it and/or
7
* modify it either under the terms of the GNU Lesser General Public
8
* License version 2.1 as published by the Free Software Foundation
9
* (the "LGPL") or, at your option, under the terms of the Mozilla
10
* Public License Version 1.1 (the "MPL"). If you do not alter this
11
* notice, a recipient may use your version of this file under either
12
* the MPL or the LGPL.
14
* You should have received a copy of the LGPL along with this library
15
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
* You should have received a copy of the MPL along with this library
18
* in the file COPYING-MPL-1.1
20
* The contents of this file are subject to the Mozilla Public License
21
* Version 1.1 (the "License"); you may not use this file except in
22
* compliance with the License. You may obtain a copy of the License at
23
* http://www.mozilla.org/MPL/
25
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
26
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
27
* the specific language governing rights and limitations.
29
* The Original Code is the cairo graphics library.
31
* The Initial Developer of the Original Code is Mozilla Corporation.
34
* Vladimir Vukicevic <vladimir@mozilla.com>
39
#include "cairo-quartz-private.h"
43
/* The 10.5 SDK includes a funky new definition of FloatToFixed which
44
* causes all sorts of breakage; so reset to old-style definition
48
#define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
56
#define ND(_x) fprintf _x
58
#define ND(_x) do {} while(0)
61
#define IS_EMPTY(s) ((s)->extents.width == 0 || (s)->extents.height == 0)
63
/* This method is private, but it exists. Its params are are exposed
64
* as args to the NS* method, but not as CG.
66
enum PrivateCGCompositeMode {
67
kPrivateCGCompositeClear = 0,
68
kPrivateCGCompositeCopy = 1,
69
kPrivateCGCompositeSourceOver = 2,
70
kPrivateCGCompositeSourceIn = 3,
71
kPrivateCGCompositeSourceOut = 4,
72
kPrivateCGCompositeSourceAtop = 5,
73
kPrivateCGCompositeDestinationOver = 6,
74
kPrivateCGCompositeDestinationIn = 7,
75
kPrivateCGCompositeDestinationOut = 8,
76
kPrivateCGCompositeDestinationAtop = 9,
77
kPrivateCGCompositeXOR = 10,
78
kPrivateCGCompositePlusDarker = 11, // (max (0, (1-d) + (1-s)))
79
kPrivateCGCompositePlusLighter = 12, // (min (1, s + d))
81
typedef enum PrivateCGCompositeMode PrivateCGCompositeMode;
82
CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
83
CG_EXTERN void CGContextResetCTM (CGContextRef);
84
CG_EXTERN void CGContextSetCTM (CGContextRef, CGAffineTransform);
85
CG_EXTERN void CGContextResetClip (CGContextRef);
86
CG_EXTERN CGSize CGContextGetPatternPhase (CGContextRef);
88
/* We need to work with the 10.3 SDK as well (and 10.3 machines; luckily, 10.3.9
89
* has all the stuff we care about, just some of it isn't exported in the SDK.
91
#ifndef kCGBitmapByteOrder32Host
92
#define USE_10_3_WORKAROUNDS
93
#define kCGBitmapAlphaInfoMask 0x1F
94
#define kCGBitmapByteOrderMask 0x7000
95
#define kCGBitmapByteOrder32Host 0
97
typedef uint32_t CGBitmapInfo;
99
/* public in 10.4, present in 10.3.9 */
100
CG_EXTERN void CGContextReplacePathWithStrokedPath (CGContextRef);
101
CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef);
104
/* Some of these are present in earlier versions of the OS than where
105
* they are public; others are not public at all (CGContextCopyPath,
106
* CGContextReplacePathWithClipPath, many of the getters, etc.)
108
static void (*CGContextClipToMaskPtr) (CGContextRef, CGRect, CGImageRef) = NULL;
109
static void (*CGContextDrawTiledImagePtr) (CGContextRef, CGRect, CGImageRef) = NULL;
110
static unsigned int (*CGContextGetTypePtr) (CGContextRef) = NULL;
111
static void (*CGContextSetShouldAntialiasFontsPtr) (CGContextRef, bool) = NULL;
112
static bool (*CGContextGetShouldAntialiasFontsPtr) (CGContextRef) = NULL;
113
static bool (*CGContextGetShouldSmoothFontsPtr) (CGContextRef) = NULL;
114
static void (*CGContextSetAllowsFontSmoothingPtr) (CGContextRef, bool) = NULL;
115
static bool (*CGContextGetAllowsFontSmoothingPtr) (CGContextRef) = NULL;
116
static CGPathRef (*CGContextCopyPathPtr) (CGContextRef) = NULL;
117
static void (*CGContextReplacePathWithClipPathPtr) (CGContextRef) = NULL;
119
static SInt32 _cairo_quartz_osx_version = 0x0;
121
static cairo_bool_t _cairo_quartz_symbol_lookup_done = FALSE;
128
static void quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest);
129
static void quartz_image_to_png (CGImageRef, char *dest);
132
static cairo_quartz_surface_t *
133
_cairo_quartz_surface_create_internal (CGContextRef cgContext,
134
cairo_content_t content,
136
unsigned int height);
138
/* Load all extra symbols */
139
static void quartz_ensure_symbols(void)
141
if (_cairo_quartz_symbol_lookup_done)
144
CGContextClipToMaskPtr = dlsym(RTLD_DEFAULT, "CGContextClipToMask");
145
CGContextDrawTiledImagePtr = dlsym(RTLD_DEFAULT, "CGContextDrawTiledImage");
146
CGContextGetTypePtr = dlsym(RTLD_DEFAULT, "CGContextGetType");
147
CGContextSetShouldAntialiasFontsPtr = dlsym(RTLD_DEFAULT, "CGContextSetShouldAntialiasFonts");
148
CGContextGetShouldAntialiasFontsPtr = dlsym(RTLD_DEFAULT, "CGContextGetShouldAntialiasFonts");
149
CGContextGetShouldSmoothFontsPtr = dlsym(RTLD_DEFAULT, "CGContextGetShouldSmoothFonts");
150
CGContextCopyPathPtr = dlsym(RTLD_DEFAULT, "CGContextCopyPath");
151
CGContextReplacePathWithClipPathPtr = dlsym(RTLD_DEFAULT, "CGContextReplacePathWithClipPath");
152
CGContextGetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextGetAllowsFontSmoothing");
153
CGContextSetAllowsFontSmoothingPtr = dlsym(RTLD_DEFAULT, "CGContextSetAllowsFontSmoothing");
155
if (Gestalt(gestaltSystemVersion, &_cairo_quartz_osx_version) != noErr) {
157
_cairo_quartz_osx_version = 0x1040;
160
_cairo_quartz_symbol_lookup_done = TRUE;
164
_cairo_quartz_create_cgimage (cairo_format_t format,
169
cairo_bool_t interpolate,
170
CGColorSpaceRef colorSpaceOverride,
171
CGDataProviderReleaseDataCallback releaseCallback,
174
CGImageRef image = NULL;
175
CGDataProviderRef dataProvider = NULL;
176
CGColorSpaceRef colorSpace = colorSpaceOverride;
177
CGBitmapInfo bitinfo;
178
int bitsPerComponent, bitsPerPixel;
181
case CAIRO_FORMAT_ARGB32:
182
if (colorSpace == NULL)
183
colorSpace = CGColorSpaceCreateDeviceRGB();
184
bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
185
bitsPerComponent = 8;
189
case CAIRO_FORMAT_RGB24:
190
if (colorSpace == NULL)
191
colorSpace = CGColorSpaceCreateDeviceRGB();
192
bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
193
bitsPerComponent = 8;
197
/* XXX -- should use CGImageMaskCreate! */
198
case CAIRO_FORMAT_A8:
199
if (colorSpace == NULL)
200
colorSpace = CGColorSpaceCreateDeviceGray();
201
bitinfo = kCGImageAlphaNone;
202
bitsPerComponent = 8;
206
case CAIRO_FORMAT_A1:
211
dataProvider = CGDataProviderCreateWithData (releaseInfo,
219
releaseCallback (releaseInfo, data, height * stride);
223
image = CGImageCreate (width, height,
232
kCGRenderingIntentDefault);
236
CGDataProviderRelease (dataProvider);
238
if (colorSpace != colorSpaceOverride)
239
CGColorSpaceRelease (colorSpace);
244
static inline cairo_bool_t
245
_cairo_quartz_is_cgcontext_bitmap_context (CGContextRef cgc) {
249
if (CGContextGetTypePtr) {
250
/* 4 is the type value of a bitmap context */
251
if (CGContextGetTypePtr(cgc) == 4)
256
/* This will cause a (harmless) warning to be printed if called on a non-bitmap context */
257
return CGBitmapContextGetBitsPerPixel(cgc) != 0;
260
/* CoreGraphics limitation with flipped CTM surfaces: height must be less than signed 16-bit max */
262
#define CG_MAX_HEIGHT SHRT_MAX
263
#define CG_MAX_WIDTH USHRT_MAX
265
/* is the desired size of the surface within bounds? */
267
_cairo_quartz_verify_surface_size(int width, int height)
269
/* hmmm, allow width, height == 0 ? */
270
if (width < 0 || height < 0) {
274
if (width > CG_MAX_WIDTH || height > CG_MAX_HEIGHT) {
282
* Cairo path -> Quartz path conversion helpers
285
typedef struct _quartz_stroke {
286
CGContextRef cgContext;
287
cairo_matrix_t *ctm_inverse;
290
/* cairo path -> execute in context */
291
static cairo_status_t
292
_cairo_path_to_quartz_context_move_to (void *closure, cairo_point_t *point)
294
//ND((stderr, "moveto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)));
295
quartz_stroke_t *stroke = (quartz_stroke_t *)closure;
296
double x = _cairo_fixed_to_double (point->x);
297
double y = _cairo_fixed_to_double (point->y);
299
if (stroke->ctm_inverse)
300
cairo_matrix_transform_point (stroke->ctm_inverse, &x, &y);
302
CGContextMoveToPoint (stroke->cgContext, x, y);
303
return CAIRO_STATUS_SUCCESS;
306
static cairo_status_t
307
_cairo_path_to_quartz_context_line_to (void *closure, cairo_point_t *point)
309
//ND((stderr, "lineto: %f %f\n", _cairo_fixed_to_double(point->x), _cairo_fixed_to_double(point->y)));
310
quartz_stroke_t *stroke = (quartz_stroke_t *)closure;
311
double x = _cairo_fixed_to_double (point->x);
312
double y = _cairo_fixed_to_double (point->y);
314
if (stroke->ctm_inverse)
315
cairo_matrix_transform_point (stroke->ctm_inverse, &x, &y);
317
if (CGContextIsPathEmpty (stroke->cgContext))
318
CGContextMoveToPoint (stroke->cgContext, x, y);
320
CGContextAddLineToPoint (stroke->cgContext, x, y);
321
return CAIRO_STATUS_SUCCESS;
324
static cairo_status_t
325
_cairo_path_to_quartz_context_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2)
327
//ND( (stderr, "curveto: %f,%f %f,%f %f,%f\n",
328
// _cairo_fixed_to_double(p0->x), _cairo_fixed_to_double(p0->y),
329
// _cairo_fixed_to_double(p1->x), _cairo_fixed_to_double(p1->y),
330
// _cairo_fixed_to_double(p2->x), _cairo_fixed_to_double(p2->y)));
331
quartz_stroke_t *stroke = (quartz_stroke_t *)closure;
332
double x0 = _cairo_fixed_to_double (p0->x);
333
double y0 = _cairo_fixed_to_double (p0->y);
334
double x1 = _cairo_fixed_to_double (p1->x);
335
double y1 = _cairo_fixed_to_double (p1->y);
336
double x2 = _cairo_fixed_to_double (p2->x);
337
double y2 = _cairo_fixed_to_double (p2->y);
339
if (stroke->ctm_inverse) {
340
cairo_matrix_transform_point (stroke->ctm_inverse, &x0, &y0);
341
cairo_matrix_transform_point (stroke->ctm_inverse, &x1, &y1);
342
cairo_matrix_transform_point (stroke->ctm_inverse, &x2, &y2);
345
CGContextAddCurveToPoint (stroke->cgContext,
346
x0, y0, x1, y1, x2, y2);
347
return CAIRO_STATUS_SUCCESS;
350
static cairo_status_t
351
_cairo_path_to_quartz_context_close_path (void *closure)
353
//ND((stderr, "closepath\n"));
354
quartz_stroke_t *stroke = (quartz_stroke_t *)closure;
355
CGContextClosePath (stroke->cgContext);
356
return CAIRO_STATUS_SUCCESS;
359
static cairo_status_t
360
_cairo_quartz_cairo_path_to_quartz_context (cairo_path_fixed_t *path,
361
quartz_stroke_t *stroke)
363
return _cairo_path_fixed_interpret (path,
364
CAIRO_DIRECTION_FORWARD,
365
_cairo_path_to_quartz_context_move_to,
366
_cairo_path_to_quartz_context_line_to,
367
_cairo_path_to_quartz_context_curve_to,
368
_cairo_path_to_quartz_context_close_path,
373
* Misc helpers/callbacks
376
static PrivateCGCompositeMode
377
_cairo_quartz_cairo_operator_to_quartz (cairo_operator_t op)
380
case CAIRO_OPERATOR_CLEAR:
381
return kPrivateCGCompositeClear;
382
case CAIRO_OPERATOR_SOURCE:
383
return kPrivateCGCompositeCopy;
384
case CAIRO_OPERATOR_OVER:
385
return kPrivateCGCompositeSourceOver;
386
case CAIRO_OPERATOR_IN:
387
/* XXX This doesn't match image output */
388
return kPrivateCGCompositeSourceIn;
389
case CAIRO_OPERATOR_OUT:
390
/* XXX This doesn't match image output */
391
return kPrivateCGCompositeSourceOut;
392
case CAIRO_OPERATOR_ATOP:
393
return kPrivateCGCompositeSourceAtop;
395
case CAIRO_OPERATOR_DEST:
396
/* XXX this is handled specially (noop)! */
397
return kPrivateCGCompositeCopy;
398
case CAIRO_OPERATOR_DEST_OVER:
399
return kPrivateCGCompositeDestinationOver;
400
case CAIRO_OPERATOR_DEST_IN:
401
/* XXX This doesn't match image output */
402
return kPrivateCGCompositeDestinationIn;
403
case CAIRO_OPERATOR_DEST_OUT:
404
return kPrivateCGCompositeDestinationOut;
405
case CAIRO_OPERATOR_DEST_ATOP:
406
/* XXX This doesn't match image output */
407
return kPrivateCGCompositeDestinationAtop;
409
case CAIRO_OPERATOR_XOR:
410
return kPrivateCGCompositeXOR; /* This will generate strange results */
411
case CAIRO_OPERATOR_ADD:
412
return kPrivateCGCompositePlusLighter;
413
case CAIRO_OPERATOR_SATURATE:
414
/* XXX This doesn't match image output for SATURATE; there's no equivalent */
415
return kPrivateCGCompositePlusDarker; /* ??? */
419
return kPrivateCGCompositeCopy;
422
static inline CGLineCap
423
_cairo_quartz_cairo_line_cap_to_quartz (cairo_line_cap_t ccap)
426
case CAIRO_LINE_CAP_BUTT: return kCGLineCapButt; break;
427
case CAIRO_LINE_CAP_ROUND: return kCGLineCapRound; break;
428
case CAIRO_LINE_CAP_SQUARE: return kCGLineCapSquare; break;
431
return kCGLineCapButt;
434
static inline CGLineJoin
435
_cairo_quartz_cairo_line_join_to_quartz (cairo_line_join_t cjoin)
438
case CAIRO_LINE_JOIN_MITER: return kCGLineJoinMiter; break;
439
case CAIRO_LINE_JOIN_ROUND: return kCGLineJoinRound; break;
440
case CAIRO_LINE_JOIN_BEVEL: return kCGLineJoinBevel; break;
443
return kCGLineJoinMiter;
446
static inline CGInterpolationQuality
447
_cairo_quartz_filter_to_quartz (cairo_filter_t filter)
450
case CAIRO_FILTER_NEAREST:
451
return kCGInterpolationNone;
453
case CAIRO_FILTER_FAST:
454
return kCGInterpolationLow;
456
case CAIRO_FILTER_BEST:
457
case CAIRO_FILTER_GOOD:
458
case CAIRO_FILTER_BILINEAR:
459
case CAIRO_FILTER_GAUSSIAN:
460
return kCGInterpolationDefault;
463
return kCGInterpolationDefault;
467
_cairo_quartz_cairo_matrix_to_quartz (const cairo_matrix_t *src,
468
CGAffineTransform *dst)
483
CGAffineTransform textTransform;
486
} unbounded_show_glyphs_t;
490
cairo_fill_rule_t fill_rule;
491
} unbounded_stroke_fill_t;
495
CGAffineTransform maskTransform;
499
UNBOUNDED_STROKE_FILL,
500
UNBOUNDED_SHOW_GLYPHS,
507
unbounded_stroke_fill_t stroke_fill;
508
unbounded_show_glyphs_t show_glyphs;
509
unbounded_mask_t mask;
511
} unbounded_op_data_t;
514
_cairo_quartz_fixup_unbounded_operation (cairo_quartz_surface_t *surface,
515
unbounded_op_data_t *op,
516
cairo_antialias_t antialias)
518
CGColorSpaceRef gray;
519
CGRect clipBox, clipBoxRound;
521
CGImageRef maskImage;
523
if (!CGContextClipToMaskPtr)
526
clipBox = CGContextGetClipBoundingBox (surface->cgContext);
527
clipBoxRound = CGRectIntegral (clipBox);
529
gray = CGColorSpaceCreateDeviceGray ();
530
cgc = CGBitmapContextCreate (NULL,
531
clipBoxRound.size.width,
532
clipBoxRound.size.height,
534
clipBoxRound.size.width,
537
CGColorSpaceRelease (gray);
542
/* We want to mask out whatever we just rendered, so we fill the
543
* surface with white, and then we'll render with black.
545
CGContextSetRGBFillColor (cgc, 1.0f, 1.0f, 1.0f, 1.0f);
546
CGContextFillRect (cgc, CGRectMake (0, 0, clipBoxRound.size.width, clipBoxRound.size.height));
548
CGContextSetRGBFillColor (cgc, 0.0f, 0.0f, 0.0f, 1.0f);
549
CGContextSetShouldAntialias (cgc, (antialias != CAIRO_ANTIALIAS_NONE));
551
CGContextTranslateCTM (cgc, -clipBoxRound.origin.x, -clipBoxRound.origin.y);
553
/* We need to either render the path that was given to us, or the glyph op */
554
if (op->op == UNBOUNDED_STROKE_FILL) {
555
CGContextBeginPath (cgc);
556
CGContextAddPath (cgc, op->u.stroke_fill.cgPath);
558
if (op->u.stroke_fill.fill_rule == CAIRO_FILL_RULE_WINDING)
559
CGContextFillPath (cgc);
561
CGContextEOFillPath (cgc);
562
} else if (op->op == UNBOUNDED_SHOW_GLYPHS) {
563
CGContextSetFont (cgc, op->u.show_glyphs.font);
564
CGContextSetFontSize (cgc, 1.0);
565
CGContextSetTextMatrix (cgc, op->u.show_glyphs.textTransform);
566
CGContextTranslateCTM (cgc, op->u.show_glyphs.origin.x, op->u.show_glyphs.origin.y);
568
if (op->u.show_glyphs.isClipping) {
569
/* Note that the comment in show_glyphs about kCGTextClip
570
* and the text transform still applies here; however, the
571
* cg_advances we have were already transformed, so we
572
* don't have to do anything. */
573
CGContextSetTextDrawingMode (cgc, kCGTextClip);
574
CGContextSaveGState (cgc);
577
CGContextShowGlyphsWithAdvances (cgc,
578
op->u.show_glyphs.cg_glyphs,
579
op->u.show_glyphs.cg_advances,
580
op->u.show_glyphs.nglyphs);
582
if (op->u.show_glyphs.isClipping) {
583
CGContextFillRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height));
584
CGContextRestoreGState (cgc);
586
} else if (op->op == UNBOUNDED_MASK) {
587
CGContextSaveGState (cgc);
588
CGContextConcatCTM (cgc, op->u.mask.maskTransform);
589
CGContextClipToMask (cgc, CGRectMake (0.0f, 0.0f,
590
CGImageGetWidth(op->u.mask.mask), CGImageGetHeight(op->u.mask.mask)),
592
CGContextFillRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height));
593
CGContextRestoreGState (cgc);
596
/* Also mask out the portion of the clipbox that we rounded out, if any */
597
if (!CGRectEqualToRect (clipBox, clipBoxRound)) {
598
CGContextBeginPath (cgc);
599
CGContextAddRect (cgc, CGRectMake (0.0, 0.0, clipBoxRound.size.width, clipBoxRound.size.height));
600
CGContextAddRect (cgc, CGRectMake (clipBoxRound.origin.x - clipBox.origin.x,
601
clipBoxRound.origin.y - clipBox.origin.y,
603
clipBox.size.height));
604
CGContextEOFillPath (cgc);
607
maskImage = CGBitmapContextCreateImage (cgc);
608
CGContextRelease (cgc);
613
/* Then render with the mask */
614
CGContextSaveGState (surface->cgContext);
616
CGContextSetCompositeOperation (surface->cgContext, kPrivateCGCompositeCopy);
617
CGContextClipToMaskPtr (surface->cgContext, clipBoxRound, maskImage);
618
CGImageRelease (maskImage);
620
/* Finally, clear out the entire clipping region through our mask */
621
CGContextClearRect (surface->cgContext, clipBoxRound);
623
CGContextRestoreGState (surface->cgContext);
627
* Source -> Quartz setup and finish functions
631
ComputeGradientValue (void *info, const float *in, float *out)
634
cairo_gradient_pattern_t *grad = (cairo_gradient_pattern_t*) info;
637
/* Put fdist back in the 0.0..1.0 range if we're doing
640
if (grad->base.extend == CAIRO_EXTEND_REPEAT) {
641
fdist = fdist - floor(fdist);
642
} else if (grad->base.extend == CAIRO_EXTEND_REFLECT) {
643
fdist = fmod(fabs(fdist), 2.0);
649
for (i = 0; i < grad->n_stops; i++) {
650
if (grad->stops[i].offset > fdist)
654
if (i == 0 || i == grad->n_stops) {
655
if (i == grad->n_stops)
657
out[0] = grad->stops[i].color.red;
658
out[1] = grad->stops[i].color.green;
659
out[2] = grad->stops[i].color.blue;
660
out[3] = grad->stops[i].color.alpha;
662
float ax = grad->stops[i-1].offset;
663
float bx = grad->stops[i].offset - ax;
664
float bp = (fdist - ax)/bx;
668
grad->stops[i-1].color.red * ap +
669
grad->stops[i].color.red * bp;
671
grad->stops[i-1].color.green * ap +
672
grad->stops[i].color.green * bp;
674
grad->stops[i-1].color.blue * ap +
675
grad->stops[i].color.blue * bp;
677
grad->stops[i-1].color.alpha * ap +
678
grad->stops[i].color.alpha * bp;
683
CreateGradientFunction (cairo_gradient_pattern_t *gpat)
685
float input_value_range[2] = { 0.f, 1.f };
686
float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
687
CGFunctionCallbacks callbacks = {
688
0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
691
return CGFunctionCreate (gpat,
700
CreateRepeatingGradientFunction (cairo_quartz_surface_t *surface,
701
cairo_gradient_pattern_t *gpat,
702
CGPoint *start, CGPoint *end,
703
CGAffineTransform matrix)
705
float input_value_range[2];
706
float output_value_ranges[8] = { 0.f, 1.f, 0.f, 1.f, 0.f, 1.f, 0.f, 1.f };
707
CGFunctionCallbacks callbacks = {
708
0, ComputeGradientValue, (CGFunctionReleaseInfoCallback) cairo_pattern_destroy
711
CGPoint mstart, mend;
714
int x_rep_start = 0, x_rep_end = 0;
715
int y_rep_start = 0, y_rep_end = 0;
717
int rep_start, rep_end;
719
// figure out how many times we'd need to repeat the gradient pattern
720
// to cover the whole (transformed) surface area
721
mstart = CGPointApplyAffineTransform (*start, matrix);
722
mend = CGPointApplyAffineTransform (*end, matrix);
724
dx = fabs (mend.x - mstart.x);
725
dy = fabs (mend.y - mstart.y);
728
x_rep_start = (int) ceil(MIN(mstart.x, mend.x) / dx);
729
x_rep_end = (int) ceil((surface->extents.width - MAX(mstart.x, mend.x)) / dx);
731
if (mend.x < mstart.x) {
732
int swap = x_rep_end;
733
x_rep_end = x_rep_start;
739
y_rep_start = (int) ceil(MIN(mstart.y, mend.y) / dy);
740
y_rep_end = (int) ceil((surface->extents.width - MAX(mstart.y, mend.y)) / dy);
742
if (mend.y < mstart.y) {
743
int swap = y_rep_end;
744
y_rep_end = y_rep_start;
749
rep_start = MAX(x_rep_start, y_rep_start);
750
rep_end = MAX(x_rep_end, y_rep_end);
752
// extend the line between start and end by rep_start times from the start
753
// and rep_end times from the end
755
dx = end->x - start->x;
756
dy = end->y - start->y;
758
start->x = start->x - dx * rep_start;
759
start->y = start->y - dy * rep_start;
761
end->x = end->x + dx * rep_end;
762
end->y = end->y + dy * rep_end;
764
// set the input range for the function -- the function knows how to
765
// map values outside of 0.0 .. 1.0 to that range for REPEAT/REFLECT.
766
input_value_range[0] = 0.0 - 1.0 * rep_start;
767
input_value_range[1] = 1.0 + 1.0 * rep_end;
769
return CGFunctionCreate (gpat,
777
/* Obtain a CGImageRef from a #cairo_surface_t * */
780
DataProviderReleaseCallback (void *info, const void *data, size_t size)
782
cairo_surface_t *surface = (cairo_surface_t *) info;
783
cairo_surface_destroy (surface);
786
static cairo_status_t
787
_cairo_surface_to_cgimage (cairo_surface_t *target,
788
cairo_surface_t *source,
789
CGImageRef *image_out)
791
cairo_status_t status = CAIRO_STATUS_SUCCESS;
792
cairo_surface_type_t stype = cairo_surface_get_type (source);
793
cairo_image_surface_t *isurf;
797
if (stype == CAIRO_SURFACE_TYPE_QUARTZ_IMAGE) {
798
cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t *) source;
799
*image_out = CGImageRetain (surface->image);
800
return CAIRO_STATUS_SUCCESS;
803
if (stype == CAIRO_SURFACE_TYPE_QUARTZ) {
804
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) source;
805
if (IS_EMPTY(surface)) {
807
return CAIRO_STATUS_SUCCESS;
810
if (_cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) {
811
*image_out = CGBitmapContextCreateImage (surface->cgContext);
813
return CAIRO_STATUS_SUCCESS;
817
if (stype != CAIRO_SURFACE_TYPE_IMAGE) {
818
status = _cairo_surface_acquire_source_image (source, &isurf, &image_extra);
822
isurf = (cairo_image_surface_t *) source;
825
if (isurf->width == 0 || isurf->height == 0) {
828
cairo_image_surface_t *isurf_snap = NULL;
829
isurf_snap = (cairo_image_surface_t*) _cairo_surface_snapshot ((cairo_surface_t*) isurf);
830
if (isurf_snap == NULL)
831
return CAIRO_STATUS_NO_MEMORY;
833
if (isurf_snap->base.type != CAIRO_SURFACE_TYPE_IMAGE)
834
return CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
836
image = _cairo_quartz_create_cgimage (isurf_snap->format,
843
DataProviderReleaseCallback,
849
if ((cairo_surface_t*) isurf != source)
850
_cairo_surface_release_source_image (source, isurf, image_extra);
855
/* Generic #cairo_pattern_t -> CGPattern function */
860
cairo_bool_t do_reflect;
861
} SurfacePatternDrawInfo;
864
SurfacePatternDrawFunc (void *ainfo, CGContextRef context)
866
SurfacePatternDrawInfo *info = (SurfacePatternDrawInfo*) ainfo;
868
CGContextTranslateCTM (context, 0, info->imageBounds.size.height);
869
CGContextScaleCTM (context, 1, -1);
871
CGContextDrawImage (context, info->imageBounds, info->image);
872
if (info->do_reflect) {
873
/* draw 3 more copies of the image, flipped.
874
* DrawImage draws the image according to the current Y-direction into the rectangle given
875
* (imageBounds); at the time of the first DrawImage above, the origin is at the bottom left
876
* of the base image position, and the Y axis is extending upwards.
879
/* Make the y axis extend downwards, and draw a flipped image below */
880
CGContextScaleCTM (context, 1, -1);
881
CGContextDrawImage (context, info->imageBounds, info->image);
883
/* Shift over to the right, and flip vertically (translation is 2x,
884
* since we'll be flipping and thus rendering the rectangle "backwards"
886
CGContextTranslateCTM (context, 2 * info->imageBounds.size.width, 0);
887
CGContextScaleCTM (context, -1, 1);
888
CGContextDrawImage (context, info->imageBounds, info->image);
890
/* Then unflip the Y-axis again, and draw the image above the point. */
891
CGContextScaleCTM (context, 1, -1);
892
CGContextDrawImage (context, info->imageBounds, info->image);
898
SurfacePatternReleaseInfoFunc (void *ainfo)
900
SurfacePatternDrawInfo *info = (SurfacePatternDrawInfo*) ainfo;
902
CGImageRelease (info->image);
906
static cairo_int_status_t
907
_cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t *dest,
908
cairo_pattern_t *apattern,
911
cairo_surface_pattern_t *spattern;
912
cairo_surface_t *pat_surf;
913
cairo_rectangle_int_t extents;
917
CGAffineTransform ptransform, stransform;
918
CGPatternCallbacks cb = { 0,
919
SurfacePatternDrawFunc,
920
SurfacePatternReleaseInfoFunc };
921
SurfacePatternDrawInfo *info;
923
cairo_status_t status;
927
/* SURFACE is the only type we'll handle here */
928
if (apattern->type != CAIRO_PATTERN_TYPE_SURFACE)
929
return CAIRO_INT_STATUS_UNSUPPORTED;
931
spattern = (cairo_surface_pattern_t *) apattern;
932
pat_surf = spattern->surface;
934
status = _cairo_surface_get_extents (pat_surf, &extents);
938
status = _cairo_surface_to_cgimage ((cairo_surface_t*) dest, pat_surf, &image);
939
if (status != CAIRO_STATUS_SUCCESS)
940
return CAIRO_INT_STATUS_UNSUPPORTED;
943
return CAIRO_INT_STATUS_NOTHING_TO_DO;
945
info = malloc(sizeof(SurfacePatternDrawInfo));
947
return CAIRO_STATUS_NO_MEMORY;
949
/* XXX -- if we're printing, we may need to call CGImageCreateCopy to make sure
950
* that the data will stick around for this image when the printer gets to it.
951
* Otherwise, the underlying data store may disappear from under us!
953
* _cairo_surface_to_cgimage will copy when it converts non-Quartz surfaces,
954
* since the Quartz surfaces have a higher chance of sticking around. If the
955
* source is a quartz image surface, then it's set up to retain a ref to the
956
* image surface that it's backed by.
960
info->imageBounds = CGRectMake (0, 0, extents.width, extents.height);
962
pbounds.origin.x = 0;
963
pbounds.origin.y = 0;
965
if (spattern->base.extend == CAIRO_EXTEND_REFLECT) {
966
pbounds.size.width = 2.0 * extents.width;
967
pbounds.size.height = 2.0 * extents.height;
968
info->do_reflect = TRUE;
970
pbounds.size.width = extents.width;
971
pbounds.size.height = extents.height;
973
rw = pbounds.size.width;
974
rh = pbounds.size.height;
976
m = spattern->base.matrix;
977
cairo_matrix_invert(&m);
978
_cairo_quartz_cairo_matrix_to_quartz (&m, &stransform);
980
/* The pattern matrix is relative to the bottom left, again; the
981
* incoming cairo pattern matrix is relative to the upper left.
982
* So we take the pattern matrix and the original context matrix,
983
* which gives us the correct base translation/y flip.
985
ptransform = CGAffineTransformConcat(stransform, dest->cgContextBaseCTM);
988
ND((stderr, " pbounds: %f %f %f %f\n", pbounds.origin.x, pbounds.origin.y, pbounds.size.width, pbounds.size.height));
989
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));
990
CGAffineTransform xform = CGContextGetCTM(dest->cgContext);
991
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));
994
*cgpat = CGPatternCreate (info,
998
kCGPatternTilingConstantSpacing, /* kCGPatternTilingNoDistortion, */
1002
return CAIRO_STATUS_SUCCESS;
1013
} cairo_quartz_action_t;
1015
static cairo_quartz_action_t
1016
_cairo_quartz_setup_fallback_source (cairo_quartz_surface_t *surface,
1017
cairo_pattern_t *source)
1019
CGRect clipBox = CGContextGetClipBoundingBox (surface->cgContext);
1020
CGAffineTransform ctm;
1021
double x0, y0, w, h;
1023
cairo_surface_t *fallback;
1024
cairo_t *fallback_cr;
1027
cairo_status_t status;
1029
if (clipBox.size.width == 0.0f ||
1030
clipBox.size.height == 0.0f)
1033
// the clipBox is in userspace, so:
1034
ctm = CGContextGetCTM (surface->cgContext);
1035
ctm = CGAffineTransformInvert (ctm);
1036
clipBox = CGRectApplyAffineTransform (clipBox, ctm);
1038
// get the Y flip right -- the CTM will always have a Y flip in place
1039
clipBox.origin.y = surface->extents.height - (clipBox.origin.y + clipBox.size.height);
1041
x0 = floor(clipBox.origin.x);
1042
y0 = floor(clipBox.origin.y);
1043
w = ceil(clipBox.origin.x + clipBox.size.width) - x0;
1044
h = ceil(clipBox.origin.y + clipBox.size.height) - y0;
1046
/* Create a temporary the size of the clip surface, and position
1047
* it so that the device origin coincides with the original surface */
1048
fallback = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, (int) w, (int) h);
1049
cairo_surface_set_device_offset (fallback, -x0, -y0);
1051
/* Paint the source onto our temporary */
1052
fallback_cr = cairo_create (fallback);
1053
cairo_set_operator (fallback_cr, CAIRO_OPERATOR_SOURCE);
1054
cairo_set_source (fallback_cr, source);
1055
cairo_paint (fallback_cr);
1056
cairo_destroy (fallback_cr);
1058
status = _cairo_surface_to_cgimage ((cairo_surface_t*) surface, fallback, &img);
1059
if (status == CAIRO_STATUS_SUCCESS && img == NULL)
1062
return DO_UNSUPPORTED;
1064
surface->sourceImageRect = CGRectMake (0.0, 0.0, w, h);
1065
surface->sourceImage = img;
1066
surface->sourceImageSurface = fallback;
1067
surface->sourceTransform = CGAffineTransformMakeTranslation (x0, y0);
1072
static cairo_quartz_action_t
1073
_cairo_quartz_setup_linear_source (cairo_quartz_surface_t *surface,
1074
cairo_linear_pattern_t *lpat)
1076
cairo_pattern_t *abspat = (cairo_pattern_t *) lpat;
1079
CGFunctionRef gradFunc;
1080
CGColorSpaceRef rgb;
1081
bool extend = abspat->extend == CAIRO_EXTEND_PAD;
1083
if (lpat->base.n_stops == 0) {
1084
CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
1085
CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
1089
cairo_pattern_get_matrix (abspat, &mat);
1090
cairo_matrix_invert (&mat);
1091
_cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
1093
rgb = CGColorSpaceCreateDeviceRGB();
1095
start = CGPointMake (_cairo_fixed_to_double (lpat->p1.x),
1096
_cairo_fixed_to_double (lpat->p1.y));
1097
end = CGPointMake (_cairo_fixed_to_double (lpat->p2.x),
1098
_cairo_fixed_to_double (lpat->p2.y));
1100
// ref will be released by the CGShading's destructor
1101
cairo_pattern_reference ((cairo_pattern_t*) lpat);
1103
if (abspat->extend == CAIRO_EXTEND_NONE ||
1104
abspat->extend == CAIRO_EXTEND_PAD)
1106
gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) lpat);
1108
gradFunc = CreateRepeatingGradientFunction (surface,
1109
(cairo_gradient_pattern_t*) lpat,
1110
&start, &end, surface->sourceTransform);
1113
surface->sourceShading = CGShadingCreateAxial (rgb,
1118
CGColorSpaceRelease(rgb);
1119
CGFunctionRelease(gradFunc);
1124
static cairo_quartz_action_t
1125
_cairo_quartz_setup_radial_source (cairo_quartz_surface_t *surface,
1126
cairo_radial_pattern_t *rpat)
1128
cairo_pattern_t *abspat = (cairo_pattern_t *)rpat;
1131
CGFunctionRef gradFunc;
1132
CGColorSpaceRef rgb;
1133
bool extend = abspat->extend == CAIRO_EXTEND_PAD;
1135
if (rpat->base.n_stops == 0) {
1136
CGContextSetRGBStrokeColor (surface->cgContext, 0., 0., 0., 0.);
1137
CGContextSetRGBFillColor (surface->cgContext, 0., 0., 0., 0.);
1141
if (abspat->extend == CAIRO_EXTEND_REPEAT ||
1142
abspat->extend == CAIRO_EXTEND_REFLECT)
1144
/* I started trying to map these to Quartz, but it's much harder
1145
* then the linear case (I think it would involve doing multiple
1146
* Radial shadings). So, instead, let's just render an image
1147
* for pixman to draw the shading into, and use that.
1149
return _cairo_quartz_setup_fallback_source (surface, (cairo_pattern_t*) rpat);
1152
cairo_pattern_get_matrix (abspat, &mat);
1153
cairo_matrix_invert (&mat);
1154
_cairo_quartz_cairo_matrix_to_quartz (&mat, &surface->sourceTransform);
1156
rgb = CGColorSpaceCreateDeviceRGB();
1158
start = CGPointMake (_cairo_fixed_to_double (rpat->c1.x),
1159
_cairo_fixed_to_double (rpat->c1.y));
1160
end = CGPointMake (_cairo_fixed_to_double (rpat->c2.x),
1161
_cairo_fixed_to_double (rpat->c2.y));
1163
// ref will be released by the CGShading's destructor
1164
cairo_pattern_reference ((cairo_pattern_t*) rpat);
1166
gradFunc = CreateGradientFunction ((cairo_gradient_pattern_t*) rpat);
1168
surface->sourceShading = CGShadingCreateRadial (rgb,
1170
_cairo_fixed_to_double (rpat->r1),
1172
_cairo_fixed_to_double (rpat->r2),
1176
CGColorSpaceRelease(rgb);
1177
CGFunctionRelease(gradFunc);
1182
static cairo_quartz_action_t
1183
_cairo_quartz_setup_source (cairo_quartz_surface_t *surface,
1184
cairo_pattern_t *source)
1186
assert (!(surface->sourceImage || surface->sourceShading || surface->sourcePattern));
1188
surface->oldInterpolationQuality = CGContextGetInterpolationQuality (surface->cgContext);
1189
CGContextSetInterpolationQuality (surface->cgContext, _cairo_quartz_filter_to_quartz (source->filter));
1191
if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
1192
cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) source;
1194
CGContextSetRGBStrokeColor (surface->cgContext,
1198
solid->color.alpha);
1199
CGContextSetRGBFillColor (surface->cgContext,
1203
solid->color.alpha);
1208
if (source->type == CAIRO_PATTERN_TYPE_LINEAR) {
1209
cairo_linear_pattern_t *lpat = (cairo_linear_pattern_t *)source;
1210
return _cairo_quartz_setup_linear_source (surface, lpat);
1214
if (source->type == CAIRO_PATTERN_TYPE_RADIAL) {
1215
cairo_radial_pattern_t *rpat = (cairo_radial_pattern_t *)source;
1216
return _cairo_quartz_setup_radial_source (surface, rpat);
1220
if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
1221
(source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT)))
1223
cairo_surface_pattern_t *spat = (cairo_surface_pattern_t *) source;
1224
cairo_surface_t *pat_surf = spat->surface;
1226
cairo_matrix_t m = spat->base.matrix;
1227
cairo_rectangle_int_t extents;
1228
cairo_status_t status;
1229
CGAffineTransform xform;
1231
cairo_fixed_t fw, fh;
1233
status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img);
1234
if (status == CAIRO_STATUS_SUCCESS && img == NULL)
1237
return DO_UNSUPPORTED;
1239
surface->sourceImage = img;
1241
cairo_matrix_invert(&m);
1242
_cairo_quartz_cairo_matrix_to_quartz (&m, &surface->sourceTransform);
1244
status = _cairo_surface_get_extents (pat_surf, &extents);
1246
return DO_UNSUPPORTED;
1248
if (source->extend == CAIRO_EXTEND_NONE) {
1249
surface->sourceImageRect = CGRectMake (0, 0, extents.width, extents.height);
1253
/* Quartz seems to tile images at pixel-aligned regions only -- this
1254
* leads to seams if the image doesn't end up scaling to fill the
1255
* space exactly. The CGPattern tiling approach doesn't have this
1256
* problem. Check if we're going to fill up the space (within some
1257
* epsilon), and if not, fall back to the CGPattern type.
1260
xform = CGAffineTransformConcat (CGContextGetCTM (surface->cgContext),
1261
surface->sourceTransform);
1263
srcRect = CGRectMake (0, 0, extents.width, extents.height);
1264
srcRect = CGRectApplyAffineTransform (srcRect, xform);
1266
fw = _cairo_fixed_from_double (srcRect.size.width);
1267
fh = _cairo_fixed_from_double (srcRect.size.height);
1269
if ((fw & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON &&
1270
(fh & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON)
1272
/* We're good to use DrawTiledImage, but ensure that
1273
* the math works out */
1275
srcRect.size.width = round(srcRect.size.width);
1276
srcRect.size.height = round(srcRect.size.height);
1278
xform = CGAffineTransformInvert (xform);
1280
srcRect = CGRectApplyAffineTransform (srcRect, xform);
1282
surface->sourceImageRect = srcRect;
1284
return DO_TILED_IMAGE;
1287
/* Fall through to generic SURFACE case */
1290
if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
1291
float patternAlpha = 1.0f;
1292
CGColorSpaceRef patternSpace;
1293
CGPatternRef pattern;
1294
cairo_int_status_t status;
1296
status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, &pattern);
1297
if (status == CAIRO_INT_STATUS_NOTHING_TO_DO)
1300
return DO_UNSUPPORTED;
1302
// Save before we change the pattern, colorspace, etc. so that
1303
// we can restore and make sure that quartz releases our
1304
// pattern (which may be stack allocated)
1305
CGContextSaveGState(surface->cgContext);
1307
patternSpace = CGColorSpaceCreatePattern(NULL);
1308
CGContextSetFillColorSpace (surface->cgContext, patternSpace);
1309
CGContextSetFillPattern (surface->cgContext, pattern, &patternAlpha);
1310
CGContextSetStrokeColorSpace (surface->cgContext, patternSpace);
1311
CGContextSetStrokePattern (surface->cgContext, pattern, &patternAlpha);
1312
CGColorSpaceRelease (patternSpace);
1314
/* Quartz likes to munge the pattern phase (as yet unexplained
1315
* why); force it to 0,0 as we've already baked in the correct
1316
* pattern translation into the pattern matrix
1318
CGContextSetPatternPhase (surface->cgContext, CGSizeMake(0,0));
1320
surface->sourcePattern = pattern;
1325
return DO_UNSUPPORTED;
1329
_cairo_quartz_teardown_source (cairo_quartz_surface_t *surface,
1330
cairo_pattern_t *source)
1332
CGContextSetInterpolationQuality (surface->cgContext, surface->oldInterpolationQuality);
1334
if (surface->sourceImage) {
1335
CGImageRelease(surface->sourceImage);
1336
surface->sourceImage = NULL;
1338
cairo_surface_destroy(surface->sourceImageSurface);
1339
surface->sourceImageSurface = NULL;
1342
if (surface->sourceShading) {
1343
CGShadingRelease(surface->sourceShading);
1344
surface->sourceShading = NULL;
1347
if (surface->sourcePattern) {
1348
CGPatternRelease(surface->sourcePattern);
1349
// To tear down the pattern and colorspace
1350
CGContextRestoreGState(surface->cgContext);
1352
surface->sourcePattern = NULL;
1357
* get source/dest image implementation
1360
/* Read the image from the surface's front buffer */
1361
static cairo_int_status_t
1362
_cairo_quartz_get_image (cairo_quartz_surface_t *surface,
1363
cairo_image_surface_t **image_out)
1365
unsigned char *imageData;
1366
cairo_image_surface_t *isurf;
1368
if (IS_EMPTY(surface)) {
1369
*image_out = (cairo_image_surface_t*) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
1370
return CAIRO_STATUS_SUCCESS;
1373
if (surface->imageSurfaceEquiv) {
1374
*image_out = (cairo_image_surface_t*) cairo_surface_reference(surface->imageSurfaceEquiv);
1375
return CAIRO_STATUS_SUCCESS;
1378
if (_cairo_quartz_is_cgcontext_bitmap_context(surface->cgContext)) {
1379
unsigned int stride;
1380
unsigned int bitinfo;
1381
unsigned int bpc, bpp;
1382
CGColorSpaceRef colorspace;
1383
unsigned int color_comps;
1385
imageData = (unsigned char *) CGBitmapContextGetData(surface->cgContext);
1387
#ifdef USE_10_3_WORKAROUNDS
1388
bitinfo = CGBitmapContextGetAlphaInfo (surface->cgContext);
1390
bitinfo = CGBitmapContextGetBitmapInfo (surface->cgContext);
1392
stride = CGBitmapContextGetBytesPerRow (surface->cgContext);
1393
bpp = CGBitmapContextGetBitsPerPixel (surface->cgContext);
1394
bpc = CGBitmapContextGetBitsPerComponent (surface->cgContext);
1396
// let's hope they don't add YUV under us
1397
colorspace = CGBitmapContextGetColorSpace (surface->cgContext);
1398
color_comps = CGColorSpaceGetNumberOfComponents(colorspace);
1400
// XXX TODO: We can handle all of these by converting to
1401
// pixman masks, including non-native-endian masks
1403
return CAIRO_INT_STATUS_UNSUPPORTED;
1405
if (bpp != 32 && bpp != 8)
1406
return CAIRO_INT_STATUS_UNSUPPORTED;
1408
if (color_comps != 3 && color_comps != 1)
1409
return CAIRO_INT_STATUS_UNSUPPORTED;
1411
if (bpp == 32 && color_comps == 3 &&
1412
(bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaPremultipliedFirst &&
1413
(bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host)
1415
isurf = (cairo_image_surface_t *)
1416
cairo_image_surface_create_for_data (imageData,
1417
CAIRO_FORMAT_ARGB32,
1418
surface->extents.width,
1419
surface->extents.height,
1421
} else if (bpp == 32 && color_comps == 3 &&
1422
(bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst &&
1423
(bitinfo & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Host)
1425
isurf = (cairo_image_surface_t *)
1426
cairo_image_surface_create_for_data (imageData,
1428
surface->extents.width,
1429
surface->extents.height,
1431
} else if (bpp == 8 && color_comps == 1)
1433
isurf = (cairo_image_surface_t *)
1434
cairo_image_surface_create_for_data (imageData,
1436
surface->extents.width,
1437
surface->extents.height,
1440
return CAIRO_INT_STATUS_UNSUPPORTED;
1443
return CAIRO_INT_STATUS_UNSUPPORTED;
1447
return CAIRO_STATUS_SUCCESS;
1451
* Cairo surface backend implementations
1454
static cairo_status_t
1455
_cairo_quartz_surface_finish (void *abstract_surface)
1457
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1459
ND((stderr, "_cairo_quartz_surface_finish[%p] cgc: %p\n", surface, surface->cgContext));
1461
if (IS_EMPTY(surface))
1462
return CAIRO_STATUS_SUCCESS;
1464
/* Restore our saved gstate that we use to reset clipping */
1465
CGContextRestoreGState (surface->cgContext);
1467
CGContextRelease (surface->cgContext);
1469
surface->cgContext = NULL;
1471
if (surface->imageSurfaceEquiv) {
1472
cairo_surface_destroy (surface->imageSurfaceEquiv);
1473
surface->imageSurfaceEquiv = NULL;
1476
if (surface->imageData) {
1477
free (surface->imageData);
1478
surface->imageData = NULL;
1481
return CAIRO_STATUS_SUCCESS;
1484
static cairo_status_t
1485
_cairo_quartz_surface_acquire_source_image (void *abstract_surface,
1486
cairo_image_surface_t **image_out,
1489
cairo_int_status_t status;
1490
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1492
//ND((stderr, "%p _cairo_quartz_surface_acquire_source_image\n", surface));
1494
status = _cairo_quartz_get_image (surface, image_out);
1496
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1498
*image_extra = NULL;
1500
return CAIRO_STATUS_SUCCESS;
1504
_cairo_quartz_surface_release_source_image (void *abstract_surface,
1505
cairo_image_surface_t *image,
1508
cairo_surface_destroy ((cairo_surface_t *) image);
1512
static cairo_status_t
1513
_cairo_quartz_surface_acquire_dest_image (void *abstract_surface,
1514
cairo_rectangle_int_t *interest_rect,
1515
cairo_image_surface_t **image_out,
1516
cairo_rectangle_int_t *image_rect,
1519
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1520
cairo_int_status_t status;
1522
ND((stderr, "%p _cairo_quartz_surface_acquire_dest_image\n", surface));
1524
status = _cairo_quartz_get_image (surface, image_out);
1526
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1528
*image_rect = surface->extents;
1529
*image_extra = NULL;
1531
return CAIRO_STATUS_SUCCESS;
1535
_cairo_quartz_surface_release_dest_image (void *abstract_surface,
1536
cairo_rectangle_int_t *interest_rect,
1537
cairo_image_surface_t *image,
1538
cairo_rectangle_int_t *image_rect,
1541
//cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1543
//ND((stderr, "%p _cairo_quartz_surface_release_dest_image\n", surface));
1545
cairo_surface_destroy ((cairo_surface_t *) image);
1548
static cairo_surface_t *
1549
_cairo_quartz_surface_create_similar (void *abstract_surface,
1550
cairo_content_t content,
1554
/*cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;*/
1556
cairo_format_t format;
1558
if (content == CAIRO_CONTENT_COLOR_ALPHA)
1559
format = CAIRO_FORMAT_ARGB32;
1560
else if (content == CAIRO_CONTENT_COLOR)
1561
format = CAIRO_FORMAT_RGB24;
1562
else if (content == CAIRO_CONTENT_ALPHA)
1563
format = CAIRO_FORMAT_A8;
1567
// verify width and height of surface
1568
if (!_cairo_quartz_verify_surface_size(width, height)) {
1569
_cairo_error (CAIRO_STATUS_NO_MEMORY);
1573
return cairo_quartz_surface_create (format, width, height);
1576
static cairo_status_t
1577
_cairo_quartz_surface_clone_similar (void *abstract_surface,
1578
cairo_surface_t *src,
1583
cairo_surface_t **clone_out)
1585
cairo_quartz_surface_t *new_surface = NULL;
1586
cairo_format_t new_format;
1587
CGImageRef quartz_image = NULL;
1588
cairo_status_t status;
1592
// verify width and height of surface
1593
if (!_cairo_quartz_verify_surface_size(width, height)) {
1594
return CAIRO_INT_STATUS_UNSUPPORTED;
1597
if (width == 0 || height == 0) {
1598
*clone_out = (cairo_surface_t*)
1599
_cairo_quartz_surface_create_internal (NULL, CAIRO_CONTENT_COLOR_ALPHA,
1601
return CAIRO_STATUS_SUCCESS;
1604
if (src->backend->type == CAIRO_SURFACE_TYPE_QUARTZ) {
1605
cairo_quartz_surface_t *qsurf = (cairo_quartz_surface_t *) src;
1607
if (IS_EMPTY(qsurf)) {
1608
*clone_out = (cairo_surface_t*)
1609
_cairo_quartz_surface_create_internal (NULL, CAIRO_CONTENT_COLOR_ALPHA,
1610
qsurf->extents.width, qsurf->extents.height);
1611
return CAIRO_STATUS_SUCCESS;
1615
status = _cairo_surface_to_cgimage ((cairo_surface_t*) abstract_surface, src, &quartz_image);
1617
return CAIRO_INT_STATUS_UNSUPPORTED;
1619
new_format = CAIRO_FORMAT_ARGB32; /* assumed */
1620
if (_cairo_surface_is_image (src)) {
1621
new_format = ((cairo_image_surface_t *) src)->format;
1624
new_surface = (cairo_quartz_surface_t *)
1625
cairo_quartz_surface_create (new_format, width, height);
1627
if (quartz_image == NULL)
1630
if (!new_surface || new_surface->base.status) {
1631
CGImageRelease (quartz_image);
1632
return CAIRO_INT_STATUS_UNSUPPORTED;
1635
CGContextSaveGState (new_surface->cgContext);
1637
CGContextSetCompositeOperation (new_surface->cgContext,
1638
kPrivateCGCompositeCopy);
1640
CGContextTranslateCTM (new_surface->cgContext, -src_x, -src_y);
1641
CGContextDrawImage (new_surface->cgContext,
1642
CGRectMake (0, 0, CGImageGetWidth(quartz_image), CGImageGetHeight(quartz_image)),
1645
CGContextRestoreGState (new_surface->cgContext);
1647
CGImageRelease (quartz_image);
1650
*clone_out = (cairo_surface_t*) new_surface;
1652
return CAIRO_STATUS_SUCCESS;
1655
static cairo_int_status_t
1656
_cairo_quartz_surface_get_extents (void *abstract_surface,
1657
cairo_rectangle_int_t *extents)
1659
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1661
*extents = surface->extents;
1663
return CAIRO_STATUS_SUCCESS;
1666
static cairo_int_status_t
1667
_cairo_quartz_surface_paint (void *abstract_surface,
1668
cairo_operator_t op,
1669
cairo_pattern_t *source)
1671
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1672
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
1673
cairo_quartz_action_t action;
1675
ND((stderr, "%p _cairo_quartz_surface_paint op %d source->type %d\n", surface, op, source->type));
1677
if (IS_EMPTY(surface))
1678
return CAIRO_STATUS_SUCCESS;
1680
if (op == CAIRO_OPERATOR_DEST)
1681
return CAIRO_STATUS_SUCCESS;
1683
CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
1685
action = _cairo_quartz_setup_source (surface, source);
1687
if (action == DO_SOLID || action == DO_PATTERN) {
1688
CGContextFillRect (surface->cgContext, CGRectMake(surface->extents.x,
1690
surface->extents.width,
1691
surface->extents.height));
1692
} else if (action == DO_SHADING) {
1693
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1694
CGContextDrawShading (surface->cgContext, surface->sourceShading);
1695
} else if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
1696
CGContextSaveGState (surface->cgContext);
1698
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1699
CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
1700
CGContextScaleCTM (surface->cgContext, 1, -1);
1702
if (action == DO_IMAGE)
1703
CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1705
CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1706
CGContextRestoreGState (surface->cgContext);
1707
} else if (action != DO_NOTHING) {
1708
rv = CAIRO_INT_STATUS_UNSUPPORTED;
1711
_cairo_quartz_teardown_source (surface, source);
1713
ND((stderr, "-- paint\n"));
1717
static cairo_int_status_t
1718
_cairo_quartz_surface_fill (void *abstract_surface,
1719
cairo_operator_t op,
1720
cairo_pattern_t *source,
1721
cairo_path_fixed_t *path,
1722
cairo_fill_rule_t fill_rule,
1724
cairo_antialias_t antialias)
1726
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1727
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
1728
cairo_quartz_action_t action;
1729
quartz_stroke_t stroke;
1731
CGPathRef path_for_unbounded = NULL;
1733
ND((stderr, "%p _cairo_quartz_surface_fill op %d source->type %d\n", surface, op, source->type));
1735
if (IS_EMPTY(surface))
1736
return CAIRO_STATUS_SUCCESS;
1738
if (op == CAIRO_OPERATOR_DEST)
1739
return CAIRO_STATUS_SUCCESS;
1741
/* Check whether the path would be a no-op */
1742
/* XXX handle unbounded ops */
1743
if (_cairo_path_fixed_is_empty(path) ||
1744
(_cairo_path_fixed_is_box(path, &box) &&
1745
box.p1.x == box.p2.x &&
1746
box.p1.y == box.p2.y))
1748
return CAIRO_STATUS_SUCCESS;
1751
CGContextSaveGState (surface->cgContext);
1753
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
1754
CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
1756
action = _cairo_quartz_setup_source (surface, source);
1758
CGContextBeginPath (surface->cgContext);
1760
stroke.cgContext = surface->cgContext;
1761
stroke.ctm_inverse = NULL;
1762
rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
1766
if (!_cairo_operator_bounded_by_mask(op) && CGContextCopyPathPtr)
1767
path_for_unbounded = CGContextCopyPathPtr (surface->cgContext);
1769
if (action == DO_SOLID || action == DO_PATTERN) {
1770
if (fill_rule == CAIRO_FILL_RULE_WINDING)
1771
CGContextFillPath (surface->cgContext);
1773
CGContextEOFillPath (surface->cgContext);
1774
} else if (action == DO_SHADING) {
1776
// we have to clip and then paint the shading; we can't fill
1778
if (fill_rule == CAIRO_FILL_RULE_WINDING)
1779
CGContextClip (surface->cgContext);
1781
CGContextEOClip (surface->cgContext);
1783
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1784
CGContextDrawShading (surface->cgContext, surface->sourceShading);
1785
} else if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
1786
if (fill_rule == CAIRO_FILL_RULE_WINDING)
1787
CGContextClip (surface->cgContext);
1789
CGContextEOClip (surface->cgContext);
1791
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1792
CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
1793
CGContextScaleCTM (surface->cgContext, 1, -1);
1795
if (action == DO_IMAGE)
1796
CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1798
CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1799
} else if (action != DO_NOTHING) {
1800
rv = CAIRO_INT_STATUS_UNSUPPORTED;
1804
_cairo_quartz_teardown_source (surface, source);
1806
CGContextRestoreGState (surface->cgContext);
1808
if (path_for_unbounded) {
1809
unbounded_op_data_t ub;
1810
ub.op = UNBOUNDED_STROKE_FILL;
1811
ub.u.stroke_fill.cgPath = path_for_unbounded;
1812
ub.u.stroke_fill.fill_rule = fill_rule;
1814
_cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias);
1815
CGPathRelease (path_for_unbounded);
1818
ND((stderr, "-- fill\n"));
1822
static cairo_int_status_t
1823
_cairo_quartz_surface_stroke (void *abstract_surface,
1824
cairo_operator_t op,
1825
cairo_pattern_t *source,
1826
cairo_path_fixed_t *path,
1827
cairo_stroke_style_t *style,
1828
cairo_matrix_t *ctm,
1829
cairo_matrix_t *ctm_inverse,
1831
cairo_antialias_t antialias)
1833
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1834
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
1835
cairo_quartz_action_t action;
1836
quartz_stroke_t stroke;
1837
CGAffineTransform origCTM, strokeTransform;
1838
CGPathRef path_for_unbounded = NULL;
1840
ND((stderr, "%p _cairo_quartz_surface_stroke op %d source->type %d\n", surface, op, source->type));
1842
if (IS_EMPTY(surface))
1843
return CAIRO_STATUS_SUCCESS;
1845
if (op == CAIRO_OPERATOR_DEST)
1846
return CAIRO_STATUS_SUCCESS;
1848
CGContextSaveGState (surface->cgContext);
1850
// Turning antialiasing off used to cause misrendering with
1851
// single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels).
1852
// That's been since fixed in at least 10.5, and in the latest 10.4 dot releases.
1853
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
1854
CGContextSetLineWidth (surface->cgContext, style->line_width);
1855
CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap));
1856
CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join));
1857
CGContextSetMiterLimit (surface->cgContext, style->miter_limit);
1859
origCTM = CGContextGetCTM (surface->cgContext);
1861
_cairo_quartz_cairo_matrix_to_quartz (ctm, &strokeTransform);
1862
CGContextConcatCTM (surface->cgContext, strokeTransform);
1864
if (style->dash && style->num_dashes) {
1865
#define STATIC_DASH 32
1866
float sdash[STATIC_DASH];
1867
float *fdash = sdash;
1868
unsigned int max_dashes = style->num_dashes;
1871
if (style->num_dashes%2)
1873
if (max_dashes > STATIC_DASH)
1874
fdash = _cairo_malloc_ab (max_dashes, sizeof (float));
1876
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1878
for (k = 0; k < max_dashes; k++)
1879
fdash[k] = (float) style->dash[k % style->num_dashes];
1881
CGContextSetLineDash (surface->cgContext, style->dash_offset, fdash, max_dashes);
1886
CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
1888
action = _cairo_quartz_setup_source (surface, source);
1890
CGContextBeginPath (surface->cgContext);
1892
stroke.cgContext = surface->cgContext;
1893
stroke.ctm_inverse = ctm_inverse;
1894
rv = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
1898
if (!_cairo_operator_bounded_by_mask (op) && CGContextCopyPathPtr)
1899
path_for_unbounded = CGContextCopyPathPtr (surface->cgContext);
1901
if (action == DO_SOLID || action == DO_PATTERN) {
1902
CGContextStrokePath (surface->cgContext);
1903
} else if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
1904
CGContextReplacePathWithStrokedPath (surface->cgContext);
1905
CGContextClip (surface->cgContext);
1907
CGContextSetCTM (surface->cgContext, origCTM);
1909
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1910
CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
1911
CGContextScaleCTM (surface->cgContext, 1, -1);
1913
if (action == DO_IMAGE)
1914
CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1916
CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
1917
} else if (action == DO_SHADING) {
1918
CGContextReplacePathWithStrokedPath (surface->cgContext);
1919
CGContextClip (surface->cgContext);
1921
CGContextSetCTM (surface->cgContext, origCTM);
1923
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
1924
CGContextDrawShading (surface->cgContext, surface->sourceShading);
1925
} else if (action != DO_NOTHING) {
1926
rv = CAIRO_INT_STATUS_UNSUPPORTED;
1930
_cairo_quartz_teardown_source (surface, source);
1932
CGContextRestoreGState (surface->cgContext);
1934
if (path_for_unbounded) {
1935
unbounded_op_data_t ub;
1937
CGContextBeginPath (surface->cgContext);
1939
/* recreate the stroke state, but without the CTM, as it's been already baked
1942
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
1943
CGContextSetLineWidth (surface->cgContext, style->line_width);
1944
CGContextSetLineCap (surface->cgContext, _cairo_quartz_cairo_line_cap_to_quartz (style->line_cap));
1945
CGContextSetLineJoin (surface->cgContext, _cairo_quartz_cairo_line_join_to_quartz (style->line_join));
1946
CGContextSetMiterLimit (surface->cgContext, style->miter_limit);
1948
CGContextAddPath (surface->cgContext, path_for_unbounded);
1949
CGPathRelease (path_for_unbounded);
1951
CGContextReplacePathWithStrokedPath (surface->cgContext);
1952
path_for_unbounded = CGContextCopyPathPtr (surface->cgContext);
1954
ub.op = UNBOUNDED_STROKE_FILL;
1955
ub.u.stroke_fill.cgPath = path_for_unbounded;
1956
ub.u.stroke_fill.fill_rule = CAIRO_FILL_RULE_WINDING;
1958
_cairo_quartz_fixup_unbounded_operation (surface, &ub, antialias);
1960
CGPathRelease (path_for_unbounded);
1963
ND((stderr, "-- stroke\n"));
1967
#if CAIRO_HAS_QUARTZ_FONT
1968
static cairo_int_status_t
1969
_cairo_quartz_surface_show_glyphs (void *abstract_surface,
1970
cairo_operator_t op,
1971
cairo_pattern_t *source,
1972
cairo_glyph_t *glyphs,
1974
cairo_scaled_font_t *scaled_font,
1975
int *remaining_glyphs)
1977
CGAffineTransform textTransform, ctm;
1978
#define STATIC_BUF_SIZE 64
1979
CGGlyph glyphs_static[STATIC_BUF_SIZE];
1980
CGSize cg_advances_static[STATIC_BUF_SIZE];
1981
CGGlyph *cg_glyphs = &glyphs_static[0];
1982
CGSize *cg_advances = &cg_advances_static[0];
1984
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1985
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
1986
cairo_quartz_action_t action;
1989
CGFontRef cgfref = NULL;
1991
cairo_bool_t isClipping = FALSE;
1992
cairo_bool_t didForceFontSmoothing = FALSE;
1994
if (IS_EMPTY(surface))
1995
return CAIRO_STATUS_SUCCESS;
1997
if (num_glyphs <= 0)
1998
return CAIRO_STATUS_SUCCESS;
2000
if (op == CAIRO_OPERATOR_DEST)
2001
return CAIRO_STATUS_SUCCESS;
2003
if (cairo_scaled_font_get_type (scaled_font) != CAIRO_FONT_TYPE_QUARTZ)
2004
return CAIRO_INT_STATUS_UNSUPPORTED;
2006
CGContextSaveGState (surface->cgContext);
2008
action = _cairo_quartz_setup_source (surface, source);
2009
if (action == DO_SOLID || action == DO_PATTERN) {
2010
CGContextSetTextDrawingMode (surface->cgContext, kCGTextFill);
2011
} else if (action == DO_IMAGE || action == DO_TILED_IMAGE || action == DO_SHADING) {
2012
CGContextSetTextDrawingMode (surface->cgContext, kCGTextClip);
2015
if (action != DO_NOTHING)
2016
rv = CAIRO_INT_STATUS_UNSUPPORTED;
2020
CGContextSetCompositeOperation (surface->cgContext, _cairo_quartz_cairo_operator_to_quartz (op));
2022
/* this doesn't addref */
2023
cgfref = _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font);
2024
CGContextSetFont (surface->cgContext, cgfref);
2025
CGContextSetFontSize (surface->cgContext, 1.0);
2027
switch (scaled_font->options.antialias) {
2028
case CAIRO_ANTIALIAS_SUBPIXEL:
2029
CGContextSetShouldAntialias (surface->cgContext, TRUE);
2030
CGContextSetShouldSmoothFonts (surface->cgContext, TRUE);
2031
if (CGContextSetAllowsFontSmoothingPtr &&
2032
!CGContextGetAllowsFontSmoothingPtr (surface->cgContext))
2034
didForceFontSmoothing = TRUE;
2035
CGContextSetAllowsFontSmoothingPtr (surface->cgContext, TRUE);
2038
case CAIRO_ANTIALIAS_NONE:
2039
CGContextSetShouldAntialias (surface->cgContext, FALSE);
2041
case CAIRO_ANTIALIAS_GRAY:
2042
CGContextSetShouldAntialias (surface->cgContext, TRUE);
2043
CGContextSetShouldSmoothFonts (surface->cgContext, FALSE);
2045
case CAIRO_ANTIALIAS_DEFAULT:
2046
/* Don't do anything */
2050
if (num_glyphs > STATIC_BUF_SIZE) {
2051
cg_glyphs = (CGGlyph*) _cairo_malloc_ab (num_glyphs, sizeof(CGGlyph));
2052
if (cg_glyphs == NULL) {
2053
rv = _cairo_error (CAIRO_STATUS_NO_MEMORY);
2057
cg_advances = (CGSize*) _cairo_malloc_ab (num_glyphs, sizeof(CGSize));
2058
if (cg_advances == NULL) {
2059
rv = _cairo_error (CAIRO_STATUS_NO_MEMORY);
2064
textTransform = CGAffineTransformMake (scaled_font->font_matrix.xx,
2065
scaled_font->font_matrix.yx,
2066
scaled_font->font_matrix.xy,
2067
scaled_font->font_matrix.yy,
2069
textTransform = CGAffineTransformScale (textTransform, 1.0, -1.0);
2070
textTransform = CGAffineTransformConcat (CGAffineTransformMake(scaled_font->ctm.xx,
2071
-scaled_font->ctm.yx,
2072
-scaled_font->ctm.xy,
2073
scaled_font->ctm.yy,
2077
CGContextSetTextMatrix (surface->cgContext, textTransform);
2079
/* Convert our glyph positions to glyph advances. We need n-1 advances,
2080
* since the advance at index 0 is applied after glyph 0. */
2081
xprev = glyphs[0].x;
2082
yprev = glyphs[0].y;
2084
cg_glyphs[0] = glyphs[0].index;
2086
for (i = 1; i < num_glyphs; i++) {
2087
float xf = glyphs[i].x;
2088
float yf = glyphs[i].y;
2089
cg_glyphs[i] = glyphs[i].index;
2090
cg_advances[i-1].width = xf - xprev;
2091
cg_advances[i-1].height = yf - yprev;
2096
if (_cairo_quartz_osx_version >= 0x1050 && isClipping) {
2097
/* If we're clipping, OSX 10.5 (at least as of 10.5.2) has a
2098
* bug (apple bug ID #5834794) where the glyph
2099
* advances/positions are not transformed by the text matrix
2100
* if kCGTextClip is being used. So, we pre-transform here.
2101
* 10.4 does not have this problem (as of 10.4.11).
2103
for (i = 0; i < num_glyphs - 1; i++)
2104
cg_advances[i] = CGSizeApplyAffineTransform(cg_advances[i], textTransform);
2108
for (i = 0; i < num_glyphs; i++) {
2109
ND((stderr, "[%d: %d %f,%f]\n", i, cg_glyphs[i], cg_advances[i].width, cg_advances[i].height));
2113
/* Translate to the first glyph's position before drawing */
2114
ctm = CGContextGetCTM (surface->cgContext);
2115
CGContextTranslateCTM (surface->cgContext, glyphs[0].x, glyphs[0].y);
2117
CGContextShowGlyphsWithAdvances (surface->cgContext,
2122
CGContextSetCTM (surface->cgContext, ctm);
2124
if (action == DO_IMAGE || action == DO_TILED_IMAGE) {
2125
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
2126
CGContextTranslateCTM (surface->cgContext, 0, surface->sourceImageRect.size.height);
2127
CGContextScaleCTM (surface->cgContext, 1, -1);
2129
if (action == DO_IMAGE)
2130
CGContextDrawImage (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
2132
CGContextDrawTiledImagePtr (surface->cgContext, surface->sourceImageRect, surface->sourceImage);
2133
} else if (action == DO_SHADING) {
2134
CGContextConcatCTM (surface->cgContext, surface->sourceTransform);
2135
CGContextDrawShading (surface->cgContext, surface->sourceShading);
2139
_cairo_quartz_teardown_source (surface, source);
2141
if (didForceFontSmoothing)
2142
CGContextSetAllowsFontSmoothingPtr (surface->cgContext, FALSE);
2144
CGContextRestoreGState (surface->cgContext);
2146
if (rv == CAIRO_STATUS_SUCCESS &&
2148
!_cairo_operator_bounded_by_mask (op))
2150
unbounded_op_data_t ub;
2151
ub.op = UNBOUNDED_SHOW_GLYPHS;
2153
ub.u.show_glyphs.isClipping = isClipping;
2154
ub.u.show_glyphs.cg_glyphs = cg_glyphs;
2155
ub.u.show_glyphs.cg_advances = cg_advances;
2156
ub.u.show_glyphs.nglyphs = num_glyphs;
2157
ub.u.show_glyphs.textTransform = textTransform;
2158
ub.u.show_glyphs.font = cgfref;
2159
ub.u.show_glyphs.origin = CGPointMake (glyphs[0].x, glyphs[0].y);
2161
_cairo_quartz_fixup_unbounded_operation (surface, &ub, scaled_font->options.antialias);
2165
if (cg_advances != &cg_advances_static[0]) {
2169
if (cg_glyphs != &glyphs_static[0]) {
2175
#endif /* CAIRO_HAS_QUARTZ_FONT */
2177
static cairo_int_status_t
2178
_cairo_quartz_surface_mask_with_surface (cairo_quartz_surface_t *surface,
2179
cairo_operator_t op,
2180
cairo_pattern_t *source,
2181
cairo_surface_pattern_t *mask)
2183
cairo_rectangle_int_t extents;
2186
cairo_surface_t *pat_surf = mask->surface;
2187
cairo_status_t status = CAIRO_STATUS_SUCCESS;
2188
CGAffineTransform ctm, mask_matrix;
2190
status = _cairo_surface_get_extents (pat_surf, &extents);
2194
// everything would be masked out, so do nothing
2195
if (extents.width == 0 || extents.height == 0)
2196
return CAIRO_STATUS_SUCCESS;
2198
status = _cairo_surface_to_cgimage ((cairo_surface_t *) surface, pat_surf, &img);
2199
if (status == CAIRO_STATUS_SUCCESS && img == NULL)
2200
return CAIRO_STATUS_SUCCESS;
2204
rect = CGRectMake (0.0f, 0.0f, extents.width, extents.height);
2206
CGContextSaveGState (surface->cgContext);
2208
/* ClipToMask is essentially drawing an image, so we need to flip the CTM
2209
* to get the image to appear oriented the right way */
2210
ctm = CGContextGetCTM (surface->cgContext);
2212
_cairo_quartz_cairo_matrix_to_quartz (&mask->base.matrix, &mask_matrix);
2213
CGContextConcatCTM (surface->cgContext, CGAffineTransformInvert(mask_matrix));
2214
CGContextTranslateCTM (surface->cgContext, 0.0f, rect.size.height);
2215
CGContextScaleCTM (surface->cgContext, 1.0f, -1.0f);
2217
CGContextClipToMaskPtr (surface->cgContext, rect, img);
2219
CGContextSetCTM (surface->cgContext, ctm);
2221
status = _cairo_quartz_surface_paint (surface, op, source);
2223
CGContextRestoreGState (surface->cgContext);
2225
if (!_cairo_operator_bounded_by_mask (op)) {
2226
unbounded_op_data_t ub;
2227
ub.op = UNBOUNDED_MASK;
2228
ub.u.mask.mask = img;
2229
ub.u.mask.maskTransform = CGAffineTransformInvert(mask_matrix);
2230
_cairo_quartz_fixup_unbounded_operation (surface, &ub, CAIRO_ANTIALIAS_NONE);
2233
CGImageRelease (img);
2238
/* This is somewhat less than ideal, but it gets the job done;
2239
* it would be better to avoid calling back into cairo. This
2240
* creates a temporary surface to use as the mask.
2242
static cairo_int_status_t
2243
_cairo_quartz_surface_mask_with_generic (cairo_quartz_surface_t *surface,
2244
cairo_operator_t op,
2245
cairo_pattern_t *source,
2246
cairo_pattern_t *mask)
2248
int width = surface->extents.width - surface->extents.x;
2249
int height = surface->extents.height - surface->extents.y;
2251
cairo_surface_t *gradient_surf = NULL;
2252
cairo_t *gradient_surf_cr = NULL;
2254
cairo_surface_pattern_t surface_pattern;
2255
cairo_int_status_t status;
2257
/* Render the gradient to a surface */
2258
gradient_surf = cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32,
2261
gradient_surf_cr = cairo_create(gradient_surf);
2262
cairo_set_source (gradient_surf_cr, mask);
2263
cairo_set_operator (gradient_surf_cr, CAIRO_OPERATOR_SOURCE);
2264
cairo_paint (gradient_surf_cr);
2265
status = cairo_status (gradient_surf_cr);
2266
cairo_destroy (gradient_surf_cr);
2271
_cairo_pattern_init_for_surface (&surface_pattern, gradient_surf);
2273
status = _cairo_quartz_surface_mask_with_surface (surface, op, source, &surface_pattern);
2275
_cairo_pattern_fini (&surface_pattern.base);
2279
cairo_surface_destroy (gradient_surf);
2284
static cairo_int_status_t
2285
_cairo_quartz_surface_mask (void *abstract_surface,
2286
cairo_operator_t op,
2287
cairo_pattern_t *source,
2288
cairo_pattern_t *mask)
2290
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
2291
cairo_int_status_t rv = CAIRO_STATUS_SUCCESS;
2293
ND((stderr, "%p _cairo_quartz_surface_mask op %d source->type %d mask->type %d\n", surface, op, source->type, mask->type));
2295
if (IS_EMPTY(surface))
2296
return CAIRO_STATUS_SUCCESS;
2298
if (mask->type == CAIRO_PATTERN_TYPE_SOLID) {
2299
/* This is easy; we just need to paint with the alpha. */
2300
cairo_solid_pattern_t *solid_mask = (cairo_solid_pattern_t *) mask;
2302
CGContextSetAlpha (surface->cgContext, solid_mask->color.alpha);
2303
rv = _cairo_quartz_surface_paint (surface, op, source);
2304
CGContextSetAlpha (surface->cgContext, 1.0);
2309
/* If we have CGContextClipToMask, we can do more complex masks */
2310
if (CGContextClipToMaskPtr) {
2311
/* For these, we can skip creating a temporary surface, since we already have one */
2312
if (mask->type == CAIRO_PATTERN_TYPE_SURFACE && mask->extend == CAIRO_EXTEND_NONE)
2313
return _cairo_quartz_surface_mask_with_surface (surface, op, source, (cairo_surface_pattern_t *) mask);
2315
return _cairo_quartz_surface_mask_with_generic (surface, op, source, mask);
2318
/* So, CGContextClipToMask is not present in 10.3.9, so we're
2319
* doomed; if we have imageData, we can do fallback, otherwise
2320
* just pretend success.
2322
if (surface->imageData)
2323
return CAIRO_INT_STATUS_UNSUPPORTED;
2325
return CAIRO_STATUS_SUCCESS;
2328
static cairo_int_status_t
2329
_cairo_quartz_surface_intersect_clip_path (void *abstract_surface,
2330
cairo_path_fixed_t *path,
2331
cairo_fill_rule_t fill_rule,
2333
cairo_antialias_t antialias)
2335
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
2336
quartz_stroke_t stroke;
2337
cairo_status_t status;
2339
ND((stderr, "%p _cairo_quartz_surface_intersect_clip_path path: %p\n", surface, path));
2341
if (IS_EMPTY(surface))
2342
return CAIRO_STATUS_SUCCESS;
2345
/* If we're being asked to reset the clip, we can only do it
2346
* by restoring the gstate to our previous saved one, and
2349
* Note that this assumes that ALL quartz surface creation
2350
* functions will do a SaveGState first; we do this in create_internal.
2352
CGContextRestoreGState (surface->cgContext);
2353
CGContextSaveGState (surface->cgContext);
2355
CGContextBeginPath (surface->cgContext);
2356
stroke.cgContext = surface->cgContext;
2357
stroke.ctm_inverse = NULL;
2359
CGContextSetShouldAntialias (surface->cgContext, (antialias != CAIRO_ANTIALIAS_NONE));
2361
/* path must not be empty. */
2362
CGContextMoveToPoint (surface->cgContext, 0, 0);
2363
status = _cairo_quartz_cairo_path_to_quartz_context (path, &stroke);
2367
if (fill_rule == CAIRO_FILL_RULE_WINDING)
2368
CGContextClip (surface->cgContext);
2370
CGContextEOClip (surface->cgContext);
2373
ND((stderr, "-- intersect_clip_path\n"));
2375
return CAIRO_STATUS_SUCCESS;
2378
// XXXtodo implement show_page; need to figure out how to handle begin/end
2380
static const struct _cairo_surface_backend cairo_quartz_surface_backend = {
2381
CAIRO_SURFACE_TYPE_QUARTZ,
2382
_cairo_quartz_surface_create_similar,
2383
_cairo_quartz_surface_finish,
2384
_cairo_quartz_surface_acquire_source_image,
2385
_cairo_quartz_surface_release_source_image,
2386
_cairo_quartz_surface_acquire_dest_image,
2387
_cairo_quartz_surface_release_dest_image,
2388
_cairo_quartz_surface_clone_similar,
2389
NULL, /* composite */
2390
NULL, /* fill_rectangles */
2391
NULL, /* composite_trapezoids */
2392
NULL, /* copy_page */
2393
NULL, /* show_page */
2394
NULL, /* set_clip_region */
2395
_cairo_quartz_surface_intersect_clip_path,
2396
_cairo_quartz_surface_get_extents,
2397
NULL, /* old_show_glyphs */
2398
NULL, /* get_font_options */
2400
NULL, /* mark_dirty_rectangle */
2401
NULL, /* scaled_font_fini */
2402
NULL, /* scaled_glyph_fini */
2404
_cairo_quartz_surface_paint,
2405
_cairo_quartz_surface_mask,
2406
_cairo_quartz_surface_stroke,
2407
_cairo_quartz_surface_fill,
2408
#if CAIRO_HAS_QUARTZ_FONT
2409
_cairo_quartz_surface_show_glyphs,
2411
NULL, /* show_glyphs */
2414
NULL, /* snapshot */
2415
NULL, /* is_similar */
2417
NULL /* fill_stroke */
2420
cairo_quartz_surface_t *
2421
_cairo_quartz_surface_create_internal (CGContextRef cgContext,
2422
cairo_content_t content,
2424
unsigned int height)
2426
cairo_quartz_surface_t *surface;
2428
quartz_ensure_symbols();
2430
/* Init the base surface */
2431
surface = malloc(sizeof(cairo_quartz_surface_t));
2432
if (surface == NULL)
2433
return (cairo_quartz_surface_t*) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
2435
memset(surface, 0, sizeof(cairo_quartz_surface_t));
2437
_cairo_surface_init(&surface->base, &cairo_quartz_surface_backend,
2440
/* Save our extents */
2441
surface->extents.x = surface->extents.y = 0;
2442
surface->extents.width = width;
2443
surface->extents.height = height;
2445
if (IS_EMPTY(surface)) {
2446
surface->cgContext = NULL;
2447
surface->cgContextBaseCTM = CGAffineTransformIdentity;
2448
surface->imageData = NULL;
2452
/* Save so we can always get back to a known-good CGContext -- this is
2453
* required for proper behaviour of intersect_clip_path(NULL)
2455
CGContextSaveGState (cgContext);
2457
surface->cgContext = cgContext;
2458
surface->cgContextBaseCTM = CGContextGetCTM (cgContext);
2460
surface->imageData = NULL;
2461
surface->imageSurfaceEquiv = NULL;
2467
* cairo_quartz_surface_create_for_cg_context
2468
* @cgContext: the existing CGContext for which to create the surface
2469
* @width: width of the surface, in pixels
2470
* @height: height of the surface, in pixels
2472
* Creates a Quartz surface that wraps the given CGContext. The
2473
* CGContext is assumed to be in the standard Cairo coordinate space
2474
* (that is, with the origin at the upper left and the Y axis
2475
* increasing downward). If the CGContext is in the Quartz coordinate
2476
* space (with the origin at the bottom left), then it should be
2477
* flipped before this function is called. The flip can be accomplished
2478
* using a translate and a scale; for example:
2480
* <informalexample><programlisting>
2481
* CGContextTranslateCTM (cgContext, 0.0, height);
2482
* CGContextScaleCTM (cgContext, 1.0, -1.0);
2483
* </programlisting></informalexample>
2485
* All Cairo operations are implemented in terms of Quartz operations,
2486
* as long as Quartz-compatible elements are used (such as Quartz fonts).
2488
* Return value: the newly created Cairo surface.
2494
cairo_quartz_surface_create_for_cg_context (CGContextRef cgContext,
2496
unsigned int height)
2498
cairo_quartz_surface_t *surf;
2500
CGContextRetain (cgContext);
2502
surf = _cairo_quartz_surface_create_internal (cgContext, CAIRO_CONTENT_COLOR_ALPHA,
2504
if (surf->base.status) {
2505
CGContextRelease (cgContext);
2506
// create_internal will have set an error
2507
return (cairo_surface_t*) surf;
2510
return (cairo_surface_t *) surf;
2514
* cairo_quartz_surface_create
2515
* @format: format of pixels in the surface to create
2516
* @width: width of the surface, in pixels
2517
* @height: height of the surface, in pixels
2519
* Creates a Quartz surface backed by a CGBitmap. The surface is
2520
* created using the Device RGB (or Device Gray, for A8) color space.
2521
* All Cairo operations, including those that require software
2522
* rendering, will succeed on this surface.
2524
* Return value: the newly created surface.
2529
cairo_quartz_surface_create (cairo_format_t format,
2531
unsigned int height)
2533
cairo_quartz_surface_t *surf;
2535
CGColorSpaceRef cgColorspace;
2536
CGBitmapInfo bitinfo;
2539
int bitsPerComponent;
2541
// verify width and height of surface
2542
if (!_cairo_quartz_verify_surface_size(width, height))
2543
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
2545
if (width == 0 || height == 0) {
2546
return (cairo_surface_t*) _cairo_quartz_surface_create_internal (NULL, _cairo_content_from_format (format),
2550
if (format == CAIRO_FORMAT_ARGB32 ||
2551
format == CAIRO_FORMAT_RGB24)
2553
cgColorspace = CGColorSpaceCreateDeviceRGB();
2554
bitinfo = kCGBitmapByteOrder32Host;
2555
if (format == CAIRO_FORMAT_ARGB32)
2556
bitinfo |= kCGImageAlphaPremultipliedFirst;
2558
bitinfo |= kCGImageAlphaNoneSkipFirst;
2559
bitsPerComponent = 8;
2561
/* The Apple docs say that for best performance, the stride and the data
2562
* pointer should be 16-byte aligned. malloc already aligns to 16-bytes,
2563
* so we don't have to anything special on allocation.
2566
stride += (16 - (stride & 15)) & 15;
2567
} else if (format == CAIRO_FORMAT_A8) {
2568
cgColorspace = CGColorSpaceCreateDeviceGray();
2572
stride = (width & ~3) + 4;
2573
bitinfo = kCGImageAlphaNone;
2574
bitsPerComponent = 8;
2575
} else if (format == CAIRO_FORMAT_A1) {
2576
/* I don't think we can usefully support this, as defined by
2577
* cairo_format_t -- these are 1-bit pixels stored in 32-bit
2580
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
2582
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT));
2585
imageData = _cairo_malloc_ab (height, stride);
2587
CGColorSpaceRelease (cgColorspace);
2588
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
2591
/* zero the memory to match the image surface behaviour */
2592
memset (imageData, 0, height * stride);
2594
cgc = CGBitmapContextCreate (imageData,
2601
CGColorSpaceRelease (cgColorspace);
2605
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
2608
/* flip the Y axis */
2609
CGContextTranslateCTM (cgc, 0.0, height);
2610
CGContextScaleCTM (cgc, 1.0, -1.0);
2612
surf = _cairo_quartz_surface_create_internal (cgc, _cairo_content_from_format (format),
2614
if (surf->base.status) {
2615
CGContextRelease (cgc);
2617
// create_internal will have set an error
2618
return (cairo_surface_t*) surf;
2621
surf->imageData = imageData;
2622
surf->imageSurfaceEquiv = cairo_image_surface_create_for_data (imageData, format, width, height, stride);
2624
return (cairo_surface_t *) surf;
2628
* cairo_quartz_surface_get_cg_context
2629
* @surface: the Cairo Quartz surface
2631
* Returns the CGContextRef that the given Quartz surface is backed
2634
* Return value: the CGContextRef for the given surface.
2639
cairo_quartz_surface_get_cg_context (cairo_surface_t *surface)
2641
cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t*)surface;
2643
if (cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_QUARTZ)
2646
return quartz->cgContext;
2656
void ExportCGImageToPNGFile(CGImageRef inImageRef, char* dest)
2658
Handle dataRef = NULL;
2660
CFStringRef inPath = CFStringCreateWithCString(NULL, dest, kCFStringEncodingASCII);
2662
GraphicsExportComponent grex = 0;
2663
unsigned long sizeWritten;
2665
ComponentResult result;
2667
// create the data reference
2668
result = QTNewDataReferenceFromFullPathCFString(inPath, kQTNativeDefaultPathStyle,
2669
0, &dataRef, &dataRefType);
2671
if (NULL != dataRef && noErr == result) {
2672
// get the PNG exporter
2673
result = OpenADefaultComponent(GraphicsExporterComponentType, kQTFileTypePNG,
2677
// tell the exporter where to find its source image
2678
result = GraphicsExportSetInputCGImage(grex, inImageRef);
2680
if (noErr == result) {
2681
// tell the exporter where to save the exporter image
2682
result = GraphicsExportSetOutputDataReference(grex, dataRef,
2685
if (noErr == result) {
2686
// write the PNG file
2687
result = GraphicsExportDoExport(grex, &sizeWritten);
2691
// remember to close the component
2692
CloseComponent(grex);
2695
// remember to dispose of the data reference handle
2696
DisposeHandle(dataRef);
2701
quartz_image_to_png (CGImageRef imgref, char *dest)
2703
static int sctr = 0;
2704
char sptr[] = "/Users/vladimir/Desktop/barXXXXX.png";
2707
fprintf (stderr, "** Writing %p to bar%d\n", imgref, sctr);
2708
sprintf (sptr, "/Users/vladimir/Desktop/bar%d.png", sctr);
2713
ExportCGImageToPNGFile(imgref, dest);
2717
quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest)
2719
static int sctr = 0;
2720
char sptr[] = "/Users/vladimir/Desktop/fooXXXXX.png";
2722
if (nq->base.type != CAIRO_SURFACE_TYPE_QUARTZ) {
2723
fprintf (stderr, "** quartz_surface_to_png: surface %p isn't quartz!\n", nq);
2728
fprintf (stderr, "** Writing %p to foo%d\n", nq, sctr);
2729
sprintf (sptr, "/Users/vladimir/Desktop/foo%d.png", sctr);
2734
CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext);
2735
if (imgref == NULL) {
2736
fprintf (stderr, "quartz surface at %p is not a bitmap context!\n", nq);
2740
ExportCGImageToPNGFile(imgref, dest);
2742
CGImageRelease(imgref);
2745
#endif /* QUARTZ_DEBUG */