From 50d383bca293544a71fd11ce82edc74643d498a1 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Sun, 2 Jun 2024 11:40:59 +0000 Subject: [PATCH] Bug 1900028 - Handle CAIRO_FORMAT_A8 in _cairo_surface_to_cgimage for masking operations. r=gfx-reviewers,lsalzman Differential Revision: https://phabricator.services.mozilla.com/D212354 --- gfx/cairo/26-quartz-surface-mask.patch | 124 +++++++++++++++++++++ gfx/cairo/cairo/src/cairo-quartz-surface.c | 96 +++++++++++++++- 2 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 gfx/cairo/26-quartz-surface-mask.patch diff --git a/gfx/cairo/26-quartz-surface-mask.patch b/gfx/cairo/26-quartz-surface-mask.patch new file mode 100644 index 000000000000..dd5c71c07a62 --- /dev/null +++ b/gfx/cairo/26-quartz-surface-mask.patch @@ -0,0 +1,124 @@ +# HG changeset patch +# User Jonathan Kew +# Date 1717237382 -3600 +# Sat Jun 01 11:23:02 2024 +0100 +# Node ID d5f7b9fd904e04406c56899c5cac9248b122ea35 +# Parent c8d3e447c892474e061c9ffd22ec1823f06ecffa +Bug 1900028 - Handle CAIRO_FORMAT_A8 in _cairo_surface_to_cgimage for masking operations. + +Differential Revision: https://phabricator.services.mozilla.com/D212354 + +diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c +--- a/gfx/cairo/cairo/src/cairo-quartz-surface.c ++++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c +@@ -684,6 +684,55 @@ CairoQuartzCreateGradientFunction (const + &gradient_callbacks); + } + ++static CGImageRef ++CairoQuartzCreateCGImageMask (cairo_format_t format, ++ unsigned int width, ++ unsigned int height, ++ unsigned int stride, ++ void *data, ++ cairo_bool_t interpolate, ++ CGDataProviderReleaseDataCallback releaseCallback, ++ void *releaseInfo) ++{ ++ CGImageRef image = NULL; ++ CGDataProviderRef dataProvider = NULL; ++ int bitsPerComponent = 8, bitsPerPixel = 8; ++ ++ if (format != CAIRO_FORMAT_A8) ++ return NULL; ++ ++ dataProvider = CGDataProviderCreateWithData (releaseInfo, ++ data, ++ height * stride, ++ releaseCallback); ++ ++ if (unlikely (!dataProvider)) { ++ // manually release ++ if (releaseCallback) ++ releaseCallback (releaseInfo, data, height * stride); ++ goto FINISH; ++ } ++ ++ cairo_quartz_float_t decode[] = {1.0, 0.0}; ++ image = CGImageMaskCreate (width, height, ++ bitsPerComponent, ++ bitsPerPixel, ++ stride, ++ dataProvider, ++ decode, ++ interpolate); ++ ++FINISH: ++ CGDataProviderRelease (dataProvider); ++ return image; ++} ++ ++static void ++DataProviderReleaseCallback (void *info, const void *data, size_t size) ++{ ++ free (info); ++} ++ + static cairo_status_t + _cairo_surface_to_cgimage (cairo_surface_t *source, + cairo_rectangle_int_t *extents, +@@ -742,13 +791,48 @@ static cairo_status_t + &image_extra); + if (unlikely (status)) + return status; +- image_surface = +- (cairo_quartz_image_surface_t*)cairo_quartz_image_surface_create (&surface->base); +- status = image_surface->base.status; +- if (status) ++ ++ if (surface->format == CAIRO_FORMAT_A8) { ++ /* cairo_quartz_image_surface_create doesn't handle CAIRO_FORMAT_A8, ++ * so we create a CGImage manually here for masking operations. ++ */ ++ void* image_data = _cairo_malloc_ab (surface->height, surface->stride); ++ if (unlikely (!image_data)) ++ { ++ _cairo_surface_release_source_image (source, surface, image_extra); ++ return _cairo_error (CAIRO_STATUS_NO_MEMORY); ++ } ++ ++ /* The last row of data may have less than stride bytes so make sure we ++ * only copy the minimum amount required from that row. ++ */ ++ memcpy (image_data, surface->data, ++ (surface->height - 1) * surface->stride + ++ cairo_format_stride_for_width (surface->format, ++ surface->width)); ++ *image_out = CairoQuartzCreateCGImageMask (surface->format, ++ surface->width, ++ surface->height, ++ surface->stride, ++ image_data, ++ TRUE, ++ DataProviderReleaseCallback, ++ image_data); ++ /* TODO: differentiate memory error and unsupported surface type */ ++ if (unlikely (*image_out == NULL)) ++ status = CAIRO_INT_STATUS_UNSUPPORTED; ++ + _cairo_surface_release_source_image (source, surface, image_extra); +- else +- acquired = TRUE; ++ return status; ++ } else { ++ image_surface = ++ (cairo_quartz_image_surface_t*)cairo_quartz_image_surface_create (&surface->base); ++ status = image_surface->base.status; ++ if (status) ++ _cairo_surface_release_source_image (source, surface, image_extra); ++ else ++ acquired = TRUE; ++ } + } + + *image_out = NULL; diff --git a/gfx/cairo/cairo/src/cairo-quartz-surface.c b/gfx/cairo/cairo/src/cairo-quartz-surface.c index 505f3ee1f5a4..c88c2977175c 100644 --- a/gfx/cairo/cairo/src/cairo-quartz-surface.c +++ b/gfx/cairo/cairo/src/cairo-quartz-surface.c @@ -684,6 +684,55 @@ CairoQuartzCreateGradientFunction (const cairo_gradient_pattern_t *gradient, &gradient_callbacks); } +static CGImageRef +CairoQuartzCreateCGImageMask (cairo_format_t format, + unsigned int width, + unsigned int height, + unsigned int stride, + void *data, + cairo_bool_t interpolate, + CGDataProviderReleaseDataCallback releaseCallback, + void *releaseInfo) +{ + CGImageRef image = NULL; + CGDataProviderRef dataProvider = NULL; + int bitsPerComponent = 8, bitsPerPixel = 8; + + if (format != CAIRO_FORMAT_A8) + return NULL; + + dataProvider = CGDataProviderCreateWithData (releaseInfo, + data, + height * stride, + releaseCallback); + + if (unlikely (!dataProvider)) { + // manually release + if (releaseCallback) + releaseCallback (releaseInfo, data, height * stride); + goto FINISH; + } + + cairo_quartz_float_t decode[] = {1.0, 0.0}; + image = CGImageMaskCreate (width, height, + bitsPerComponent, + bitsPerPixel, + stride, + dataProvider, + decode, + interpolate); + +FINISH: + CGDataProviderRelease (dataProvider); + return image; +} + +static void +DataProviderReleaseCallback (void *info, const void *data, size_t size) +{ + free (info); +} + static cairo_status_t _cairo_surface_to_cgimage (cairo_surface_t *source, cairo_rectangle_int_t *extents, @@ -742,13 +791,48 @@ _cairo_surface_to_cgimage (cairo_surface_t *source, &image_extra); if (unlikely (status)) return status; - image_surface = - (cairo_quartz_image_surface_t*)cairo_quartz_image_surface_create (&surface->base); - status = image_surface->base.status; - if (status) + + if (surface->format == CAIRO_FORMAT_A8) { + /* cairo_quartz_image_surface_create doesn't handle CAIRO_FORMAT_A8, + * so we create a CGImage manually here for masking operations. + */ + void* image_data = _cairo_malloc_ab (surface->height, surface->stride); + if (unlikely (!image_data)) + { + _cairo_surface_release_source_image (source, surface, image_extra); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + /* The last row of data may have less than stride bytes so make sure we + * only copy the minimum amount required from that row. + */ + memcpy (image_data, surface->data, + (surface->height - 1) * surface->stride + + cairo_format_stride_for_width (surface->format, + surface->width)); + *image_out = CairoQuartzCreateCGImageMask (surface->format, + surface->width, + surface->height, + surface->stride, + image_data, + TRUE, + DataProviderReleaseCallback, + image_data); + /* TODO: differentiate memory error and unsupported surface type */ + if (unlikely (*image_out == NULL)) + status = CAIRO_INT_STATUS_UNSUPPORTED; + _cairo_surface_release_source_image (source, surface, image_extra); - else - acquired = TRUE; + return status; + } else { + image_surface = + (cairo_quartz_image_surface_t*)cairo_quartz_image_surface_create (&surface->base); + status = image_surface->base.status; + if (status) + _cairo_surface_release_source_image (source, surface, image_extra); + else + acquired = TRUE; + } } *image_out = NULL;