diff options
author | Uli Schlachter <psychon@znc.in> | 2022-02-25 15:30:40 +0000 |
---|---|---|
committer | Uli Schlachter <psychon@znc.in> | 2022-02-25 15:30:40 +0000 |
commit | ebba42b19161961b524154a2037aa848405b0db3 (patch) | |
tree | 51a4d135f488abd05d68a000f559029d85c3bc2d | |
parent | 4fc72919e149a3fd26a863757832d67c661b6727 (diff) | |
parent | 2e0075e265155ff144ced23ff87c4bed4cdfbc27 (diff) | |
download | cairo-ebba42b19161961b524154a2037aa848405b0db3.tar.gz |
Merge branch 'quartz-snapshot' into 'master'
quartz: Improve use of images for better performance.
See merge request cairo/cairo!282
-rw-r--r-- | .gitlab-ci/ignore-quartz-argb32.txt | 1 | ||||
-rw-r--r-- | src/cairo-quartz-private.h | 6 | ||||
-rw-r--r-- | src/cairo-quartz-surface.c | 661 | ||||
-rw-r--r-- | src/cairo-types-private.h | 3 | ||||
-rw-r--r-- | test/reference/mask.quartz.argb32.ref.png | bin | 10599 -> 10578 bytes | |||
-rw-r--r-- | test/reference/trap-clip.quartz.argb32.ref.png | bin | 6108 -> 6089 bytes |
6 files changed, 375 insertions, 296 deletions
diff --git a/.gitlab-ci/ignore-quartz-argb32.txt b/.gitlab-ci/ignore-quartz-argb32.txt index b09168133..e00077831 100644 --- a/.gitlab-ci/ignore-quartz-argb32.txt +++ b/.gitlab-ci/ignore-quartz-argb32.txt @@ -14,7 +14,6 @@ ft-text-vertical-layout-type1 ft-text-vertical-layout-type3 negative-stride-image operator-www -pdf-operators-text radial-gradient radial-gradient-mask radial-gradient-mask-source diff --git a/src/cairo-quartz-private.h b/src/cairo-quartz-private.h index 42e1f9e91..609a9052c 100644 --- a/src/cairo-quartz-private.h +++ b/src/cairo-quartz-private.h @@ -55,7 +55,7 @@ typedef enum { DO_DIRECT, DO_SHADING, DO_IMAGE, - DO_TILED_IMAGE + DO_LAYER } cairo_quartz_action_t; /* define CTFontRef for pre-10.5 SDKs */ @@ -67,12 +67,14 @@ typedef struct cairo_quartz_surface { CGContextRef cgContext; CGAffineTransform cgContextBaseCTM; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 10600 void *imageData; - cairo_surface_t *imageSurfaceEquiv; +#endif cairo_surface_clipper_t clipper; cairo_rectangle_int_t extents; cairo_rectangle_int_t virtual_extents; + CGLayerRef cgLayer; } cairo_quartz_surface_t; typedef struct cairo_quartz_image_surface { diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index a92c8fed3..289fd213a 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -111,8 +111,6 @@ CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeM /* Some of these are present in earlier versions of the OS than where * they are public; other are not public at all */ -/* public since 10.5 */ -static void (*CGContextDrawTiledImagePtr) (CGContextRef, CGRect, CGImageRef) = NULL; /* public since 10.6 */ static CGPathRef (*CGContextCopyPathPtr) (CGContextRef) = NULL; @@ -129,10 +127,25 @@ static cairo_bool_t _cairo_quartz_symbol_lookup_done = FALSE; */ #ifdef QUARTZ_DEBUG -static void quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest); -static void quartz_image_to_png (CGImageRef, char *dest); +static void quartz_surface_to_png (cairo_quartz_surface_t *nq, const char *dest); +static void quartz_image_to_png (CGImageRef, const char *dest); #endif +typedef struct +{ + cairo_surface_t base; + CGImageRef image; +} cairo_quartz_snapshot_t; + +static cairo_surface_t* _cairo_quartz_snapshot_create (cairo_quartz_surface_t *surface); +static cairo_status_t _cairo_quartz_snapshot_finish (void* surface); +static CGImageRef _cairo_quartz_surface_snapshot_get_image (cairo_quartz_surface_t *surface); + +static const cairo_surface_backend_t cairo_quartz_snapshot_backend = { + CAIRO_INTERNAL_SURFACE_TYPE_QUARTZ_SNAPSHOT, + _cairo_quartz_snapshot_finish, +}; + static cairo_quartz_surface_t * _cairo_quartz_surface_create_internal (CGContextRef cgContext, cairo_content_t content, @@ -145,7 +158,6 @@ static void quartz_ensure_symbols (void) if (likely (_cairo_quartz_symbol_lookup_done)) return; - CGContextDrawTiledImagePtr = dlsym (RTLD_DEFAULT, "CGContextDrawTiledImage"); CGContextGetTypePtr = dlsym (RTLD_DEFAULT, "CGContextGetType"); CGContextCopyPathPtr = dlsym (RTLD_DEFAULT, "CGContextCopyPath"); CGContextGetAllowsFontSmoothingPtr = dlsym (RTLD_DEFAULT, "CGContextGetAllowsFontSmoothing"); @@ -202,6 +214,8 @@ CairoQuartzCreateCGImage (cairo_format_t format, case CAIRO_FORMAT_RGB30: case CAIRO_FORMAT_RGB16_565: + case CAIRO_FORMAT_RGB96F: + case CAIRO_FORMAT_RGBA128F: case CAIRO_FORMAT_INVALID: default: return NULL; @@ -810,10 +824,12 @@ _cairo_surface_to_cgimage (cairo_surface_t *source, } if (_cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) { - *image_out = CGBitmapContextCreateImage (surface->cgContext); - if (*image_out) - return CAIRO_STATUS_SUCCESS; + *image_out = _cairo_quartz_surface_snapshot_get_image (surface); + return CAIRO_STATUS_SUCCESS; } + + *image_out = NULL; + return CAIRO_STATUS_SURFACE_TYPE_MISMATCH; } if (source->type == CAIRO_SURFACE_TYPE_RECORDING) { @@ -940,15 +956,15 @@ SurfacePatternReleaseInfoFunc (void *ainfo) } static cairo_int_status_t -_cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t *dest, - const cairo_pattern_t *apattern, +_cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t *surface, + const cairo_pattern_t *source, const cairo_clip_t *clip, CGPatternRef *cgpat) { - cairo_surface_pattern_t *spattern; - cairo_surface_t *pat_surf; + cairo_surface_pattern_t *spattern = (cairo_surface_pattern_t *) source; + cairo_surface_t *pat_surf = spattern->surface; cairo_rectangle_int_t extents; - cairo_format_t format = _cairo_format_from_content (dest->base.content); + cairo_format_t format = _cairo_format_from_content (surface->base.content); CGImageRef image; CGRect pbounds; @@ -959,42 +975,28 @@ _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t SurfacePatternDrawInfo *info; cairo_quartz_float_t rw, rh; cairo_status_t status; - cairo_bool_t is_bounded; - - cairo_matrix_t m; + cairo_bool_t is_bounded = _cairo_surface_get_extents (pat_surf, &extents); + cairo_matrix_t m = spattern->base.matrix; /* SURFACE is the only type we'll handle here */ - assert (apattern->type == CAIRO_PATTERN_TYPE_SURFACE); + assert (source->type == CAIRO_PATTERN_TYPE_SURFACE); - spattern = (cairo_surface_pattern_t *) apattern; - pat_surf = spattern->surface; - - if (pat_surf->type != CAIRO_SURFACE_TYPE_RECORDING) { - is_bounded = _cairo_surface_get_extents (pat_surf, &extents); + if (pat_surf->type != CAIRO_SURFACE_TYPE_RECORDING) assert (is_bounded); - } - else - _cairo_surface_get_extents (&dest->base, &extents); - m = spattern->base.matrix; status = _cairo_surface_to_cgimage (pat_surf, &extents, format, &m, clip, &image); + if (unlikely (status)) return status; info = _cairo_malloc (sizeof (SurfacePatternDrawInfo)); if (unlikely (!info)) + { + CGImageRelease (image); return CAIRO_STATUS_NO_MEMORY; + } - /* XXX -- if we're printing, we may need to call CGImageCreateCopy to make sure - * that the data will stick around for this image when the printer gets to it. - * Otherwise, the underlying data store may disappear from under us! - * - * _cairo_surface_to_cgimage will copy when it converts non-Quartz surfaces, - * since the Quartz surfaces have a higher chance of sticking around. If the - * source is a quartz image surface, then it's set up to retain a ref to the - * image surface that it's backed by. - */ info->image = image; info->imageBounds = CGRectMake (0, 0, extents.width, extents.height); info->do_reflect = FALSE; @@ -1002,13 +1004,22 @@ _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t pbounds.origin.x = 0; pbounds.origin.y = 0; - if (spattern->base.extend == CAIRO_EXTEND_REFLECT) { + switch (spattern->base.extend) { + case CAIRO_EXTEND_NONE: + break; + case CAIRO_EXTEND_REPEAT: + pbounds.size.width = extents.width; + pbounds.size.height = extents.height; + break; + case CAIRO_EXTEND_REFLECT: pbounds.size.width = 2.0 * extents.width; pbounds.size.height = 2.0 * extents.height; info->do_reflect = TRUE; - } else { + break; + case CAIRO_EXTEND_PAD: pbounds.size.width = extents.width; pbounds.size.height = extents.height; + break; } rw = pbounds.size.width; rh = pbounds.size.height; @@ -1021,12 +1032,12 @@ _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t * So we take the pattern matrix and the original context matrix, * which gives us the correct base translation/y flip. */ - ptransform = CGAffineTransformConcat (stransform, dest->cgContextBaseCTM); + ptransform = CGAffineTransformConcat (stransform, surface->cgContextBaseCTM); #ifdef QUARTZ_DEBUG ND ((stderr, " pbounds: %f %f %f %f\n", pbounds.origin.x, pbounds.origin.y, pbounds.size.width, pbounds.size.height)); ND ((stderr, " pattern xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", ptransform.tx, ptransform.ty, ptransform.a, ptransform.b, ptransform.c, ptransform.d)); - CGAffineTransform xform = CGContextGetCTM (dest->cgContext); + CGAffineTransform xform = CGContextGetCTM (surface->cgContext); ND ((stderr, " context xform: t: %f %f xx: %f xy: %f yx: %f yy: %f\n", xform.tx, xform.ty, xform.a, xform.b, xform.c, xform.d)); #endif @@ -1058,10 +1069,10 @@ typedef struct { /* Destination rect */ CGRect rect; - /* Used with DO_SHADING, DO_IMAGE and DO_TILED_IMAGE */ + /* Used with DO_SHADING, DO_IMAGE */ CGAffineTransform transform; - /* Used with DO_IMAGE and DO_TILED_IMAGE */ + /* Used with DO_IMAGE */ CGImageRef image; /* Used with DO_SHADING */ @@ -1072,6 +1083,124 @@ typedef struct { CGRect clipRect; } cairo_quartz_drawing_state_t; +static cairo_int_status_t +_cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, + const cairo_pattern_t *source, + cairo_quartz_surface_t *surface, + const cairo_clip_t *clip, + cairo_operator_t op) +{ + const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; + cairo_surface_t *pat_surf = spat->surface; + cairo_matrix_t m = spat->base.matrix; + cairo_format_t format = _cairo_format_from_content (surface->base.content); + cairo_rectangle_int_t extents, pattern_extents; + CGImageRef img; + + cairo_quartz_float_t patternAlpha = 1.0f; + CGColorSpaceRef patternSpace; + CGPatternRef cgpat = NULL; + cairo_int_status_t status; + + _cairo_surface_get_extents (&surface->base, &extents); + + if (pat_surf->backend->type == CAIRO_SURFACE_TYPE_QUARTZ) { + cairo_quartz_surface_t *quartz_surf = (cairo_quartz_surface_t *) pat_surf; + + if (quartz_surf->cgLayer && source->extend == CAIRO_EXTEND_NONE) { + cairo_matrix_invert (&m); + _cairo_quartz_cairo_matrix_to_quartz (&m, &state->transform); + state->rect = CGRectMake (0, 0, quartz_surf->extents.width, quartz_surf->extents.height); + state->layer = quartz_surf->cgLayer; + state->action = DO_LAYER; + return CAIRO_STATUS_SUCCESS; + } + } + + status = _cairo_surface_to_cgimage (pat_surf, &extents, format, + &m, clip, &img); // Note that only pat_surf will get used! + if (unlikely (status)) + return status; + + state->image = img; + + if (state->filter == kCGInterpolationNone && _cairo_matrix_is_translation (&m)) { + m.x0 = -ceil (m.x0 - 0.5); + m.y0 = -ceil (m.y0 - 0.5); + } else { + cairo_matrix_invert (&m); + } + + _cairo_quartz_cairo_matrix_to_quartz (&m, &state->transform); + + if (pat_surf->type != CAIRO_SURFACE_TYPE_RECORDING) { + cairo_bool_t is_bounded = _cairo_surface_get_extents (pat_surf, &pattern_extents); + assert (is_bounded); + } else { + _cairo_surface_get_extents (&surface->base, &pattern_extents); + } + + if (source->extend == CAIRO_EXTEND_NONE) { + int x, y; + + if (op == CAIRO_OPERATOR_SOURCE && + (pat_surf->content == CAIRO_CONTENT_ALPHA || + ! _cairo_matrix_is_integer_translation (&m, &x, &y))) + { + state->layer = CGLayerCreateWithContext (surface->cgContext, + state->clipRect.size, + NULL); + state->cgDrawContext = CGLayerGetContext (state->layer); + CGContextTranslateCTM (state->cgDrawContext, + -state->clipRect.origin.x, + -state->clipRect.origin.y); + } + + CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 1); + + state->rect = CGRectMake (0, 0, pattern_extents.width, pattern_extents.height); + state->action = DO_IMAGE; + return CAIRO_STATUS_SUCCESS; + } + + if (source->extend == CAIRO_EXTEND_REPEAT) + { + CGAffineTransform xform = CGAffineTransformConcat (CGContextGetCTM (state->cgDrawContext), + state->transform); + CGRect srcRect = CGRectMake (0, 0, extents.width, extents.height); + srcRect = CGRectApplyAffineTransform (srcRect, xform); + xform = CGAffineTransformInvert (xform); + srcRect = CGRectApplyAffineTransform (srcRect, xform); + state->rect = srcRect; + } + + status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, clip, &cgpat); + if (unlikely (status)) + return status; + + patternSpace = CGColorSpaceCreatePattern (NULL); + /* To pass pthread-same-source. */ + if (source->extend == CAIRO_EXTEND_REPEAT) + CGContextSetInterpolationQuality(state->cgDrawContext, state->filter); + CGContextSetFillColorSpace (state->cgDrawContext, patternSpace); + CGContextSetFillPattern (state->cgDrawContext, cgpat, &patternAlpha); + CGContextSetStrokeColorSpace (state->cgDrawContext, patternSpace); + CGContextSetStrokePattern (state->cgDrawContext, cgpat, &patternAlpha); + CGColorSpaceRelease (patternSpace); + + /* Quartz likes to munge the pattern phase (as yet unexplained + * why); force it to 0,0 as we've already baked in the correct + * pattern translation into the pattern matrix + */ + CGContextSetPatternPhase (state->cgDrawContext, CGSizeMake (0, 0)); + + CGPatternRelease (cgpat); + + state->action = DO_DIRECT; + return CAIRO_STATUS_SUCCESS; +} + + /* Quartz does not support repeating gradients. We handle repeating gradients by manually extending the gradient and repeating color stops. We need to @@ -1144,13 +1273,13 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state, const cairo_clip_t *clip = composite->clip; cairo_bool_t needs_temp; cairo_status_t status; - cairo_format_t format = _cairo_format_from_content (composite->surface->content); state->layer = NULL; state->image = NULL; state->shading = NULL; state->cgDrawContext = NULL; state->cgMaskContext = NULL; + state->layer = NULL; status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); if (unlikely (status)) @@ -1245,131 +1374,9 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state, return _cairo_quartz_setup_gradient_source (state, gpat, &extents); } - if (source->type == CAIRO_PATTERN_TYPE_SURFACE && - (source->extend == CAIRO_EXTEND_NONE || (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT))) - { - const cairo_surface_pattern_t *spat = (const cairo_surface_pattern_t *) source; - cairo_surface_t *pat_surf = spat->surface; - CGImageRef img; - cairo_matrix_t m = spat->base.matrix; - cairo_rectangle_int_t extents; - CGAffineTransform xform; - CGRect srcRect; - cairo_fixed_t fw, fh; - cairo_bool_t is_bounded; - - _cairo_surface_get_extents (composite->surface, &extents); - status = _cairo_surface_to_cgimage (pat_surf, &extents, format, - &m, clip, &img); - if (unlikely (status)) - return status; - - state->image = img; - - if (state->filter == kCGInterpolationNone && _cairo_matrix_is_translation (&m)) { - m.x0 = -ceil (m.x0 - 0.5); - m.y0 = -ceil (m.y0 - 0.5); - } else { - cairo_matrix_invert (&m); - } - - _cairo_quartz_cairo_matrix_to_quartz (&m, &state->transform); - - if (pat_surf->type != CAIRO_SURFACE_TYPE_RECORDING) { - is_bounded = _cairo_surface_get_extents (pat_surf, &extents); - assert (is_bounded); - } - srcRect = CGRectMake (0, 0, extents.width, extents.height); - - if (source->extend == CAIRO_EXTEND_NONE) { - int x, y; - if (op == CAIRO_OPERATOR_SOURCE && - (pat_surf->content == CAIRO_CONTENT_ALPHA || - ! _cairo_matrix_is_integer_translation (&m, &x, &y))) - { - state->layer = CGLayerCreateWithContext (surface->cgContext, - state->clipRect.size, - NULL); - state->cgDrawContext = CGLayerGetContext (state->layer); - CGContextTranslateCTM (state->cgDrawContext, - -state->clipRect.origin.x, - -state->clipRect.origin.y); - } - - CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 1); - - state->rect = srcRect; - state->action = DO_IMAGE; - return CAIRO_STATUS_SUCCESS; - } - - CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 1); - - /* Quartz seems to tile images at pixel-aligned regions only -- this - * leads to seams if the image doesn't end up scaling to fill the - * space exactly. The CGPattern tiling approach doesn't have this - * problem. Check if we're going to fill up the space (within some - * epsilon), and if not, fall back to the CGPattern type. - */ - - xform = CGAffineTransformConcat (CGContextGetCTM (state->cgDrawContext), - state->transform); - - srcRect = CGRectApplyAffineTransform (srcRect, xform); - - fw = _cairo_fixed_from_double (srcRect.size.width); - fh = _cairo_fixed_from_double (srcRect.size.height); - - if ((fw & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON && - (fh & CAIRO_FIXED_FRAC_MASK) <= CAIRO_FIXED_EPSILON) - { - /* We're good to use DrawTiledImage, but ensure that - * the math works out */ - - srcRect.size.width = round (srcRect.size.width); - srcRect.size.height = round (srcRect.size.height); - - xform = CGAffineTransformInvert (xform); - - srcRect = CGRectApplyAffineTransform (srcRect, xform); - - state->rect = srcRect; - state->action = DO_TILED_IMAGE; - return CAIRO_STATUS_SUCCESS; - } - - /* Fall through to generic SURFACE case */ - } - - if (source->type == CAIRO_PATTERN_TYPE_SURFACE) { - cairo_quartz_float_t patternAlpha = 1.0f; - CGColorSpaceRef patternSpace; - CGPatternRef pattern = NULL; - cairo_int_status_t status; - - status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, clip, &pattern); - if (unlikely (status)) - return status; - - patternSpace = CGColorSpaceCreatePattern (NULL); - CGContextSetFillColorSpace (state->cgDrawContext, patternSpace); - CGContextSetFillPattern (state->cgDrawContext, pattern, &patternAlpha); - CGContextSetStrokeColorSpace (state->cgDrawContext, patternSpace); - CGContextSetStrokePattern (state->cgDrawContext, pattern, &patternAlpha); - CGColorSpaceRelease (patternSpace); - - /* Quartz likes to munge the pattern phase (as yet unexplained - * why); force it to 0,0 as we've already baked in the correct - * pattern translation into the pattern matrix - */ - CGContextSetPatternPhase (state->cgDrawContext, CGSizeMake (0, 0)); - - CGPatternRelease (pattern); - - state->action = DO_DIRECT; - return CAIRO_STATUS_SUCCESS; - } + if (source->type == CAIRO_PATTERN_TYPE_SURFACE) + return _cairo_quartz_setup_pattern_source (state, source, surface, clip, op); return CAIRO_INT_STATUS_UNSUPPORTED; } @@ -1381,9 +1388,10 @@ _cairo_quartz_teardown_state (cairo_quartz_drawing_state_t *state, cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) extents->surface; if (state->layer) { - CGContextDrawLayerInRect (surface->cgContext, - state->clipRect, - state->layer); + if (state->action != DO_LAYER) + CGContextDrawLayerInRect (surface->cgContext, + state->clipRect, + state->layer); CGLayerRelease (state->layer); } @@ -1397,6 +1405,29 @@ _cairo_quartz_teardown_state (cairo_quartz_drawing_state_t *state, CGShadingRelease (state->shading); } +static inline void +_cairo_quartz_draw_cgcontext (cairo_quartz_drawing_state_t *state, + cairo_operator_t op) +{ + if (! (op == CAIRO_OPERATOR_SOURCE && + state->cgDrawContext == state->cgMaskContext)) + return; + + CGContextBeginPath (state->cgDrawContext); + CGContextAddRect (state->cgDrawContext, state->rect); + + CGContextTranslateCTM (state->cgDrawContext, 0, state->rect.size.height); + CGContextScaleCTM (state->cgDrawContext, 1, -1); + CGContextConcatCTM (state->cgDrawContext, + CGAffineTransformInvert (state->transform)); + + CGContextAddRect (state->cgDrawContext, state->clipRect); + + CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 0); + CGContextEOFillPath (state->cgDrawContext); +} + + static void _cairo_quartz_draw_source (cairo_quartz_drawing_state_t *state, cairo_operator_t op) @@ -1421,25 +1452,16 @@ _cairo_quartz_draw_source (cairo_quartz_drawing_state_t *state, if (state->action == DO_IMAGE) { CGContextDrawImage (state->cgDrawContext, state->rect, state->image); - if (op == CAIRO_OPERATOR_SOURCE && - state->cgDrawContext == state->cgMaskContext) - { - CGContextBeginPath (state->cgDrawContext); - CGContextAddRect (state->cgDrawContext, state->rect); - - CGContextTranslateCTM (state->cgDrawContext, 0, state->rect.size.height); - CGContextScaleCTM (state->cgDrawContext, 1, -1); - CGContextConcatCTM (state->cgDrawContext, - CGAffineTransformInvert (state->transform)); - - CGContextAddRect (state->cgDrawContext, state->clipRect); + _cairo_quartz_draw_cgcontext (state, op); + return; + } - CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 0); - CGContextEOFillPath (state->cgDrawContext); - } - } else { - CGContextDrawTiledImagePtr (state->cgDrawContext, state->rect, state->image); + if (state->action == DO_LAYER) { + CGContextDrawLayerInRect (state->cgDrawContext, state->rect, state->layer); + _cairo_quartz_draw_cgcontext (state, op); + return; } + assert (FALSE); // Unreachable } static cairo_image_surface_t * @@ -1447,19 +1469,17 @@ _cairo_quartz_surface_map_to_image (void *abstract_surface, const cairo_rectangle_int_t *extents) { cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; + cairo_surface_t *return_surface = NULL; unsigned int stride, bitinfo, bpp, color_comps; CGColorSpaceRef colorspace; void *imageData; cairo_format_t format; - if (surface->imageSurfaceEquiv) - return _cairo_surface_map_to_image (surface->imageSurfaceEquiv, extents); - if (IS_EMPTY (surface)) return (cairo_image_surface_t *) cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); if (! _cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) - return _cairo_image_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + return _cairo_image_surface_create_in_error (_cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH)); bitinfo = CGBitmapContextGetBitmapInfo (surface->cgContext); bpp = CGBitmapContextGetBitsPerPixel (surface->cgContext); @@ -1483,34 +1503,32 @@ _cairo_quartz_surface_map_to_image (void *abstract_surface, { format = CAIRO_FORMAT_RGB24; } - else if (bpp == 8 && color_comps == 1) + else if (bpp == 8 && color_comps == 0) { - format = CAIRO_FORMAT_A1; + format = CAIRO_FORMAT_A8; } else { - return _cairo_image_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + return _cairo_image_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_FORMAT)); } imageData = CGBitmapContextGetData (surface->cgContext); stride = CGBitmapContextGetBytesPerRow (surface->cgContext); - return (cairo_image_surface_t *) cairo_image_surface_create_for_data (imageData, - format, - extents->width, - extents->height, - stride); + imageData += extents->y * stride + extents->x * bpp / 8; + return_surface = cairo_image_surface_create_for_data (imageData, + format, + extents->width, + extents->height, + stride); + + return (cairo_image_surface_t *) return_surface; } static cairo_int_status_t _cairo_quartz_surface_unmap_image (void *abstract_surface, cairo_image_surface_t *image) { - cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) abstract_surface; - - if (surface->imageSurfaceEquiv) - return _cairo_surface_unmap_image (surface->imageSurfaceEquiv, image); - cairo_surface_finish (&image->base); cairo_surface_destroy (&image->base); @@ -1540,13 +1558,18 @@ _cairo_quartz_surface_finish (void *abstract_surface) surface->cgContext = NULL; - if (surface->imageSurfaceEquiv) { - cairo_surface_destroy (surface->imageSurfaceEquiv); - surface->imageSurfaceEquiv = NULL; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 10600 + if (surface->imageData) { + free (surface->imageData); + surface->imageData = NULL; } +#endif - free (surface->imageData); - surface->imageData = NULL; + if (surface->cgLayer) + { + CGLayerRelease (surface->cgLayer); + surface->cgLayer = NULL; + } return CAIRO_STATUS_SUCCESS; } @@ -1580,6 +1603,46 @@ _cairo_quartz_surface_release_source_image (void *abstract_surface, _cairo_quartz_surface_unmap_image (abstract_surface, image); } +static cairo_surface_t* +_cairo_quartz_surface_create_with_cglayer (cairo_quartz_surface_t *surface, + cairo_content_t content, + int width, int height) +{ + CGAffineTransform xform; + CGContextRef context; + CGLayerRef layer; + cairo_quartz_surface_t* new_surface; + + if (surface->cgContext == NULL || surface->cgLayer != NULL) + return NULL; + + if (width <= 0 || height <= 0) + return NULL; + + xform = CGContextGetUserSpaceToDeviceSpaceTransform (surface->cgContext); + layer = CGLayerCreateWithContext (surface->cgContext, + CGSizeMake (width * xform.a, + height * xform.d), + NULL); + + context = CGLayerGetContext (layer); + CGContextTranslateCTM (context, 0.0, height); + CGContextScaleCTM (context, xform.a, -xform.d); + new_surface = _cairo_quartz_surface_create_internal (context, content, + width, height); + if (unlikely (new_surface->base.status)) + { + CGContextRelease (context); + CGLayerRelease (layer); + return &new_surface->base; + } + new_surface->cgLayer = CGLayerRetain(layer); + CGContextRetain(context); + new_surface->virtual_extents = surface->virtual_extents; + + return &new_surface->base; +} + static cairo_surface_t * _cairo_quartz_surface_create_similar (void *abstract_surface, cairo_content_t content, @@ -1590,6 +1653,17 @@ _cairo_quartz_surface_create_similar (void *abstract_surface, cairo_surface_t *similar; cairo_format_t format; + // verify width and height of surface + if (!_cairo_quartz_verify_surface_size (width, height)) { + return _cairo_surface_create_in_error (_cairo_error + (CAIRO_STATUS_INVALID_SIZE)); + } + + surface = (cairo_quartz_surface_t *) abstract_surface; + if (surface->cgContext && !surface->cgLayer && !(width && height)) + _cairo_quartz_surface_create_with_cglayer (surface, content, + width, height); + if (content == CAIRO_CONTENT_COLOR_ALPHA) format = CAIRO_FORMAT_ARGB32; else if (content == CAIRO_CONTENT_COLOR) @@ -1599,17 +1673,10 @@ _cairo_quartz_surface_create_similar (void *abstract_surface, else return NULL; - // verify width and height of surface - if (!_cairo_quartz_verify_surface_size (width, height)) { - return _cairo_surface_create_in_error (_cairo_error - (CAIRO_STATUS_INVALID_SIZE)); - } - similar = cairo_quartz_surface_create (format, width, height); if (unlikely (similar->status)) return similar; - surface = (cairo_quartz_surface_t *) abstract_surface; similar_quartz = (cairo_quartz_surface_t *) similar; similar_quartz->virtual_extents = surface->virtual_extents; @@ -2207,7 +2274,7 @@ _cairo_quartz_surface_clipper_intersect_clip_path (cairo_surface_clipper_t *clip // XXXtodo implement show_page; need to figure out how to handle begin/end -static const struct _cairo_surface_backend cairo_quartz_surface_backend = { +static const cairo_surface_backend_t cairo_quartz_surface_backend = { CAIRO_SURFACE_TYPE_QUARTZ, _cairo_quartz_surface_finish, @@ -2238,6 +2305,10 @@ static const struct _cairo_surface_backend cairo_quartz_surface_backend = { _cairo_quartz_surface_fill, NULL, /* fill-stroke */ _cairo_quartz_surface_glyphs, + NULL, /* has_show_text_glyphs */ + NULL, /* show_text_glyphs */ + NULL, /* get_supported_mime_types */ + NULL, /* tag */ }; cairo_quartz_surface_t * @@ -2271,9 +2342,9 @@ _cairo_quartz_surface_create_internal (CGContextRef cgContext, surface->extents.width = width; surface->extents.height = height; surface->virtual_extents = surface->extents; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 10600 surface->imageData = NULL; - surface->imageSurfaceEquiv = NULL; - +#endif if (IS_EMPTY (surface)) { surface->cgContext = NULL; @@ -2359,7 +2430,7 @@ cairo_quartz_surface_create (cairo_format_t format, CGContextRef cgc; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; - void *imageData; + void *imageData = NULL; int stride; int bitsPerComponent; @@ -2402,16 +2473,16 @@ cairo_quartz_surface_create (cairo_format_t format, * so we don't have to anything special on allocation. */ stride = (stride + 15) & ~15; - +#if MAC_OS_X_VERSION_MIN_REQUIRED < 10600 imageData = _cairo_malloc_ab (height, stride); if (unlikely (!imageData)) { CGColorSpaceRelease (cgColorspace); return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } - /* zero the memory to match the image surface behaviour */ + /* zero the memory to match the image surface behavior */ memset (imageData, 0, height * stride); - +#endif /* For newer macOS versions let Core Graphics manage the buffer. */ cgc = CGBitmapContextCreate (imageData, width, height, @@ -2422,7 +2493,9 @@ cairo_quartz_surface_create (cairo_format_t format, CGColorSpaceRelease (cgColorspace); if (!cgc) { - free (imageData); + if (imageData) + free (imageData); + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); } @@ -2434,15 +2507,18 @@ cairo_quartz_surface_create (cairo_format_t format, width, height); if (surf->base.status) { CGContextRelease (cgc); - free (imageData); + + if (imageData) + free (imageData); + // create_internal will have set an error return &surf->base; } - surf->base.is_clear = TRUE; - +#if MAC_OS_X_VERSION_MIN_REQUIRED < 10600 surf->imageData = imageData; - surf->imageSurfaceEquiv = cairo_image_surface_create_for_data (imageData, format, width, height, stride); +#endif + surf->base.is_clear = TRUE; return &surf->base; } @@ -2489,99 +2565,100 @@ _cairo_surface_is_quartz (const cairo_surface_t *surface) return surface->backend == &cairo_quartz_surface_backend; } -/* Debug stuff */ - -#ifdef QUARTZ_DEBUG - -#include <Movies.h> - -void ExportCGImageToPNGFile (CGImageRef inImageRef, char* dest) +cairo_surface_t* +_cairo_quartz_snapshot_create (cairo_quartz_surface_t *surface) { - Handle dataRef = NULL; - OSType dataRefType; - CFStringRef inPath = CFStringCreateWithCString (NULL, dest, kCFStringEncodingASCII); - - GraphicsExportComponent grex = 0; - unsigned long sizeWritten; + cairo_quartz_snapshot_t *snapshot = NULL; - ComponentResult result; + if (!surface || !_cairo_surface_is_quartz (&surface->base) || IS_EMPTY (surface) || + ! _cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) + return NULL; - // create the data reference - result = QTNewDataReferenceFromFullPathCFString (inPath, kQTNativeDefaultPathStyle, - 0, &dataRef, &dataRefType); + snapshot = _cairo_malloc (sizeof (cairo_quartz_snapshot_t)); - if (NULL != dataRef && noErr == result) { - // get the PNG exporter - result = OpenADefaultComponent (GraphicsExporterComponentType, kQTFileTypePNG, - &grex); + if (unlikely (surface == NULL)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); - if (grex) { - // tell the exporter where to find its source image - result = GraphicsExportSetInputCGImage (grex, inImageRef); + memset (snapshot, 0, sizeof (cairo_quartz_snapshot_t)); + _cairo_surface_init (&snapshot->base, + &cairo_quartz_snapshot_backend, + NULL, CAIRO_CONTENT_COLOR_ALPHA, FALSE); + snapshot->image = CGBitmapContextCreateImage (surface->cgContext); - if (noErr == result) { - // tell the exporter where to save the exporter image - result = GraphicsExportSetOutputDataReference (grex, dataRef, - dataRefType); + return &snapshot->base; +} - if (noErr == result) { - // write the PNG file - result = GraphicsExportDoExport (grex, &sizeWritten); - } - } +cairo_status_t +_cairo_quartz_snapshot_finish (void *surface) +{ + cairo_quartz_snapshot_t *snapshot = (cairo_quartz_snapshot_t *)surface; + if (snapshot->image) + CGImageRelease (snapshot->image); + return CAIRO_STATUS_SUCCESS; +} - // remember to close the component - CloseComponent (grex); - } +CGImageRef +_cairo_quartz_surface_snapshot_get_image (cairo_quartz_surface_t *surface) +{ + cairo_surface_t *snapshot = + _cairo_surface_has_snapshot (&surface->base, &cairo_quartz_snapshot_backend); - // remember to dispose of the data reference handle - DisposeHandle (dataRef); + if (unlikely (!snapshot)) + { + snapshot = _cairo_quartz_snapshot_create (surface); + if (unlikely (!snapshot || cairo_surface_status (snapshot))) + return NULL; + _cairo_surface_attach_snapshot (&surface->base, snapshot, NULL); } + + return CGImageRetain (((cairo_quartz_snapshot_t*)snapshot)->image); } +/* Debug stuff */ + +#ifdef QUARTZ_DEBUG + void -quartz_image_to_png (CGImageRef imgref, char *dest) +quartz_image_to_png (CGImageRef image, const char *dest) { static int sctr = 0; - char sptr[] = "/Users/vladimir/Desktop/barXXXXX.png"; + const char* image_name = "quartz-image"; + char pathbuf[100]; - if (dest == NULL) { - fprintf (stderr, "** Writing %p to bar%d\n", imgref, sctr); - sprintf (sptr, "/Users/vladimir/Desktop/bar%d.png", sctr); - sctr++; - dest = sptr; - } + CFStringRef png_utti = CFSTR("public.png"); + CFStringRef path; + CFURLRef url; + CGImageDestinationRef image_dest; - ExportCGImageToPNGFile (imgref, dest); -} + memset (pathbuf, 0, sizeof (pathbuf)); + dest = dest ? dest : image_name; + snprintf (pathbuf, sizeof (pathbuf), "%s/Desktop/%s%d.png",getenv ("HOME"), dest, sctr++, ext); + path = CFStringCreateWithCString (NULL, pathbuf, kCFStringEncodingUTF8); + url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, FALSE); + image_dest = CGImageDestinationCreateWithURL (url, png_utti, 1, NULL); + + CGImageDestinationAddImage (image_dest, image, NULL); + CGImageDestinationFinalize (image_dest); + + CFRelease (url); + CFRelease (path); + } void -quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest) +quartz_surface_to_png (cairo_quartz_surface_t *nq, const char *dest) { static int sctr = 0; - char sptr[] = "/Users/vladimir/Desktop/fooXXXXX.png"; + CGImageRef image; if (nq->base.type != CAIRO_SURFACE_TYPE_QUARTZ) { fprintf (stderr, "** quartz_surface_to_png: surface %p isn't quartz!\n", nq); return; } - if (dest == NULL) { - fprintf (stderr, "** Writing %p to foo%d\n", nq, sctr); - sprintf (sptr, "/Users/vladimir/Desktop/foo%d.png", sctr); - sctr++; - dest = sptr; - } - - CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext); - if (imgref == NULL) { - fprintf (stderr, "quartz surface at %p is not a bitmap context!\n", nq); - return; - } - - ExportCGImageToPNGFile (imgref, dest); + image = CGBitmapContextCreateImage (nq->cgContext); + quartz_image_to_png (image, dest); - CGImageRelease (imgref); + CGImageRelease (image); } #endif /* QUARTZ_DEBUG */ diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h index 2ec2ce67b..59404243b 100644 --- a/src/cairo-types-private.h +++ b/src/cairo-types-private.h @@ -254,7 +254,8 @@ typedef enum _cairo_internal_surface_type { CAIRO_INTERNAL_SURFACE_TYPE_TEST_PAGINATED, CAIRO_INTERNAL_SURFACE_TYPE_TEST_WRAPPING, CAIRO_INTERNAL_SURFACE_TYPE_NULL, - CAIRO_INTERNAL_SURFACE_TYPE_TYPE3_GLYPH + CAIRO_INTERNAL_SURFACE_TYPE_TYPE3_GLYPH, + CAIRO_INTERNAL_SURFACE_TYPE_QUARTZ_SNAPSHOT } cairo_internal_surface_type_t; typedef enum _cairo_internal_device_type { diff --git a/test/reference/mask.quartz.argb32.ref.png b/test/reference/mask.quartz.argb32.ref.png Binary files differindex cf9ddc9ac..ab800daf7 100644 --- a/test/reference/mask.quartz.argb32.ref.png +++ b/test/reference/mask.quartz.argb32.ref.png diff --git a/test/reference/trap-clip.quartz.argb32.ref.png b/test/reference/trap-clip.quartz.argb32.ref.png Binary files differindex 2255805ff..66a1e8afe 100644 --- a/test/reference/trap-clip.quartz.argb32.ref.png +++ b/test/reference/trap-clip.quartz.argb32.ref.png |