From 64786613ee3bf94193e9f515ca42208b5bb3d17d Mon Sep 17 00:00:00 2001 From: John Ralls Date: Thu, 10 Feb 2022 12:54:46 -0800 Subject: [PATCH 1/7] [quartz] Snapshot CGBitmapContext-mapped surfaces to cache CGImages. Motivation: Avoid need to recreate CGImages for unchanged surfaces, an expensive operation, while ensuring that the CGImages are properly freed and new ones created when the surface does change. Thanks to Uli Schlacter for suggestion and coding guidance. --- src/cairo-quartz-surface.c | 80 ++++++++++++++++++++++++++++++++++++-- src/cairo-types-private.h | 3 +- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index a92c8fed3..973c5d1a5 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -133,6 +133,21 @@ static void quartz_surface_to_png (cairo_quartz_surface_t *nq, char *dest); static void quartz_image_to_png (CGImageRef, 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, @@ -202,6 +217,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 +827,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) { @@ -2207,7 +2226,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 +2257,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 * @@ -2489,6 +2512,55 @@ _cairo_surface_is_quartz (const cairo_surface_t *surface) return surface->backend == &cairo_quartz_surface_backend; } +cairo_surface_t* +_cairo_quartz_snapshot_create (cairo_quartz_surface_t *surface) +{ + cairo_quartz_snapshot_t *snapshot = NULL; + + if (!surface || !_cairo_surface_is_quartz (&surface->base) || IS_EMPTY (surface) || + ! _cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) + return NULL; + + snapshot = _cairo_malloc (sizeof (cairo_quartz_snapshot_t)); + + if (unlikely (surface == NULL)) + return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); + + 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); + + return &snapshot->base; +} + +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; +} + +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); + + 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 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 { From 76e6a0ddf7dfee6d1bc826fd46737d75054b1a0f Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 13 Feb 2022 13:50:57 -0800 Subject: [PATCH 2/7] [quartz] Remove cached image_surface on quartz surfaces. Caching doesn't really do anything and removing it provides a 50% speedup and gets pdf-operators-text to pass on argb32. --- .gitlab-ci/ignore-quartz-argb32.txt | 1 - src/cairo-quartz-private.h | 3 +- src/cairo-quartz-surface.c | 66 +++++++++++++++-------------- 3 files changed, 36 insertions(+), 34 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..6f5ea4442 100644 --- a/src/cairo-quartz-private.h +++ b/src/cairo-quartz-private.h @@ -67,8 +67,9 @@ 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; diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index 973c5d1a5..d34a5f411 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -1466,19 +1466,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); @@ -1502,23 +1500,26 @@ _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 @@ -1527,9 +1528,6 @@ _cairo_quartz_surface_unmap_image (void *abstract_surface, { 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); @@ -1559,13 +1557,12 @@ _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; } - - free (surface->imageData); - surface->imageData = NULL; +#endif return CAIRO_STATUS_SUCCESS; } @@ -2294,9 +2291,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; @@ -2382,7 +2379,7 @@ cairo_quartz_surface_create (cairo_format_t format, CGContextRef cgc; CGColorSpaceRef cgColorspace; CGBitmapInfo bitinfo; - void *imageData; + void *imageData = NULL; int stride; int bitsPerComponent; @@ -2425,16 +2422,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, @@ -2445,7 +2442,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)); } @@ -2457,15 +2456,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; } From bacbe9bb2da5afec7cb64bc5a89dd898e1826e8a Mon Sep 17 00:00:00 2001 From: John Ralls Date: Mon, 14 Feb 2022 17:23:12 -0800 Subject: [PATCH 3/7] [quartz] Create similar surfaces using a CGLayer for faster drawing. --- src/cairo-quartz-private.h | 4 +- src/cairo-quartz-surface.c | 139 +++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/cairo-quartz-private.h b/src/cairo-quartz-private.h index 6f5ea4442..968d64643 100644 --- a/src/cairo-quartz-private.h +++ b/src/cairo-quartz-private.h @@ -55,7 +55,8 @@ typedef enum { DO_DIRECT, DO_SHADING, DO_IMAGE, - DO_TILED_IMAGE + DO_TILED_IMAGE, + DO_LAYER } cairo_quartz_action_t; /* define CTFontRef for pre-10.5 SDKs */ @@ -74,6 +75,7 @@ typedef struct cairo_quartz_surface { 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 d34a5f411..2f954ac9e 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -1170,6 +1170,7 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state, state->shading = NULL; state->cgDrawContext = NULL; state->cgMaskContext = NULL; + state->layer = NULL; status = _cairo_surface_clipper_set_clip (&surface->clipper, clip); if (unlikely (status)) @@ -1277,6 +1278,18 @@ _cairo_quartz_setup_state (cairo_quartz_drawing_state_t *state, cairo_fixed_t fw, fh; cairo_bool_t is_bounded; + 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; + } + } + _cairo_surface_get_extents (composite->surface, &extents); status = _cairo_surface_to_cgimage (pat_surf, &extents, format, &m, clip, &img); @@ -1400,9 +1413,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); } @@ -1416,6 +1430,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) @@ -1440,25 +1477,19 @@ _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); - - CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 0); - CGContextEOFillPath (state->cgDrawContext); - } - } else { - CGContextDrawTiledImagePtr (state->cgDrawContext, state->rect, state->image); + _cairo_quartz_draw_cgcontext (state, op); + return; } + if (state->action == DO_TILED_IMAGE) { + CGContextDrawTiledImagePtr (state->cgDrawContext, state->rect, state->image); + return; + } + 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 * @@ -1526,8 +1557,6 @@ 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; - cairo_surface_finish (&image->base); cairo_surface_destroy (&image->base); @@ -1564,6 +1593,12 @@ _cairo_quartz_surface_finish (void *abstract_surface) } #endif + if (surface->cgLayer) + { + CGLayerRelease (surface->cgLayer); + surface->cgLayer = NULL; + } + return CAIRO_STATUS_SUCCESS; } @@ -1596,6 +1631,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, @@ -1606,6 +1681,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) @@ -1615,17 +1701,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; From ae320c4d75d981220de87d9b44ffe30b2bcab69e Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sat, 19 Feb 2022 18:34:01 -0800 Subject: [PATCH 4/7] [quartz] Use CoreGraphics instead of Qt to write debug png file. --- src/cairo-quartz-surface.c | 102 ++++++++++--------------------------- 1 file changed, 27 insertions(+), 75 deletions(-) diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index 2f954ac9e..6ae6689e0 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -129,8 +129,8 @@ 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 @@ -2646,95 +2646,47 @@ _cairo_quartz_surface_snapshot_get_image (cairo_quartz_surface_t *surface) #ifdef QUARTZ_DEBUG -#include - -void ExportCGImageToPNGFile (CGImageRef inImageRef, char* dest) -{ - Handle dataRef = NULL; - OSType dataRefType; - CFStringRef inPath = CFStringCreateWithCString (NULL, dest, kCFStringEncodingASCII); - - GraphicsExportComponent grex = 0; - unsigned long sizeWritten; - - ComponentResult result; - - // create the data reference - result = QTNewDataReferenceFromFullPathCFString (inPath, kQTNativeDefaultPathStyle, - 0, &dataRef, &dataRefType); - - if (NULL != dataRef && noErr == result) { - // get the PNG exporter - result = OpenADefaultComponent (GraphicsExporterComponentType, kQTFileTypePNG, - &grex); - - if (grex) { - // tell the exporter where to find its source image - result = GraphicsExportSetInputCGImage (grex, inImageRef); - - if (noErr == result) { - // tell the exporter where to save the exporter image - result = GraphicsExportSetOutputDataReference (grex, dataRef, - dataRefType); - - if (noErr == result) { - // write the PNG file - result = GraphicsExportDoExport (grex, &sizeWritten); - } - } - - // remember to close the component - CloseComponent (grex); - } - - // remember to dispose of the data reference handle - DisposeHandle (dataRef); - } -} - void -quartz_image_to_png (CGImageRef imgref, char *dest) +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; - } + image = CGBitmapContextCreateImage (nq->cgContext); + quartz_image_to_png (image, dest); - CGImageRef imgref = CGBitmapContextCreateImage (nq->cgContext); - if (imgref == NULL) { - fprintf (stderr, "quartz surface at %p is not a bitmap context!\n", nq); - return; - } - - ExportCGImageToPNGFile (imgref, dest); - - CGImageRelease (imgref); + CGImageRelease (image); } #endif /* QUARTZ_DEBUG */ From b6e0f36ee5de521fffcadffcece1ef27bb4dfd5b Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sat, 19 Feb 2022 18:38:29 -0800 Subject: [PATCH 5/7] [quartz]Cleanup _cairo_quartz_cairo_repeating_surface_pattern_to_quartz --- src/cairo-quartz-surface.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index 6ae6689e0..da3f39c0d 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -964,8 +964,8 @@ _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t 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 *) apattern; + cairo_surface_t *pat_surf = spattern->surface; cairo_rectangle_int_t extents; cairo_format_t format = _cairo_format_from_content (dest->base.content); @@ -978,42 +978,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); - 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; From a502280fcd4ca28c1c5d350674edde645cb784c8 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sat, 19 Feb 2022 18:37:16 -0800 Subject: [PATCH 6/7] [quartz] extract function _cairo_quartz_setup_pattern_source To simplify _cairo_quartz_setup_state. --- src/cairo-quartz-surface.c | 283 +++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 137 deletions(-) diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index da3f39c0d..3f1d91dfc 100644 --- a/src/cairo-quartz-surface.c +++ b/src/cairo-quartz-surface.c @@ -1077,6 +1077,150 @@ 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; + CGRect srcRect; + CGImageRef img; + + cairo_quartz_float_t patternAlpha = 1.0f; + CGColorSpaceRef patternSpace; + CGPatternRef pattern = 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, &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; + } + + if (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT) { + int fh, fw; + CGAffineTransform xform; + + 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 */ + 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; +} + + /* Quartz does not support repeating gradients. We handle repeating gradients by manually extending the gradient and repeating color stops. We need to @@ -1149,7 +1293,6 @@ _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; @@ -1251,143 +1394,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; - 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; - } - } - - _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; } From 2e0075e265155ff144ced23ff87c4bed4cdfbc27 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Thu, 24 Feb 2022 14:58:57 -0800 Subject: [PATCH 7/7] [quartz] Don't use CGContextDrawTiledImage for tiled patterns. CGContextDrawTiledImage turned up as a significant time-user while profiling a benchmark created to evaluate https://gitlab.gnome.org/GNOME/gtk/-/issues/3714. Without this commit the benchmark is able to perform a mean frame rate of 2.19 frames per second with a standard deviation of 0.09; with the commit the mean frame rate is 2.37 fps, s.d. 0.30, both over 15 10-second samples. Student's t-test reports a 9.8% likelyhood that the two represent the same distribution. --- src/cairo-quartz-private.h | 1 - src/cairo-quartz-surface.c | 113 +++++++----------- test/reference/mask.quartz.argb32.ref.png | Bin 10599 -> 10578 bytes .../reference/trap-clip.quartz.argb32.ref.png | Bin 6108 -> 6089 bytes 4 files changed, 45 insertions(+), 69 deletions(-) diff --git a/src/cairo-quartz-private.h b/src/cairo-quartz-private.h index 968d64643..609a9052c 100644 --- a/src/cairo-quartz-private.h +++ b/src/cairo-quartz-private.h @@ -55,7 +55,6 @@ typedef enum { DO_DIRECT, DO_SHADING, DO_IMAGE, - DO_TILED_IMAGE, DO_LAYER } cairo_quartz_action_t; diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c index 3f1d91dfc..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; @@ -160,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"); @@ -959,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_pattern_t *) apattern; + 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; @@ -982,7 +979,7 @@ _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (cairo_quartz_surface_t 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); if (pat_surf->type != CAIRO_SURFACE_TYPE_RECORDING) assert (is_bounded); @@ -1007,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; @@ -1026,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 @@ -1063,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 */ @@ -1088,13 +1094,12 @@ _cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, 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; - CGRect srcRect; + cairo_rectangle_int_t extents, pattern_extents; CGImageRef img; cairo_quartz_float_t patternAlpha = 1.0f; CGColorSpaceRef patternSpace; - CGPatternRef pattern = NULL; + CGPatternRef cgpat = NULL; cairo_int_status_t status; _cairo_surface_get_extents (&surface->base, &extents); @@ -1119,7 +1124,7 @@ _cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, state->image = img; - if (state->filter == kCGInterpolationNone && _cairo_matrix_is_translation (&m)) { + if (state->filter == kCGInterpolationNone && _cairo_matrix_is_translation (&m)) { m.x0 = -ceil (m.x0 - 0.5); m.y0 = -ceil (m.y0 - 0.5); } else { @@ -1129,14 +1134,15 @@ _cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, _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, &extents); + 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); } - 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))) @@ -1152,60 +1158,34 @@ _cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, CGContextSetRGBFillColor (state->cgDrawContext, 0, 0, 0, 1); - state->rect = srcRect; + state->rect = CGRectMake (0, 0, pattern_extents.width, pattern_extents.height); state->action = DO_IMAGE; return CAIRO_STATUS_SUCCESS; } - if (CGContextDrawTiledImagePtr && source->extend == CAIRO_EXTEND_REPEAT) { - int fh, fw; - CGAffineTransform xform; - - 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); - + 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); - - 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; - } + xform = CGAffineTransformInvert (xform); + srcRect = CGRectApplyAffineTransform (srcRect, xform); + state->rect = srcRect; } - /* Fall through to generic SURFACE case */ - status = _cairo_quartz_cairo_repeating_surface_pattern_to_quartz (surface, source, clip, &pattern); + + 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, pattern, &patternAlpha); + CGContextSetFillPattern (state->cgDrawContext, cgpat, &patternAlpha); CGContextSetStrokeColorSpace (state->cgDrawContext, patternSpace); - CGContextSetStrokePattern (state->cgDrawContext, pattern, &patternAlpha); + CGContextSetStrokePattern (state->cgDrawContext, cgpat, &patternAlpha); CGColorSpaceRelease (patternSpace); /* Quartz likes to munge the pattern phase (as yet unexplained @@ -1214,7 +1194,7 @@ _cairo_quartz_setup_pattern_source (cairo_quartz_drawing_state_t *state, */ CGContextSetPatternPhase (state->cgDrawContext, CGSizeMake (0, 0)); - CGPatternRelease (pattern); + CGPatternRelease (cgpat); state->action = DO_DIRECT; return CAIRO_STATUS_SUCCESS; @@ -1475,10 +1455,7 @@ _cairo_quartz_draw_source (cairo_quartz_drawing_state_t *state, _cairo_quartz_draw_cgcontext (state, op); return; } - if (state->action == DO_TILED_IMAGE) { - CGContextDrawTiledImagePtr (state->cgDrawContext, state->rect, state->image); - return; - } + if (state->action == DO_LAYER) { CGContextDrawLayerInRect (state->cgDrawContext, state->rect, state->layer); _cairo_quartz_draw_cgcontext (state, op); diff --git a/test/reference/mask.quartz.argb32.ref.png b/test/reference/mask.quartz.argb32.ref.png index cf9ddc9acc691ca8489a806db8235de706b13f8b..ab800daf7d1dbda154471d52400e87e71ad6b2d7 100644 GIT binary patch literal 10578 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 literal 10599 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 diff --git a/test/reference/trap-clip.quartz.argb32.ref.png b/test/reference/trap-clip.quartz.argb32.ref.png index 2255805ff5a9b993dbad00682e9dbadb42a4e583..66a1e8afeed97e8b34b049dedb8d3612684c8027 100644 GIT binary patch delta 6062 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 B;kEz(