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
);
897 SurfacePatternReleaseInfoFunc (void *ainfo
)
899 SurfacePatternDrawInfo
*info
= (SurfacePatternDrawInfo
*) ainfo
;
901 CGImageRelease (info
->image
);
905 static cairo_int_status_t
906 _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t
*dest
,
907 cairo_pattern_t
*apattern
,
910 cairo_surface_pattern_t
*spattern
;
911 cairo_surface_t
*pat_surf
;
912 cairo_rectangle_int_t extents
;
916 CGAffineTransform ptransform
, stransform
;
917 CGPatternCallbacks cb
= { 0,
918 SurfacePatternDrawFunc
,
919 SurfacePatternReleaseInfoFunc
};
920 SurfacePatternDrawInfo
*info
;
922 cairo_status_t status
;
926 /* SURFACE is the only type we'll handle here */
927 if (apattern
->type
!= CAIRO_PATTERN_TYPE_SURFACE
)
928 return CAIRO_INT_STATUS_UNSUPPORTED
;
930 spattern
= (cairo_surface_pattern_t
*) apattern
;
931 pat_surf
= spattern
->surface
;
933 status
= _cairo_surface_get_extents (pat_surf
, &extents
);
937 status
= _cairo_surface_to_cgimage ((cairo_surface_t
*) dest
, pat_surf
, &image
);
938 if (status
!= CAIRO_STATUS_SUCCESS
)
939 return CAIRO_INT_STATUS_UNSUPPORTED
;
942 return CAIRO_INT_STATUS_NOTHING_TO_DO
;
944 info
= malloc(sizeof(SurfacePatternDrawInfo
));
946 return CAIRO_STATUS_NO_MEMORY
;
948 /* XXX -- if we're printing, we may need to call CGImageCreateCopy to make sure
949 * that the data will stick around for this image when the printer gets to it.
950 * Otherwise, the underlying data store may disappear from under us!
952 * _cairo_surface_to_cgimage will copy when it converts non-Quartz surfaces,
953 * since the Quartz surfaces have a higher chance of sticking around. If the
954 * source is a quartz image surface, then it's set up to retain a ref to the
955 * image surface that it's backed by.
958 info
->imageBounds
= CGRectMake (0, 0, extents
.width
, extents
.height
);
959 info
->do_reflect
= FALSE
;
961 pbounds
.origin
.x
= 0;
962 pbounds
.origin
.y
= 0;
964 if (spattern
->base
.extend
== CAIRO_EXTEND_REFLECT
) {
965 pbounds
.size
.width
= 2.0 * extents
.width
;
966 pbounds
.size
.height
= 2.0 * extents
.height
;
967 info
->do_reflect
= TRUE
;
969 pbounds
.size
.width
= extents
.width
;
970 pbounds
.size
.height
= extents
.height
;
972 rw
= pbounds
.size
.width
;
973 rh
= pbounds
.size
.height
;
975 m
= spattern
->base
.matrix
;
976 cairo_matrix_invert(&m
);
977 _cairo_quartz_cairo_matrix_to_quartz (&m
, &stransform
);
979 /* The pattern matrix is relative to the bottom left, again; the
980 * incoming cairo pattern matrix is relative to the upper left.
981 * So we take the pattern matrix and the original context matrix,
982 * which gives us the correct base translation/y flip.
984 ptransform
= CGAffineTransformConcat(stransform
, dest
->cgContextBaseCTM
);
987 ND((stderr
, " pbounds: %f %f %f %f\n", pbounds
.origin
.x
, pbounds
.origin
.y
, pbounds
.size
.width
, pbounds
.size
.height
));
988 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
));
989 CGAffineTransform xform
= CGContextGetCTM(dest
->cgContext
);
990 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
));
993 *cgpat
= CGPatternCreate (info
,
997 kCGPatternTilingConstantSpacing
, /* kCGPatternTilingNoDistortion, */
1001 return CAIRO_STATUS_SUCCESS
;
1012 } cairo_quartz_action_t
;
1014 static cairo_quartz_action_t
1015 _cairo_quartz_setup_fallback_source (cairo_quartz_surface_t
*surface
,
1016 cairo_pattern_t
*source
)
1018 CGRect clipBox
= CGContextGetClipBoundingBox (surface
->cgContext
);
1019 CGAffineTransform ctm
;
1020 double x0
, y0
, w
, h
;
1022 cairo_surface_t
*fallback
;
1023 cairo_t
*fallback_cr
;
1026 cairo_status_t status
;
1028 if (clipBox
.size
.width
== 0.0f
||
1029 clipBox
.size
.height
== 0.0f
)
1032 // the clipBox is in userspace, so:
1033 ctm
= CGContextGetCTM (surface
->cgContext
);
1034 ctm
= CGAffineTransformInvert (ctm
);
1035 clipBox
= CGRectApplyAffineTransform (clipBox
, ctm
);
1037 // get the Y flip right -- the CTM will always have a Y flip in place
1038 clipBox
.origin
.y
= surface
->extents
.height
- (clipBox
.origin
.y
+ clipBox
.size
.height
);
1040 x0
= floor(clipBox
.origin
.x
);
1041 y0
= floor(clipBox
.origin
.y
);
1042 w
= ceil(clipBox
.origin
.x
+ clipBox
.size
.width
) - x0
;
1043 h
= ceil(clipBox
.origin
.y
+ clipBox
.size
.height
) - y0
;
1045 /* Create a temporary the size of the clip surface, and position
1046 * it so that the device origin coincides with the original surface */
1047 fallback
= cairo_image_surface_create (CAIRO_FORMAT_ARGB32
, (int) w
, (int) h
);
1048 cairo_surface_set_device_offset (fallback
, -x0
, -y0
);
1050 /* Paint the source onto our temporary */
1051 fallback_cr
= cairo_create (fallback
);
1052 cairo_set_operator (fallback_cr
, CAIRO_OPERATOR_SOURCE
);
1053 cairo_set_source (fallback_cr
, source
);
1054 cairo_paint (fallback_cr
);
1055 cairo_destroy (fallback_cr
);
1057 status
= _cairo_surface_to_cgimage ((cairo_surface_t
*) surface
, fallback
, &img
);
1058 if (status
== CAIRO_STATUS_SUCCESS
&& img
== NULL
)
1061 return DO_UNSUPPORTED
;
1063 surface
->sourceImageRect
= CGRectMake (0.0, 0.0, w
, h
);
1064 surface
->sourceImage
= img
;
1065 surface
->sourceImageSurface
= fallback
;
1066 surface
->sourceTransform
= CGAffineTransformMakeTranslation (x0
, y0
);
1071 static cairo_quartz_action_t
1072 _cairo_quartz_setup_linear_source (cairo_quartz_surface_t
*surface
,
1073 cairo_linear_pattern_t
*lpat
)
1075 cairo_pattern_t
*abspat
= (cairo_pattern_t
*) lpat
;
1078 CGFunctionRef gradFunc
;
1079 CGColorSpaceRef rgb
;
1080 bool extend
= abspat
->extend
== CAIRO_EXTEND_PAD
;
1082 if (lpat
->base
.n_stops
== 0) {
1083 CGContextSetRGBStrokeColor (surface
->cgContext
, 0., 0., 0., 0.);
1084 CGContextSetRGBFillColor (surface
->cgContext
, 0., 0., 0., 0.);
1088 cairo_pattern_get_matrix (abspat
, &mat
);
1089 cairo_matrix_invert (&mat
);
1090 _cairo_quartz_cairo_matrix_to_quartz (&mat
, &surface
->sourceTransform
);
1092 rgb
= CGColorSpaceCreateDeviceRGB();
1094 start
= CGPointMake (_cairo_fixed_to_double (lpat
->p1
.x
),
1095 _cairo_fixed_to_double (lpat
->p1
.y
));
1096 end
= CGPointMake (_cairo_fixed_to_double (lpat
->p2
.x
),
1097 _cairo_fixed_to_double (lpat
->p2
.y
));
1099 // ref will be released by the CGShading's destructor
1100 cairo_pattern_reference ((cairo_pattern_t
*) lpat
);
1102 if (abspat
->extend
== CAIRO_EXTEND_NONE
||
1103 abspat
->extend
== CAIRO_EXTEND_PAD
)
1105 gradFunc
= CreateGradientFunction ((cairo_gradient_pattern_t
*) lpat
);
1107 gradFunc
= CreateRepeatingGradientFunction (surface
,
1108 (cairo_gradient_pattern_t
*) lpat
,
1109 &start
, &end
, surface
->sourceTransform
);
1112 surface
->sourceShading
= CGShadingCreateAxial (rgb
,
1117 CGColorSpaceRelease(rgb
);
1118 CGFunctionRelease(gradFunc
);
1123 static cairo_quartz_action_t
1124 _cairo_quartz_setup_radial_source (cairo_quartz_surface_t
*surface
,
1125 cairo_radial_pattern_t
*rpat
)
1127 cairo_pattern_t
*abspat
= (cairo_pattern_t
*)rpat
;
1130 CGFunctionRef gradFunc
;
1131 CGColorSpaceRef rgb
;
1132 bool extend
= abspat
->extend
== CAIRO_EXTEND_PAD
;
1134 if (rpat
->base
.n_stops
== 0) {
1135 CGContextSetRGBStrokeColor (surface
->cgContext
, 0., 0., 0., 0.);
1136 CGContextSetRGBFillColor (surface
->cgContext
, 0., 0., 0., 0.);
1140 if (abspat
->extend
== CAIRO_EXTEND_REPEAT
||
1141 abspat
->extend
== CAIRO_EXTEND_REFLECT
)
1143 /* I started trying to map these to Quartz, but it's much harder
1144 * then the linear case (I think it would involve doing multiple
1145 * Radial shadings). So, instead, let's just render an image
1146 * for pixman to draw the shading into, and use that.
1148 return _cairo_quartz_setup_fallback_source (surface
, (cairo_pattern_t
*) rpat
);
1151 cairo_pattern_get_matrix (abspat
, &mat
);
1152 cairo_matrix_invert (&mat
);
1153 _cairo_quartz_cairo_matrix_to_quartz (&mat
, &surface
->sourceTransform
);
1155 rgb
= CGColorSpaceCreateDeviceRGB();
1157 start
= CGPointMake (_cairo_fixed_to_double (rpat
->c1
.x
),
1158 _cairo_fixed_to_double (rpat
->c1
.y
));
1159 end
= CGPointMake (_cairo_fixed_to_double (rpat
->c2
.x
),
1160 _cairo_fixed_to_double (rpat
->c2
.y
));
1162 // ref will be released by the CGShading's destructor
1163 cairo_pattern_reference ((cairo_pattern_t
*) rpat
);
1165 gradFunc
= CreateGradientFunction ((cairo_gradient_pattern_t
*) rpat
);
1167 surface
->sourceShading
= CGShadingCreateRadial (rgb
,
1169 _cairo_fixed_to_double (rpat
->r1
),
1171 _cairo_fixed_to_double (rpat
->r2
),
1175 CGColorSpaceRelease(rgb
);
1176 CGFunctionRelease(gradFunc
);
1181 static cairo_quartz_action_t
1182 _cairo_quartz_setup_source (cairo_quartz_surface_t
*surface
,
1183 cairo_pattern_t
*source
)
1185 assert (!(surface
->sourceImage
|| surface
->sourceShading
|| surface
->sourcePattern
));
1187 surface
->oldInterpolationQuality
= CGContextGetInterpolationQuality (surface
->cgContext
);
1188 CGContextSetInterpolationQuality (surface
->cgContext
, _cairo_quartz_filter_to_quartz (source
->filter
));
1190 if (source
->type
== CAIRO_PATTERN_TYPE_SOLID
) {
1191 cairo_solid_pattern_t
*solid
= (cairo_solid_pattern_t
*) source
;
1193 CGContextSetRGBStrokeColor (surface
->cgContext
,
1197 solid
->color
.alpha
);
1198 CGContextSetRGBFillColor (surface
->cgContext
,
1202 solid
->color
.alpha
);
1207 if (source
->type
== CAIRO_PATTERN_TYPE_LINEAR
) {
1208 cairo_linear_pattern_t
*lpat
= (cairo_linear_pattern_t
*)source
;
1209 return _cairo_quartz_setup_linear_source (surface
, lpat
);
1213 if (source
->type
== CAIRO_PATTERN_TYPE_RADIAL
) {
1214 cairo_radial_pattern_t
*rpat
= (cairo_radial_pattern_t
*)source
;
1215 return _cairo_quartz_setup_radial_source (surface
, rpat
);
1219 if (source
->type
== CAIRO_PATTERN_TYPE_SURFACE
&&
1220 (source
->extend
== CAIRO_EXTEND_NONE
|| (CGContextDrawTiledImagePtr
&& source
->extend
== CAIRO_EXTEND_REPEAT
)))
1222 cairo_surface_pattern_t
*spat
= (cairo_surface_pattern_t
*) source
;
1223 cairo_surface_t
*pat_surf
= spat
->surface
;
1225 cairo_matrix_t m
= spat
->base
.matrix
;
1226 cairo_rectangle_int_t extents
;
1227 cairo_status_t status
;
1228 CGAffineTransform xform
;
1230 cairo_fixed_t fw
, fh
;
1232 status
= _cairo_surface_to_cgimage ((cairo_surface_t
*) surface
, pat_surf
, &img
);
1233 if (status
== CAIRO_STATUS_SUCCESS
&& img
== NULL
)
1236 return DO_UNSUPPORTED
;
1238 surface
->sourceImage
= img
;
1240 cairo_matrix_invert(&m
);
1241 _cairo_quartz_cairo_matrix_to_quartz (&m
, &surface
->sourceTransform
);
1243 status
= _cairo_surface_get_extents (pat_surf
, &extents
);
1245 return DO_UNSUPPORTED
;
1247 if (source
->extend
== CAIRO_EXTEND_NONE
) {
1248 surface
->sourceImageRect
= CGRectMake (0, 0, extents
.width
, extents
.height
);
1252 /* Quartz seems to tile images at pixel-aligned regions only -- this
1253 * leads to seams if the image doesn't end up scaling to fill the
1254 * space exactly. The CGPattern tiling approach doesn't have this
1255 * problem. Check if we're going to fill up the space (within some
1256 * epsilon), and if not, fall back to the CGPattern type.
1259 xform
= CGAffineTransformConcat (CGContextGetCTM (surface
->cgContext
),
1260 surface
->sourceTransform
);
1262 srcRect
= CGRectMake (0, 0, extents
.width
, extents
.height
);
1263 srcRect
= CGRectApplyAffineTransform (srcRect
, xform
);
1265 fw
= _cairo_fixed_from_double (srcRect
.size
.width
);
1266 fh
= _cairo_fixed_from_double (srcRect
.size
.height
);
1268 if ((fw
& CAIRO_FIXED_FRAC_MASK
) <= CAIRO_FIXED_EPSILON
&&
1269 (fh
& CAIRO_FIXED_FRAC_MASK
) <= CAIRO_FIXED_EPSILON
)
1271 /* We're good to use DrawTiledImage, but ensure that
1272 * the math works out */
1274 srcRect
.size
.width
= round(srcRect
.size
.width
);
1275 srcRect
.size
.height
= round(srcRect
.size
.height
);
1277 xform
= CGAffineTransformInvert (xform
);
1279 srcRect
= CGRectApplyAffineTransform (srcRect
, xform
);
1281 surface
->sourceImageRect
= srcRect
;
1283 return DO_TILED_IMAGE
;
1286 /* Fall through to generic SURFACE case */
1289 if (source
->type
== CAIRO_PATTERN_TYPE_SURFACE
) {
1290 float patternAlpha
= 1.0f
;
1291 CGColorSpaceRef patternSpace
;
1292 CGPatternRef pattern
;
1293 cairo_int_status_t status
;
1295 status
= _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface
, source
, &pattern
);
1296 if (status
== CAIRO_INT_STATUS_NOTHING_TO_DO
)
1299 return DO_UNSUPPORTED
;
1301 // Save before we change the pattern, colorspace, etc. so that
1302 // we can restore and make sure that quartz releases our
1303 // pattern (which may be stack allocated)
1304 CGContextSaveGState(surface
->cgContext
);
1306 patternSpace
= CGColorSpaceCreatePattern(NULL
);
1307 CGContextSetFillColorSpace (surface
->cgContext
, patternSpace
);
1308 CGContextSetFillPattern (surface
->cgContext
, pattern
, &patternAlpha
);
1309 CGContextSetStrokeColorSpace (surface
->cgContext
, patternSpace
);
1310 CGContextSetStrokePattern (surface
->cgContext
, pattern
, &patternAlpha
);
1311 CGColorSpaceRelease (patternSpace
);
1313 /* Quartz likes to munge the pattern phase (as yet unexplained
1314 * why); force it to 0,0 as we've already baked in the correct
1315 * pattern translation into the pattern matrix
1317 CGContextSetPatternPhase (surface
->cgContext
, CGSizeMake(0,0));
1319 surface
->sourcePattern
= pattern
;
1324 return DO_UNSUPPORTED
;
1328 _cairo_quartz_teardown_source (cairo_quartz_surface_t
*surface
,
1329 cairo_pattern_t
*source
)
1331 CGContextSetInterpolationQuality (surface
->cgContext
, surface
->oldInterpolationQuality
);
1333 if (surface
->sourceImage
) {
1334 CGImageRelease(surface
->sourceImage
);
1335 surface
->sourceImage
= NULL
;
1337 cairo_surface_destroy(surface
->sourceImageSurface
);
1338 surface
->sourceImageSurface
= NULL
;
1341 if (surface
->sourceShading
) {
1342 CGShadingRelease(surface
->sourceShading
);
1343 surface
->sourceShading
= NULL
;
1346 if (surface
->sourcePattern
) {
1347 CGPatternRelease(surface
->sourcePattern
);
1348 // To tear down the pattern and colorspace
1349 CGContextRestoreGState(surface
->cgContext
);
1351 surface
->sourcePattern
= NULL
;
1356 * get source/dest image implementation
1359 /* Read the image from the surface's front buffer */
1360 static cairo_int_status_t
1361 _cairo_quartz_get_image (cairo_quartz_surface_t
*surface
,
1362 cairo_image_surface_t
**image_out
)
1364 unsigned char *imageData
;
1365 cairo_image_surface_t
*isurf
;
1367 if (IS_EMPTY(surface
)) {
1368 *image_out
= (cairo_image_surface_t
*) cairo_image_surface_create (CAIRO_FORMAT_ARGB32
, 0, 0);
1369 return CAIRO_STATUS_SUCCESS
;
1372 if (surface
->imageSurfaceEquiv
) {
1373 *image_out
= (cairo_image_surface_t
*) cairo_surface_reference(surface
->imageSurfaceEquiv
);
1374 return CAIRO_STATUS_SUCCESS
;
1377 if (_cairo_quartz_is_cgcontext_bitmap_context(surface
->cgContext
)) {
1378 unsigned int stride
;
1379 unsigned int bitinfo
;
1380 unsigned int bpc
, bpp
;
1381 CGColorSpaceRef colorspace
;
1382 unsigned int color_comps
;
1384 imageData
= (unsigned char *) CGBitmapContextGetData(surface
->cgContext
);
1386 #ifdef USE_10_3_WORKAROUNDS
1387 bitinfo
= CGBitmapContextGetAlphaInfo (surface
->cgContext
);
1389 bitinfo
= CGBitmapContextGetBitmapInfo (surface
->cgContext
);
1391 stride
= CGBitmapContextGetBytesPerRow (surface
->cgContext
);
1392 bpp
= CGBitmapContextGetBitsPerPixel (surface
->cgContext
);
1393 bpc
= CGBitmapContextGetBitsPerComponent (surface
->cgContext
);
1395 // let's hope they don't add YUV under us
1396 colorspace
= CGBitmapContextGetColorSpace (surface
->cgContext
);
1397 color_comps
= CGColorSpaceGetNumberOfComponents(colorspace
);
1399 // XXX TODO: We can handle all of these by converting to
1400 // pixman masks, including non-native-endian masks
1402 return CAIRO_INT_STATUS_UNSUPPORTED
;
1404 if (bpp
!= 32 && bpp
!= 8)
1405 return CAIRO_INT_STATUS_UNSUPPORTED
;
1407 if (color_comps
!= 3 && color_comps
!= 1)
1408 return CAIRO_INT_STATUS_UNSUPPORTED
;
1410 if (bpp
== 32 && color_comps
== 3 &&
1411 (bitinfo
& kCGBitmapAlphaInfoMask
) == kCGImageAlphaPremultipliedFirst
&&
1412 (bitinfo
& kCGBitmapByteOrderMask
) == kCGBitmapByteOrder32Host
)
1414 isurf
= (cairo_image_surface_t
*)
1415 cairo_image_surface_create_for_data (imageData
,
1416 CAIRO_FORMAT_ARGB32
,
1417 surface
->extents
.width
,
1418 surface
->extents
.height
,
1420 } else if (bpp
== 32 && color_comps
== 3 &&
1421 (bitinfo
& kCGBitmapAlphaInfoMask
) == kCGImageAlphaNoneSkipFirst
&&
1422 (bitinfo
& kCGBitmapByteOrderMask
) == kCGBitmapByteOrder32Host
)
1424 isurf
= (cairo_image_surface_t
*)
1425 cairo_image_surface_create_for_data (imageData
,
1427 surface
->extents
.width
,
1428 surface
->extents
.height
,
1430 } else if (bpp
== 8 && color_comps
== 1)
1432 isurf
= (cairo_image_surface_t
*)
1433 cairo_image_surface_create_for_data (imageData
,
1435 surface
->extents
.width
,
1436 surface
->extents
.height
,
1439 return CAIRO_INT_STATUS_UNSUPPORTED
;
1442 return CAIRO_INT_STATUS_UNSUPPORTED
;
1446 return CAIRO_STATUS_SUCCESS
;
1450 * Cairo surface backend implementations
1453 static cairo_status_t
1454 _cairo_quartz_surface_finish (void *abstract_surface
)
1456 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1458 ND((stderr
, "_cairo_quartz_surface_finish[%p] cgc: %p\n", surface
, surface
->cgContext
));
1460 if (IS_EMPTY(surface
))
1461 return CAIRO_STATUS_SUCCESS
;
1463 /* Restore our saved gstate that we use to reset clipping */
1464 CGContextRestoreGState (surface
->cgContext
);
1466 CGContextRelease (surface
->cgContext
);
1468 surface
->cgContext
= NULL
;
1470 if (surface
->imageSurfaceEquiv
) {
1471 cairo_surface_destroy (surface
->imageSurfaceEquiv
);
1472 surface
->imageSurfaceEquiv
= NULL
;
1475 if (surface
->imageData
) {
1476 free (surface
->imageData
);
1477 surface
->imageData
= NULL
;
1480 return CAIRO_STATUS_SUCCESS
;
1483 static cairo_status_t
1484 _cairo_quartz_surface_acquire_source_image (void *abstract_surface
,
1485 cairo_image_surface_t
**image_out
,
1488 cairo_int_status_t status
;
1489 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1491 //ND((stderr, "%p _cairo_quartz_surface_acquire_source_image\n", surface));
1493 status
= _cairo_quartz_get_image (surface
, image_out
);
1495 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
1497 *image_extra
= NULL
;
1499 return CAIRO_STATUS_SUCCESS
;
1503 _cairo_quartz_surface_release_source_image (void *abstract_surface
,
1504 cairo_image_surface_t
*image
,
1507 cairo_surface_destroy ((cairo_surface_t
*) image
);
1511 static cairo_status_t
1512 _cairo_quartz_surface_acquire_dest_image (void *abstract_surface
,
1513 cairo_rectangle_int_t
*interest_rect
,
1514 cairo_image_surface_t
**image_out
,
1515 cairo_rectangle_int_t
*image_rect
,
1518 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1519 cairo_int_status_t status
;
1521 ND((stderr
, "%p _cairo_quartz_surface_acquire_dest_image\n", surface
));
1523 status
= _cairo_quartz_get_image (surface
, image_out
);
1525 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
1527 *image_rect
= surface
->extents
;
1528 *image_extra
= NULL
;
1530 return CAIRO_STATUS_SUCCESS
;
1534 _cairo_quartz_surface_release_dest_image (void *abstract_surface
,
1535 cairo_rectangle_int_t
*interest_rect
,
1536 cairo_image_surface_t
*image
,
1537 cairo_rectangle_int_t
*image_rect
,
1540 //cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;
1542 //ND((stderr, "%p _cairo_quartz_surface_release_dest_image\n", surface));
1544 cairo_surface_destroy ((cairo_surface_t
*) image
);
1547 static cairo_surface_t
*
1548 _cairo_quartz_surface_create_similar (void *abstract_surface
,
1549 cairo_content_t content
,
1553 /*cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface;*/
1555 cairo_format_t format
;
1557 if (content
== CAIRO_CONTENT_COLOR_ALPHA
)
1558 format
= CAIRO_FORMAT_ARGB32
;
1559 else if (content
== CAIRO_CONTENT_COLOR
)
1560 format
= CAIRO_FORMAT_RGB24
;
1561 else if (content
== CAIRO_CONTENT_ALPHA
)
1562 format
= CAIRO_FORMAT_A8
;
1566 // verify width and height of surface
1567 if (!_cairo_quartz_verify_surface_size(width
, height
)) {
1568 return _cairo_surface_create_in_error (_cairo_error
1569 (CAIRO_STATUS_NO_MEMORY
));
1572 return cairo_quartz_surface_create (format
, width
, height
);
1575 static cairo_status_t
1576 _cairo_quartz_surface_clone_similar (void *abstract_surface
,
1577 cairo_surface_t
*src
,
1582 int *clone_offset_x
,
1583 int *clone_offset_y
,
1584 cairo_surface_t
**clone_out
)
1586 cairo_quartz_surface_t
*new_surface
= NULL
;
1587 cairo_format_t new_format
;
1588 CGImageRef quartz_image
= NULL
;
1589 cairo_status_t status
;
1593 // verify width and height of surface
1594 if (!_cairo_quartz_verify_surface_size(width
, height
)) {
1595 return CAIRO_INT_STATUS_UNSUPPORTED
;
1598 if (width
== 0 || height
== 0) {
1599 *clone_out
= (cairo_surface_t
*)
1600 _cairo_quartz_surface_create_internal (NULL
, CAIRO_CONTENT_COLOR_ALPHA
,
1602 *clone_offset_x
= 0;
1603 *clone_offset_y
= 0;
1604 return CAIRO_STATUS_SUCCESS
;
1607 if (src
->backend
->type
== CAIRO_SURFACE_TYPE_QUARTZ
) {
1608 cairo_quartz_surface_t
*qsurf
= (cairo_quartz_surface_t
*) src
;
1610 if (IS_EMPTY(qsurf
)) {
1611 *clone_out
= (cairo_surface_t
*)
1612 _cairo_quartz_surface_create_internal (NULL
, CAIRO_CONTENT_COLOR_ALPHA
,
1613 qsurf
->extents
.width
, qsurf
->extents
.height
);
1614 *clone_offset_x
= 0;
1615 *clone_offset_y
= 0;
1616 return CAIRO_STATUS_SUCCESS
;
1620 status
= _cairo_surface_to_cgimage ((cairo_surface_t
*) abstract_surface
, src
, &quartz_image
);
1622 return CAIRO_INT_STATUS_UNSUPPORTED
;
1624 new_format
= CAIRO_FORMAT_ARGB32
; /* assumed */
1625 if (_cairo_surface_is_image (src
)) {
1626 new_format
= ((cairo_image_surface_t
*) src
)->format
;
1629 new_surface
= (cairo_quartz_surface_t
*)
1630 cairo_quartz_surface_create (new_format
, width
, height
);
1632 if (quartz_image
== NULL
)
1635 if (!new_surface
|| new_surface
->base
.status
) {
1636 CGImageRelease (quartz_image
);
1637 return CAIRO_INT_STATUS_UNSUPPORTED
;
1640 CGContextSaveGState (new_surface
->cgContext
);
1642 CGContextSetCompositeOperation (new_surface
->cgContext
,
1643 kPrivateCGCompositeCopy
);
1645 CGContextTranslateCTM (new_surface
->cgContext
, -src_x
, -src_y
);
1646 CGContextDrawImage (new_surface
->cgContext
,
1647 CGRectMake (0, 0, CGImageGetWidth(quartz_image
), CGImageGetHeight(quartz_image
)),
1650 CGContextRestoreGState (new_surface
->cgContext
);
1652 CGImageRelease (quartz_image
);
1655 *clone_offset_x
= src_x
;
1656 *clone_offset_y
= src_y
;
1657 *clone_out
= (cairo_surface_t
*) new_surface
;
1659 return CAIRO_STATUS_SUCCESS
;
1662 static cairo_int_status_t
1663 _cairo_quartz_surface_get_extents (void *abstract_surface
,
1664 cairo_rectangle_int_t
*extents
)
1666 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1668 *extents
= surface
->extents
;
1670 return CAIRO_STATUS_SUCCESS
;
1673 static cairo_int_status_t
1674 _cairo_quartz_surface_paint (void *abstract_surface
,
1675 cairo_operator_t op
,
1676 cairo_pattern_t
*source
)
1678 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1679 cairo_int_status_t rv
= CAIRO_STATUS_SUCCESS
;
1680 cairo_quartz_action_t action
;
1682 ND((stderr
, "%p _cairo_quartz_surface_paint op %d source->type %d\n", surface
, op
, source
->type
));
1684 if (IS_EMPTY(surface
))
1685 return CAIRO_STATUS_SUCCESS
;
1687 if (op
== CAIRO_OPERATOR_DEST
)
1688 return CAIRO_STATUS_SUCCESS
;
1690 CGContextSetCompositeOperation (surface
->cgContext
, _cairo_quartz_cairo_operator_to_quartz (op
));
1692 action
= _cairo_quartz_setup_source (surface
, source
);
1694 if (action
== DO_SOLID
|| action
== DO_PATTERN
) {
1695 CGContextFillRect (surface
->cgContext
, CGRectMake(surface
->extents
.x
,
1697 surface
->extents
.width
,
1698 surface
->extents
.height
));
1699 } else if (action
== DO_SHADING
) {
1700 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1701 CGContextDrawShading (surface
->cgContext
, surface
->sourceShading
);
1702 } else if (action
== DO_IMAGE
|| action
== DO_TILED_IMAGE
) {
1703 CGContextSaveGState (surface
->cgContext
);
1705 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1706 CGContextTranslateCTM (surface
->cgContext
, 0, surface
->sourceImageRect
.size
.height
);
1707 CGContextScaleCTM (surface
->cgContext
, 1, -1);
1709 if (action
== DO_IMAGE
)
1710 CGContextDrawImage (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1712 CGContextDrawTiledImagePtr (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1713 CGContextRestoreGState (surface
->cgContext
);
1714 } else if (action
!= DO_NOTHING
) {
1715 rv
= CAIRO_INT_STATUS_UNSUPPORTED
;
1718 _cairo_quartz_teardown_source (surface
, source
);
1720 ND((stderr
, "-- paint\n"));
1724 static cairo_int_status_t
1725 _cairo_quartz_surface_fill (void *abstract_surface
,
1726 cairo_operator_t op
,
1727 cairo_pattern_t
*source
,
1728 cairo_path_fixed_t
*path
,
1729 cairo_fill_rule_t fill_rule
,
1731 cairo_antialias_t antialias
)
1733 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1734 cairo_int_status_t rv
= CAIRO_STATUS_SUCCESS
;
1735 cairo_quartz_action_t action
;
1736 quartz_stroke_t stroke
;
1738 CGPathRef path_for_unbounded
= NULL
;
1740 ND((stderr
, "%p _cairo_quartz_surface_fill op %d source->type %d\n", surface
, op
, source
->type
));
1742 if (IS_EMPTY(surface
))
1743 return CAIRO_STATUS_SUCCESS
;
1745 if (op
== CAIRO_OPERATOR_DEST
)
1746 return CAIRO_STATUS_SUCCESS
;
1748 /* Check whether the path would be a no-op */
1749 /* XXX handle unbounded ops */
1750 if (_cairo_path_fixed_is_empty(path
) ||
1751 (_cairo_path_fixed_is_box(path
, &box
) &&
1752 box
.p1
.x
== box
.p2
.x
&&
1753 box
.p1
.y
== box
.p2
.y
))
1755 return CAIRO_STATUS_SUCCESS
;
1758 CGContextSaveGState (surface
->cgContext
);
1760 CGContextSetShouldAntialias (surface
->cgContext
, (antialias
!= CAIRO_ANTIALIAS_NONE
));
1761 CGContextSetCompositeOperation (surface
->cgContext
, _cairo_quartz_cairo_operator_to_quartz (op
));
1763 action
= _cairo_quartz_setup_source (surface
, source
);
1765 CGContextBeginPath (surface
->cgContext
);
1767 stroke
.cgContext
= surface
->cgContext
;
1768 stroke
.ctm_inverse
= NULL
;
1769 rv
= _cairo_quartz_cairo_path_to_quartz_context (path
, &stroke
);
1773 if (!_cairo_operator_bounded_by_mask(op
) && CGContextCopyPathPtr
)
1774 path_for_unbounded
= CGContextCopyPathPtr (surface
->cgContext
);
1776 if (action
== DO_SOLID
|| action
== DO_PATTERN
) {
1777 if (fill_rule
== CAIRO_FILL_RULE_WINDING
)
1778 CGContextFillPath (surface
->cgContext
);
1780 CGContextEOFillPath (surface
->cgContext
);
1781 } else if (action
== DO_SHADING
) {
1783 // we have to clip and then paint the shading; we can't fill
1785 if (fill_rule
== CAIRO_FILL_RULE_WINDING
)
1786 CGContextClip (surface
->cgContext
);
1788 CGContextEOClip (surface
->cgContext
);
1790 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1791 CGContextDrawShading (surface
->cgContext
, surface
->sourceShading
);
1792 } else if (action
== DO_IMAGE
|| action
== DO_TILED_IMAGE
) {
1793 if (fill_rule
== CAIRO_FILL_RULE_WINDING
)
1794 CGContextClip (surface
->cgContext
);
1796 CGContextEOClip (surface
->cgContext
);
1798 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1799 CGContextTranslateCTM (surface
->cgContext
, 0, surface
->sourceImageRect
.size
.height
);
1800 CGContextScaleCTM (surface
->cgContext
, 1, -1);
1802 if (action
== DO_IMAGE
)
1803 CGContextDrawImage (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1805 CGContextDrawTiledImagePtr (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1806 } else if (action
!= DO_NOTHING
) {
1807 rv
= CAIRO_INT_STATUS_UNSUPPORTED
;
1811 _cairo_quartz_teardown_source (surface
, source
);
1813 CGContextRestoreGState (surface
->cgContext
);
1815 if (path_for_unbounded
) {
1816 unbounded_op_data_t ub
;
1817 ub
.op
= UNBOUNDED_STROKE_FILL
;
1818 ub
.u
.stroke_fill
.cgPath
= path_for_unbounded
;
1819 ub
.u
.stroke_fill
.fill_rule
= fill_rule
;
1821 _cairo_quartz_fixup_unbounded_operation (surface
, &ub
, antialias
);
1822 CGPathRelease (path_for_unbounded
);
1825 ND((stderr
, "-- fill\n"));
1829 static cairo_int_status_t
1830 _cairo_quartz_surface_stroke (void *abstract_surface
,
1831 cairo_operator_t op
,
1832 cairo_pattern_t
*source
,
1833 cairo_path_fixed_t
*path
,
1834 cairo_stroke_style_t
*style
,
1835 cairo_matrix_t
*ctm
,
1836 cairo_matrix_t
*ctm_inverse
,
1838 cairo_antialias_t antialias
)
1840 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1841 cairo_int_status_t rv
= CAIRO_STATUS_SUCCESS
;
1842 cairo_quartz_action_t action
;
1843 quartz_stroke_t stroke
;
1844 CGAffineTransform origCTM
, strokeTransform
;
1845 CGPathRef path_for_unbounded
= NULL
;
1847 ND((stderr
, "%p _cairo_quartz_surface_stroke op %d source->type %d\n", surface
, op
, source
->type
));
1849 if (IS_EMPTY(surface
))
1850 return CAIRO_STATUS_SUCCESS
;
1852 if (op
== CAIRO_OPERATOR_DEST
)
1853 return CAIRO_STATUS_SUCCESS
;
1855 CGContextSaveGState (surface
->cgContext
);
1857 // Turning antialiasing off used to cause misrendering with
1858 // single-pixel lines (e.g. 20,10.5 -> 21,10.5 end up being rendered as 2 pixels).
1859 // That's been since fixed in at least 10.5, and in the latest 10.4 dot releases.
1860 CGContextSetShouldAntialias (surface
->cgContext
, (antialias
!= CAIRO_ANTIALIAS_NONE
));
1861 CGContextSetLineWidth (surface
->cgContext
, style
->line_width
);
1862 CGContextSetLineCap (surface
->cgContext
, _cairo_quartz_cairo_line_cap_to_quartz (style
->line_cap
));
1863 CGContextSetLineJoin (surface
->cgContext
, _cairo_quartz_cairo_line_join_to_quartz (style
->line_join
));
1864 CGContextSetMiterLimit (surface
->cgContext
, style
->miter_limit
);
1866 origCTM
= CGContextGetCTM (surface
->cgContext
);
1868 _cairo_quartz_cairo_matrix_to_quartz (ctm
, &strokeTransform
);
1869 CGContextConcatCTM (surface
->cgContext
, strokeTransform
);
1871 if (style
->dash
&& style
->num_dashes
) {
1872 #define STATIC_DASH 32
1873 float sdash
[STATIC_DASH
];
1874 float *fdash
= sdash
;
1875 unsigned int max_dashes
= style
->num_dashes
;
1878 if (style
->num_dashes
%2)
1880 if (max_dashes
> STATIC_DASH
)
1881 fdash
= _cairo_malloc_ab (max_dashes
, sizeof (float));
1883 return _cairo_error (CAIRO_STATUS_NO_MEMORY
);
1885 for (k
= 0; k
< max_dashes
; k
++)
1886 fdash
[k
] = (float) style
->dash
[k
% style
->num_dashes
];
1888 CGContextSetLineDash (surface
->cgContext
, style
->dash_offset
, fdash
, max_dashes
);
1893 CGContextSetCompositeOperation (surface
->cgContext
, _cairo_quartz_cairo_operator_to_quartz (op
));
1895 action
= _cairo_quartz_setup_source (surface
, source
);
1897 CGContextBeginPath (surface
->cgContext
);
1899 stroke
.cgContext
= surface
->cgContext
;
1900 stroke
.ctm_inverse
= ctm_inverse
;
1901 rv
= _cairo_quartz_cairo_path_to_quartz_context (path
, &stroke
);
1905 if (!_cairo_operator_bounded_by_mask (op
) && CGContextCopyPathPtr
)
1906 path_for_unbounded
= CGContextCopyPathPtr (surface
->cgContext
);
1908 if (action
== DO_SOLID
|| action
== DO_PATTERN
) {
1909 CGContextStrokePath (surface
->cgContext
);
1910 } else if (action
== DO_IMAGE
|| action
== DO_TILED_IMAGE
) {
1911 CGContextReplacePathWithStrokedPath (surface
->cgContext
);
1912 CGContextClip (surface
->cgContext
);
1914 CGContextSetCTM (surface
->cgContext
, origCTM
);
1916 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1917 CGContextTranslateCTM (surface
->cgContext
, 0, surface
->sourceImageRect
.size
.height
);
1918 CGContextScaleCTM (surface
->cgContext
, 1, -1);
1920 if (action
== DO_IMAGE
)
1921 CGContextDrawImage (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1923 CGContextDrawTiledImagePtr (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
1924 } else if (action
== DO_SHADING
) {
1925 CGContextReplacePathWithStrokedPath (surface
->cgContext
);
1926 CGContextClip (surface
->cgContext
);
1928 CGContextSetCTM (surface
->cgContext
, origCTM
);
1930 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
1931 CGContextDrawShading (surface
->cgContext
, surface
->sourceShading
);
1932 } else if (action
!= DO_NOTHING
) {
1933 rv
= CAIRO_INT_STATUS_UNSUPPORTED
;
1937 _cairo_quartz_teardown_source (surface
, source
);
1939 CGContextRestoreGState (surface
->cgContext
);
1941 if (path_for_unbounded
) {
1942 unbounded_op_data_t ub
;
1944 CGContextBeginPath (surface
->cgContext
);
1946 /* recreate the stroke state, but without the CTM, as it's been already baked
1949 CGContextSetShouldAntialias (surface
->cgContext
, (antialias
!= CAIRO_ANTIALIAS_NONE
));
1950 CGContextSetLineWidth (surface
->cgContext
, style
->line_width
);
1951 CGContextSetLineCap (surface
->cgContext
, _cairo_quartz_cairo_line_cap_to_quartz (style
->line_cap
));
1952 CGContextSetLineJoin (surface
->cgContext
, _cairo_quartz_cairo_line_join_to_quartz (style
->line_join
));
1953 CGContextSetMiterLimit (surface
->cgContext
, style
->miter_limit
);
1955 CGContextAddPath (surface
->cgContext
, path_for_unbounded
);
1956 CGPathRelease (path_for_unbounded
);
1958 CGContextReplacePathWithStrokedPath (surface
->cgContext
);
1959 path_for_unbounded
= CGContextCopyPathPtr (surface
->cgContext
);
1961 ub
.op
= UNBOUNDED_STROKE_FILL
;
1962 ub
.u
.stroke_fill
.cgPath
= path_for_unbounded
;
1963 ub
.u
.stroke_fill
.fill_rule
= CAIRO_FILL_RULE_WINDING
;
1965 _cairo_quartz_fixup_unbounded_operation (surface
, &ub
, antialias
);
1967 CGPathRelease (path_for_unbounded
);
1970 ND((stderr
, "-- stroke\n"));
1974 #if CAIRO_HAS_QUARTZ_FONT
1975 static cairo_int_status_t
1976 _cairo_quartz_surface_show_glyphs (void *abstract_surface
,
1977 cairo_operator_t op
,
1978 cairo_pattern_t
*source
,
1979 cairo_glyph_t
*glyphs
,
1981 cairo_scaled_font_t
*scaled_font
,
1982 int *remaining_glyphs
)
1984 CGAffineTransform textTransform
, ctm
;
1985 #define STATIC_BUF_SIZE 64
1986 CGGlyph glyphs_static
[STATIC_BUF_SIZE
];
1987 CGSize cg_advances_static
[STATIC_BUF_SIZE
];
1988 CGGlyph
*cg_glyphs
= &glyphs_static
[0];
1989 CGSize
*cg_advances
= &cg_advances_static
[0];
1991 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
1992 cairo_int_status_t rv
= CAIRO_STATUS_SUCCESS
;
1993 cairo_quartz_action_t action
;
1996 CGFontRef cgfref
= NULL
;
1998 cairo_bool_t isClipping
= FALSE
;
1999 cairo_bool_t didForceFontSmoothing
= FALSE
;
2001 if (IS_EMPTY(surface
))
2002 return CAIRO_STATUS_SUCCESS
;
2004 if (num_glyphs
<= 0)
2005 return CAIRO_STATUS_SUCCESS
;
2007 if (op
== CAIRO_OPERATOR_DEST
)
2008 return CAIRO_STATUS_SUCCESS
;
2010 if (cairo_scaled_font_get_type (scaled_font
) != CAIRO_FONT_TYPE_QUARTZ
)
2011 return CAIRO_INT_STATUS_UNSUPPORTED
;
2013 CGContextSaveGState (surface
->cgContext
);
2015 action
= _cairo_quartz_setup_source (surface
, source
);
2016 if (action
== DO_SOLID
|| action
== DO_PATTERN
) {
2017 CGContextSetTextDrawingMode (surface
->cgContext
, kCGTextFill
);
2018 } else if (action
== DO_IMAGE
|| action
== DO_TILED_IMAGE
|| action
== DO_SHADING
) {
2019 CGContextSetTextDrawingMode (surface
->cgContext
, kCGTextClip
);
2022 if (action
!= DO_NOTHING
)
2023 rv
= CAIRO_INT_STATUS_UNSUPPORTED
;
2027 CGContextSetCompositeOperation (surface
->cgContext
, _cairo_quartz_cairo_operator_to_quartz (op
));
2029 /* this doesn't addref */
2030 cgfref
= _cairo_quartz_scaled_font_get_cg_font_ref (scaled_font
);
2031 CGContextSetFont (surface
->cgContext
, cgfref
);
2032 CGContextSetFontSize (surface
->cgContext
, 1.0);
2034 switch (scaled_font
->options
.antialias
) {
2035 case CAIRO_ANTIALIAS_SUBPIXEL
:
2036 CGContextSetShouldAntialias (surface
->cgContext
, TRUE
);
2037 CGContextSetShouldSmoothFonts (surface
->cgContext
, TRUE
);
2038 if (CGContextSetAllowsFontSmoothingPtr
&&
2039 !CGContextGetAllowsFontSmoothingPtr (surface
->cgContext
))
2041 didForceFontSmoothing
= TRUE
;
2042 CGContextSetAllowsFontSmoothingPtr (surface
->cgContext
, TRUE
);
2045 case CAIRO_ANTIALIAS_NONE
:
2046 CGContextSetShouldAntialias (surface
->cgContext
, FALSE
);
2048 case CAIRO_ANTIALIAS_GRAY
:
2049 CGContextSetShouldAntialias (surface
->cgContext
, TRUE
);
2050 CGContextSetShouldSmoothFonts (surface
->cgContext
, FALSE
);
2052 case CAIRO_ANTIALIAS_DEFAULT
:
2053 /* Don't do anything */
2057 if (num_glyphs
> STATIC_BUF_SIZE
) {
2058 cg_glyphs
= (CGGlyph
*) _cairo_malloc_ab (num_glyphs
, sizeof(CGGlyph
));
2059 if (cg_glyphs
== NULL
) {
2060 rv
= _cairo_error (CAIRO_STATUS_NO_MEMORY
);
2064 cg_advances
= (CGSize
*) _cairo_malloc_ab (num_glyphs
, sizeof(CGSize
));
2065 if (cg_advances
== NULL
) {
2066 rv
= _cairo_error (CAIRO_STATUS_NO_MEMORY
);
2071 textTransform
= CGAffineTransformMake (scaled_font
->font_matrix
.xx
,
2072 scaled_font
->font_matrix
.yx
,
2073 scaled_font
->font_matrix
.xy
,
2074 scaled_font
->font_matrix
.yy
,
2076 textTransform
= CGAffineTransformScale (textTransform
, 1.0, -1.0);
2077 textTransform
= CGAffineTransformConcat (CGAffineTransformMake(scaled_font
->ctm
.xx
,
2078 -scaled_font
->ctm
.yx
,
2079 -scaled_font
->ctm
.xy
,
2080 scaled_font
->ctm
.yy
,
2084 CGContextSetTextMatrix (surface
->cgContext
, textTransform
);
2086 /* Convert our glyph positions to glyph advances. We need n-1 advances,
2087 * since the advance at index 0 is applied after glyph 0. */
2088 xprev
= glyphs
[0].x
;
2089 yprev
= glyphs
[0].y
;
2091 cg_glyphs
[0] = glyphs
[0].index
;
2093 for (i
= 1; i
< num_glyphs
; i
++) {
2094 float xf
= glyphs
[i
].x
;
2095 float yf
= glyphs
[i
].y
;
2096 cg_glyphs
[i
] = glyphs
[i
].index
;
2097 cg_advances
[i
-1].width
= xf
- xprev
;
2098 cg_advances
[i
-1].height
= yf
- yprev
;
2103 if (_cairo_quartz_osx_version
>= 0x1050 && isClipping
) {
2104 /* If we're clipping, OSX 10.5 (at least as of 10.5.2) has a
2105 * bug (apple bug ID #5834794) where the glyph
2106 * advances/positions are not transformed by the text matrix
2107 * if kCGTextClip is being used. So, we pre-transform here.
2108 * 10.4 does not have this problem (as of 10.4.11).
2110 for (i
= 0; i
< num_glyphs
- 1; i
++)
2111 cg_advances
[i
] = CGSizeApplyAffineTransform(cg_advances
[i
], textTransform
);
2115 for (i
= 0; i
< num_glyphs
; i
++) {
2116 ND((stderr
, "[%d: %d %f,%f]\n", i
, cg_glyphs
[i
], cg_advances
[i
].width
, cg_advances
[i
].height
));
2120 /* Translate to the first glyph's position before drawing */
2121 ctm
= CGContextGetCTM (surface
->cgContext
);
2122 CGContextTranslateCTM (surface
->cgContext
, glyphs
[0].x
, glyphs
[0].y
);
2124 CGContextShowGlyphsWithAdvances (surface
->cgContext
,
2129 CGContextSetCTM (surface
->cgContext
, ctm
);
2131 if (action
== DO_IMAGE
|| action
== DO_TILED_IMAGE
) {
2132 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
2133 CGContextTranslateCTM (surface
->cgContext
, 0, surface
->sourceImageRect
.size
.height
);
2134 CGContextScaleCTM (surface
->cgContext
, 1, -1);
2136 if (action
== DO_IMAGE
)
2137 CGContextDrawImage (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
2139 CGContextDrawTiledImagePtr (surface
->cgContext
, surface
->sourceImageRect
, surface
->sourceImage
);
2140 } else if (action
== DO_SHADING
) {
2141 CGContextConcatCTM (surface
->cgContext
, surface
->sourceTransform
);
2142 CGContextDrawShading (surface
->cgContext
, surface
->sourceShading
);
2146 _cairo_quartz_teardown_source (surface
, source
);
2148 if (didForceFontSmoothing
)
2149 CGContextSetAllowsFontSmoothingPtr (surface
->cgContext
, FALSE
);
2151 CGContextRestoreGState (surface
->cgContext
);
2153 if (rv
== CAIRO_STATUS_SUCCESS
&&
2155 !_cairo_operator_bounded_by_mask (op
))
2157 unbounded_op_data_t ub
;
2158 ub
.op
= UNBOUNDED_SHOW_GLYPHS
;
2160 ub
.u
.show_glyphs
.isClipping
= isClipping
;
2161 ub
.u
.show_glyphs
.cg_glyphs
= cg_glyphs
;
2162 ub
.u
.show_glyphs
.cg_advances
= cg_advances
;
2163 ub
.u
.show_glyphs
.nglyphs
= num_glyphs
;
2164 ub
.u
.show_glyphs
.textTransform
= textTransform
;
2165 ub
.u
.show_glyphs
.font
= cgfref
;
2166 ub
.u
.show_glyphs
.origin
= CGPointMake (glyphs
[0].x
, glyphs
[0].y
);
2168 _cairo_quartz_fixup_unbounded_operation (surface
, &ub
, scaled_font
->options
.antialias
);
2172 if (cg_advances
!= &cg_advances_static
[0]) {
2176 if (cg_glyphs
!= &glyphs_static
[0]) {
2182 #endif /* CAIRO_HAS_QUARTZ_FONT */
2184 static cairo_int_status_t
2185 _cairo_quartz_surface_mask_with_surface (cairo_quartz_surface_t
*surface
,
2186 cairo_operator_t op
,
2187 cairo_pattern_t
*source
,
2188 cairo_surface_pattern_t
*mask
)
2190 cairo_rectangle_int_t extents
;
2193 cairo_surface_t
*pat_surf
= mask
->surface
;
2194 cairo_status_t status
= CAIRO_STATUS_SUCCESS
;
2195 CGAffineTransform ctm
, mask_matrix
;
2197 status
= _cairo_surface_get_extents (pat_surf
, &extents
);
2201 // everything would be masked out, so do nothing
2202 if (extents
.width
== 0 || extents
.height
== 0)
2203 return CAIRO_STATUS_SUCCESS
;
2205 status
= _cairo_surface_to_cgimage ((cairo_surface_t
*) surface
, pat_surf
, &img
);
2206 if (status
== CAIRO_STATUS_SUCCESS
&& img
== NULL
)
2207 return CAIRO_STATUS_SUCCESS
;
2211 rect
= CGRectMake (0.0f
, 0.0f
, extents
.width
, extents
.height
);
2213 CGContextSaveGState (surface
->cgContext
);
2215 /* ClipToMask is essentially drawing an image, so we need to flip the CTM
2216 * to get the image to appear oriented the right way */
2217 ctm
= CGContextGetCTM (surface
->cgContext
);
2219 _cairo_quartz_cairo_matrix_to_quartz (&mask
->base
.matrix
, &mask_matrix
);
2220 CGContextConcatCTM (surface
->cgContext
, CGAffineTransformInvert(mask_matrix
));
2221 CGContextTranslateCTM (surface
->cgContext
, 0.0f
, rect
.size
.height
);
2222 CGContextScaleCTM (surface
->cgContext
, 1.0f
, -1.0f
);
2224 CGContextClipToMaskPtr (surface
->cgContext
, rect
, img
);
2226 CGContextSetCTM (surface
->cgContext
, ctm
);
2228 status
= _cairo_quartz_surface_paint (surface
, op
, source
);
2230 CGContextRestoreGState (surface
->cgContext
);
2232 if (!_cairo_operator_bounded_by_mask (op
)) {
2233 unbounded_op_data_t ub
;
2234 ub
.op
= UNBOUNDED_MASK
;
2235 ub
.u
.mask
.mask
= img
;
2236 ub
.u
.mask
.maskTransform
= CGAffineTransformInvert(mask_matrix
);
2237 _cairo_quartz_fixup_unbounded_operation (surface
, &ub
, CAIRO_ANTIALIAS_NONE
);
2240 CGImageRelease (img
);
2245 /* This is somewhat less than ideal, but it gets the job done;
2246 * it would be better to avoid calling back into cairo. This
2247 * creates a temporary surface to use as the mask.
2249 static cairo_int_status_t
2250 _cairo_quartz_surface_mask_with_generic (cairo_quartz_surface_t
*surface
,
2251 cairo_operator_t op
,
2252 cairo_pattern_t
*source
,
2253 cairo_pattern_t
*mask
)
2255 int width
= surface
->extents
.width
- surface
->extents
.x
;
2256 int height
= surface
->extents
.height
- surface
->extents
.y
;
2258 cairo_surface_t
*gradient_surf
= NULL
;
2259 cairo_t
*gradient_surf_cr
= NULL
;
2261 cairo_surface_pattern_t surface_pattern
;
2262 cairo_int_status_t status
;
2264 /* Render the gradient to a surface */
2265 gradient_surf
= cairo_quartz_surface_create (CAIRO_FORMAT_ARGB32
,
2268 gradient_surf_cr
= cairo_create(gradient_surf
);
2269 cairo_set_source (gradient_surf_cr
, mask
);
2270 cairo_set_operator (gradient_surf_cr
, CAIRO_OPERATOR_SOURCE
);
2271 cairo_paint (gradient_surf_cr
);
2272 status
= cairo_status (gradient_surf_cr
);
2273 cairo_destroy (gradient_surf_cr
);
2278 _cairo_pattern_init_for_surface (&surface_pattern
, gradient_surf
);
2280 status
= _cairo_quartz_surface_mask_with_surface (surface
, op
, source
, &surface_pattern
);
2282 _cairo_pattern_fini (&surface_pattern
.base
);
2286 cairo_surface_destroy (gradient_surf
);
2291 static cairo_int_status_t
2292 _cairo_quartz_surface_mask (void *abstract_surface
,
2293 cairo_operator_t op
,
2294 cairo_pattern_t
*source
,
2295 cairo_pattern_t
*mask
)
2297 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
2298 cairo_int_status_t rv
= CAIRO_STATUS_SUCCESS
;
2300 ND((stderr
, "%p _cairo_quartz_surface_mask op %d source->type %d mask->type %d\n", surface
, op
, source
->type
, mask
->type
));
2302 if (IS_EMPTY(surface
))
2303 return CAIRO_STATUS_SUCCESS
;
2305 if (mask
->type
== CAIRO_PATTERN_TYPE_SOLID
) {
2306 /* This is easy; we just need to paint with the alpha. */
2307 cairo_solid_pattern_t
*solid_mask
= (cairo_solid_pattern_t
*) mask
;
2309 CGContextSetAlpha (surface
->cgContext
, solid_mask
->color
.alpha
);
2310 rv
= _cairo_quartz_surface_paint (surface
, op
, source
);
2311 CGContextSetAlpha (surface
->cgContext
, 1.0);
2316 /* If we have CGContextClipToMask, we can do more complex masks */
2317 if (CGContextClipToMaskPtr
) {
2318 /* For these, we can skip creating a temporary surface, since we already have one */
2319 if (mask
->type
== CAIRO_PATTERN_TYPE_SURFACE
&& mask
->extend
== CAIRO_EXTEND_NONE
)
2320 return _cairo_quartz_surface_mask_with_surface (surface
, op
, source
, (cairo_surface_pattern_t
*) mask
);
2322 return _cairo_quartz_surface_mask_with_generic (surface
, op
, source
, mask
);
2325 /* So, CGContextClipToMask is not present in 10.3.9, so we're
2326 * doomed; if we have imageData, we can do fallback, otherwise
2327 * just pretend success.
2329 if (surface
->imageData
)
2330 return CAIRO_INT_STATUS_UNSUPPORTED
;
2332 return CAIRO_STATUS_SUCCESS
;
2335 static cairo_int_status_t
2336 _cairo_quartz_surface_intersect_clip_path (void *abstract_surface
,
2337 cairo_path_fixed_t
*path
,
2338 cairo_fill_rule_t fill_rule
,
2340 cairo_antialias_t antialias
)
2342 cairo_quartz_surface_t
*surface
= (cairo_quartz_surface_t
*) abstract_surface
;
2343 quartz_stroke_t stroke
;
2344 cairo_status_t status
;
2346 ND((stderr
, "%p _cairo_quartz_surface_intersect_clip_path path: %p\n", surface
, path
));
2348 if (IS_EMPTY(surface
))
2349 return CAIRO_STATUS_SUCCESS
;
2352 /* If we're being asked to reset the clip, we can only do it
2353 * by restoring the gstate to our previous saved one, and
2356 * Note that this assumes that ALL quartz surface creation
2357 * functions will do a SaveGState first; we do this in create_internal.
2359 CGContextRestoreGState (surface
->cgContext
);
2360 CGContextSaveGState (surface
->cgContext
);
2362 CGContextBeginPath (surface
->cgContext
);
2363 stroke
.cgContext
= surface
->cgContext
;
2364 stroke
.ctm_inverse
= NULL
;
2366 CGContextSetShouldAntialias (surface
->cgContext
, (antialias
!= CAIRO_ANTIALIAS_NONE
));
2368 /* path must not be empty. */
2369 CGContextMoveToPoint (surface
->cgContext
, 0, 0);
2370 status
= _cairo_quartz_cairo_path_to_quartz_context (path
, &stroke
);
2374 if (fill_rule
== CAIRO_FILL_RULE_WINDING
)
2375 CGContextClip (surface
->cgContext
);
2377 CGContextEOClip (surface
->cgContext
);
2380 ND((stderr
, "-- intersect_clip_path\n"));
2382 return CAIRO_STATUS_SUCCESS
;
2385 // XXXtodo implement show_page; need to figure out how to handle begin/end
2387 static const struct _cairo_surface_backend cairo_quartz_surface_backend
= {
2388 CAIRO_SURFACE_TYPE_QUARTZ
,
2389 _cairo_quartz_surface_create_similar
,
2390 _cairo_quartz_surface_finish
,
2391 _cairo_quartz_surface_acquire_source_image
,
2392 _cairo_quartz_surface_release_source_image
,
2393 _cairo_quartz_surface_acquire_dest_image
,
2394 _cairo_quartz_surface_release_dest_image
,
2395 _cairo_quartz_surface_clone_similar
,
2396 NULL
, /* composite */
2397 NULL
, /* fill_rectangles */
2398 NULL
, /* composite_trapezoids */
2399 NULL
, /* copy_page */
2400 NULL
, /* show_page */
2401 NULL
, /* set_clip_region */
2402 _cairo_quartz_surface_intersect_clip_path
,
2403 _cairo_quartz_surface_get_extents
,
2404 NULL
, /* old_show_glyphs */
2405 NULL
, /* get_font_options */
2407 NULL
, /* mark_dirty_rectangle */
2408 NULL
, /* scaled_font_fini */
2409 NULL
, /* scaled_glyph_fini */
2411 _cairo_quartz_surface_paint
,
2412 _cairo_quartz_surface_mask
,
2413 _cairo_quartz_surface_stroke
,
2414 _cairo_quartz_surface_fill
,
2415 #if CAIRO_HAS_QUARTZ_FONT
2416 _cairo_quartz_surface_show_glyphs
,
2418 NULL
, /* show_glyphs */
2421 NULL
, /* snapshot */
2422 NULL
, /* is_similar */
2424 NULL
/* fill_stroke */
2427 cairo_quartz_surface_t
*
2428 _cairo_quartz_surface_create_internal (CGContextRef cgContext
,
2429 cairo_content_t content
,
2431 unsigned int height
)
2433 cairo_quartz_surface_t
*surface
;
2435 quartz_ensure_symbols();
2437 /* Init the base surface */
2438 surface
= malloc(sizeof(cairo_quartz_surface_t
));
2439 if (surface
== NULL
)
2440 return (cairo_quartz_surface_t
*) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY
));
2442 memset(surface
, 0, sizeof(cairo_quartz_surface_t
));
2444 _cairo_surface_init(&surface
->base
, &cairo_quartz_surface_backend
,
2447 /* Save our extents */
2448 surface
->extents
.x
= surface
->extents
.y
= 0;
2449 surface
->extents
.width
= width
;
2450 surface
->extents
.height
= height
;
2452 if (IS_EMPTY(surface
)) {
2453 surface
->cgContext
= NULL
;
2454 surface
->cgContextBaseCTM
= CGAffineTransformIdentity
;
2455 surface
->imageData
= NULL
;
2459 /* Save so we can always get back to a known-good CGContext -- this is
2460 * required for proper behaviour of intersect_clip_path(NULL)
2462 CGContextSaveGState (cgContext
);
2464 surface
->cgContext
= cgContext
;
2465 surface
->cgContextBaseCTM
= CGContextGetCTM (cgContext
);
2467 surface
->imageData
= NULL
;
2468 surface
->imageSurfaceEquiv
= NULL
;
2474 * cairo_quartz_surface_create_for_cg_context
2475 * @cgContext: the existing CGContext for which to create the surface
2476 * @width: width of the surface, in pixels
2477 * @height: height of the surface, in pixels
2479 * Creates a Quartz surface that wraps the given CGContext. The
2480 * CGContext is assumed to be in the standard Cairo coordinate space
2481 * (that is, with the origin at the upper left and the Y axis
2482 * increasing downward). If the CGContext is in the Quartz coordinate
2483 * space (with the origin at the bottom left), then it should be
2484 * flipped before this function is called. The flip can be accomplished
2485 * using a translate and a scale; for example:
2487 * <informalexample><programlisting>
2488 * CGContextTranslateCTM (cgContext, 0.0, height);
2489 * CGContextScaleCTM (cgContext, 1.0, -1.0);
2490 * </programlisting></informalexample>
2492 * All Cairo operations are implemented in terms of Quartz operations,
2493 * as long as Quartz-compatible elements are used (such as Quartz fonts).
2495 * Return value: the newly created Cairo surface.
2501 cairo_quartz_surface_create_for_cg_context (CGContextRef cgContext
,
2503 unsigned int height
)
2505 cairo_quartz_surface_t
*surf
;
2507 CGContextRetain (cgContext
);
2509 surf
= _cairo_quartz_surface_create_internal (cgContext
, CAIRO_CONTENT_COLOR_ALPHA
,
2511 if (surf
->base
.status
) {
2512 CGContextRelease (cgContext
);
2513 // create_internal will have set an error
2514 return (cairo_surface_t
*) surf
;
2517 return (cairo_surface_t
*) surf
;
2521 * cairo_quartz_surface_create
2522 * @format: format of pixels in the surface to create
2523 * @width: width of the surface, in pixels
2524 * @height: height of the surface, in pixels
2526 * Creates a Quartz surface backed by a CGBitmap. The surface is
2527 * created using the Device RGB (or Device Gray, for A8) color space.
2528 * All Cairo operations, including those that require software
2529 * rendering, will succeed on this surface.
2531 * Return value: the newly created surface.
2536 cairo_quartz_surface_create (cairo_format_t format
,
2538 unsigned int height
)
2540 cairo_quartz_surface_t
*surf
;
2542 CGColorSpaceRef cgColorspace
;
2543 CGBitmapInfo bitinfo
;
2546 int bitsPerComponent
;
2548 // verify width and height of surface
2549 if (!_cairo_quartz_verify_surface_size(width
, height
))
2550 return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY
));
2552 if (width
== 0 || height
== 0) {
2553 return (cairo_surface_t
*) _cairo_quartz_surface_create_internal (NULL
, _cairo_content_from_format (format
),
2557 if (format
== CAIRO_FORMAT_ARGB32
||
2558 format
== CAIRO_FORMAT_RGB24
)
2560 cgColorspace
= CGColorSpaceCreateDeviceRGB();
2561 bitinfo
= kCGBitmapByteOrder32Host
;
2562 if (format
== CAIRO_FORMAT_ARGB32
)
2563 bitinfo
|= kCGImageAlphaPremultipliedFirst
;
2565 bitinfo
|= kCGImageAlphaNoneSkipFirst
;
2566 bitsPerComponent
= 8;
2568 /* The Apple docs say that for best performance, the stride and the data
2569 * pointer should be 16-byte aligned. malloc already aligns to 16-bytes,
2570 * so we don't have to anything special on allocation.
2573 stride
+= (16 - (stride
& 15)) & 15;
2574 } else if (format
== CAIRO_FORMAT_A8
) {
2575 cgColorspace
= CGColorSpaceCreateDeviceGray();
2579 stride
= (width
& ~3) + 4;
2580 bitinfo
= kCGImageAlphaNone
;
2581 bitsPerComponent
= 8;
2582 } else if (format
== CAIRO_FORMAT_A1
) {
2583 /* I don't think we can usefully support this, as defined by
2584 * cairo_format_t -- these are 1-bit pixels stored in 32-bit
2587 return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT
));
2589 return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT
));
2592 imageData
= _cairo_malloc_ab (height
, stride
);
2594 CGColorSpaceRelease (cgColorspace
);
2595 return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY
));
2598 /* zero the memory to match the image surface behaviour */
2599 memset (imageData
, 0, height
* stride
);
2601 cgc
= CGBitmapContextCreate (imageData
,
2608 CGColorSpaceRelease (cgColorspace
);
2612 return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY
));
2615 /* flip the Y axis */
2616 CGContextTranslateCTM (cgc
, 0.0, height
);
2617 CGContextScaleCTM (cgc
, 1.0, -1.0);
2619 surf
= _cairo_quartz_surface_create_internal (cgc
, _cairo_content_from_format (format
),
2621 if (surf
->base
.status
) {
2622 CGContextRelease (cgc
);
2624 // create_internal will have set an error
2625 return (cairo_surface_t
*) surf
;
2628 surf
->imageData
= imageData
;
2629 surf
->imageSurfaceEquiv
= cairo_image_surface_create_for_data (imageData
, format
, width
, height
, stride
);
2631 return (cairo_surface_t
*) surf
;
2635 * cairo_quartz_surface_get_cg_context
2636 * @surface: the Cairo Quartz surface
2638 * Returns the CGContextRef that the given Quartz surface is backed
2641 * Return value: the CGContextRef for the given surface.
2646 cairo_quartz_surface_get_cg_context (cairo_surface_t
*surface
)
2648 cairo_quartz_surface_t
*quartz
= (cairo_quartz_surface_t
*)surface
;
2650 if (cairo_surface_get_type(surface
) != CAIRO_SURFACE_TYPE_QUARTZ
)
2653 return quartz
->cgContext
;
2663 void ExportCGImageToPNGFile(CGImageRef inImageRef
, char* dest
)
2665 Handle dataRef
= NULL
;
2667 CFStringRef inPath
= CFStringCreateWithCString(NULL
, dest
, kCFStringEncodingASCII
);
2669 GraphicsExportComponent grex
= 0;
2670 unsigned long sizeWritten
;
2672 ComponentResult result
;
2674 // create the data reference
2675 result
= QTNewDataReferenceFromFullPathCFString(inPath
, kQTNativeDefaultPathStyle
,
2676 0, &dataRef
, &dataRefType
);
2678 if (NULL
!= dataRef
&& noErr
== result
) {
2679 // get the PNG exporter
2680 result
= OpenADefaultComponent(GraphicsExporterComponentType
, kQTFileTypePNG
,
2684 // tell the exporter where to find its source image
2685 result
= GraphicsExportSetInputCGImage(grex
, inImageRef
);
2687 if (noErr
== result
) {
2688 // tell the exporter where to save the exporter image
2689 result
= GraphicsExportSetOutputDataReference(grex
, dataRef
,
2692 if (noErr
== result
) {
2693 // write the PNG file
2694 result
= GraphicsExportDoExport(grex
, &sizeWritten
);
2698 // remember to close the component
2699 CloseComponent(grex
);
2702 // remember to dispose of the data reference handle
2703 DisposeHandle(dataRef
);
2708 quartz_image_to_png (CGImageRef imgref
, char *dest
)
2710 static int sctr
= 0;
2711 char sptr
[] = "/Users/vladimir/Desktop/barXXXXX.png";
2714 fprintf (stderr
, "** Writing %p to bar%d\n", imgref
, sctr
);
2715 sprintf (sptr
, "/Users/vladimir/Desktop/bar%d.png", sctr
);
2720 ExportCGImageToPNGFile(imgref
, dest
);
2724 quartz_surface_to_png (cairo_quartz_surface_t
*nq
, char *dest
)
2726 static int sctr
= 0;
2727 char sptr
[] = "/Users/vladimir/Desktop/fooXXXXX.png";
2729 if (nq
->base
.type
!= CAIRO_SURFACE_TYPE_QUARTZ
) {
2730 fprintf (stderr
, "** quartz_surface_to_png: surface %p isn't quartz!\n", nq
);
2735 fprintf (stderr
, "** Writing %p to foo%d\n", nq
, sctr
);
2736 sprintf (sptr
, "/Users/vladimir/Desktop/foo%d.png", sctr
);
2741 CGImageRef imgref
= CGBitmapContextCreateImage (nq
->cgContext
);
2742 if (imgref
== NULL
) {
2743 fprintf (stderr
, "quartz surface at %p is not a bitmap context!\n", nq
);
2747 ExportCGImageToPNGFile(imgref
, dest
);
2749 CGImageRelease(imgref
);
2752 #endif /* QUARTZ_DEBUG */