[quartz] Cleanup and make better use of cairo_quartz_image_surface_t.

Use a CGBitmapContext mapping the underlying image surface's data instead
of maintaining a CGImage. Generalize the quartz surface snapshot mechanism
to work with both cairo_quartz_surface_t and cairo_quartz_image_surface_t
and to use the latter to get a CGContext around non-quartz surfaces.
Use this snapshot machanism to get a CGImageRef when needed from a
cairo_quartz_image_surface_t.
This commit is contained in:
John Ralls 2022-04-26 12:20:04 -07:00
parent 6b5519626c
commit 198d1792cf
5 changed files with 135 additions and 276 deletions

View file

@ -12,7 +12,6 @@ fallback
ft-show-glyphs-positioning
ft-text-vertical-layout-type1
ft-text-vertical-layout-type3
negative-stride-image
operator-www
radial-gradient
radial-gradient-mask

View file

@ -31,7 +31,6 @@ image-surface-source
linear-gradient-one-stop
mask-ctm
mask-surface-ctm
negative-stride-image
nil-surface
operator-www
overlapping-boxes

View file

@ -49,12 +49,6 @@
#define SURFACE_ERROR_INVALID_SIZE (_cairo_surface_create_in_error(_cairo_error(CAIRO_STATUS_INVALID_SIZE)))
#define SURFACE_ERROR_INVALID_FORMAT (_cairo_surface_create_in_error(_cairo_error(CAIRO_STATUS_INVALID_FORMAT)))
static void
DataProviderReleaseCallback (void *image_info, const void *data, size_t size)
{
free (image_info);
}
static cairo_surface_t *
_cairo_quartz_image_surface_create_similar (void *asurface,
cairo_content_t content,
@ -87,8 +81,9 @@ _cairo_quartz_image_surface_finish (void *asurface)
{
cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t *) asurface;
CGImageRelease (surface->image);
cairo_surface_destroy ( (cairo_surface_t*) surface->imageSurface);
CGContextRelease (surface->cgContext);
if (surface->imageSurface)
cairo_surface_destroy ( (cairo_surface_t*) surface->imageSurface);
return CAIRO_STATUS_SUCCESS;
}
@ -134,47 +129,6 @@ _cairo_quartz_image_surface_get_extents (void *asurface,
return TRUE;
}
/* we assume some drawing happened to the image buffer; make sure it's
* represented in the CGImage on flush()
*/
static cairo_status_t
_cairo_quartz_image_surface_flush (void *asurface,
unsigned flags)
{
cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t *) asurface;
CGImageRef oldImage = surface->image;
CGImageRef newImage = NULL;
void *image_data;
if (flags)
return CAIRO_STATUS_SUCCESS;
/* XXX only flush if the image has been modified. */
image_data = _cairo_malloc_ab ( surface->imageSurface->height,
surface->imageSurface->stride);
if (unlikely (!image_data))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
memcpy (image_data, surface->imageSurface->data,
surface->imageSurface->height * surface->imageSurface->stride);
newImage = CairoQuartzCreateCGImage (surface->imageSurface->format,
surface->imageSurface->width,
surface->imageSurface->height,
surface->imageSurface->stride,
image_data,
TRUE,
NULL,
DataProviderReleaseCallback,
image_data);
surface->image = newImage;
CGImageRelease (oldImage);
return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_quartz_image_surface_paint (void *abstract_surface,
cairo_operator_t op,
@ -275,7 +229,7 @@ static const cairo_surface_backend_t cairo_quartz_image_surface_backend = {
_cairo_quartz_image_surface_get_extents,
NULL, /* get_font_options */
_cairo_quartz_image_surface_flush,
NULL, /*surface_flush */
NULL, /* mark_dirty_rectangle */
_cairo_quartz_image_surface_paint,
@ -290,12 +244,9 @@ static const cairo_surface_backend_t cairo_quartz_image_surface_backend = {
* cairo_quartz_image_surface_create:
* @image_surface: a cairo image surface to wrap with a quartz image surface
*
* Creates a Quartz surface backed by a CGImageRef that references the
* Creates a Quartz surface backed by a CGBitmapContext that references the
* given image surface. The resulting surface can be rendered quickly
* when used as a source when rendering to a #cairo_quartz_surface. If
* the data in the image surface is ever updated, cairo_surface_flush()
* must be called on the #cairo_quartz_image_surface to ensure that the
* CGImageRef refers to the updated data.
* when used as a source when rendering to a #cairo_quartz_surface.
*
* Return value: the newly created surface.
*
@ -305,13 +256,11 @@ cairo_surface_t *
cairo_quartz_image_surface_create (cairo_surface_t *surface)
{
cairo_quartz_image_surface_t *qisurf;
CGImageRef image;
cairo_image_surface_t *image_surface;
int width, height, stride;
cairo_format_t format;
void *image_data;
CGBitmapInfo bitinfo = kCGBitmapByteOrder32Host;
CGColorSpaceRef colorspace;
if (surface->status)
return surface;
@ -338,41 +287,22 @@ cairo_quartz_image_surface_create (cairo_surface_t *surface)
if (qisurf == NULL)
return SURFACE_ERROR_NO_MEMORY;
memset (qisurf, 0, sizeof(cairo_quartz_image_surface_t));
image_data = _cairo_malloc_ab (height, stride);
if (unlikely (!image_data)) {
free(qisurf);
return SURFACE_ERROR_NO_MEMORY;
}
memcpy (image_data, image_surface->data, height * stride);
image = CairoQuartzCreateCGImage (format,
width, height,
stride,
image_data,
TRUE,
NULL,
DataProviderReleaseCallback,
image_data);
if (!image) {
free (qisurf);
return SURFACE_ERROR_NO_MEMORY;
}
_cairo_surface_init (&qisurf->base,
&cairo_quartz_image_surface_backend,
NULL, /* device */
_cairo_content_from_format (format),
FALSE); /* is_vector */
colorspace = CGColorSpaceCreateDeviceRGB ();
bitinfo |= format == CAIRO_FORMAT_ARGB32 ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
qisurf->width = width;
qisurf->height = height;
qisurf->image = image;
qisurf->cgContext = CGBitmapContextCreate (image_surface->data, width, height, 8, image_surface->stride,
colorspace, bitinfo);
qisurf->imageSurface = (cairo_image_surface_t*) cairo_surface_reference(surface);
CGColorSpaceRelease (colorspace);
return &qisurf->base;
}
@ -401,3 +331,45 @@ cairo_quartz_image_surface_get_image (cairo_surface_t *surface)
return (cairo_surface_t*) qsurface->imageSurface;
}
/*
* _cairo_quartz_image_surface_get_cg_context:
* @surface: the Cairo Quartz surface
*
* Returns the CGContextRef that the given Quartz surface is backed
* by.
*
* A call to cairo_surface_flush() is required before using the
* CGContextRef to ensure that all pending drawing operations are
* finished and to restore any temporary modification cairo has made
* to its state. A call to cairo_surface_mark_dirty() is required
* after the state or the content of the CGContextRef has been
* modified.
*
* Return value: the CGContextRef for the given surface.
*
**/
CGContextRef
_cairo_quartz_image_surface_get_cg_context (cairo_surface_t *surface)
{
if (surface && _cairo_surface_is_quartz_image (surface)) {
cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t *) surface;
return quartz->cgContext;
} else
return NULL;
}
/**
* _cairo_surface_is_quartz_image:
* @surface: a #cairo_surface_t
*
* Checks if a surface is a #cairo_quartz_surface_t
*
* Return value: True if the surface is an quartz surface
**/
cairo_bool_t
_cairo_surface_is_quartz_image (const cairo_surface_t *surface)
{
return surface->backend == &cairo_quartz_image_surface_backend;
}

View file

@ -81,8 +81,7 @@ typedef struct cairo_quartz_image_surface {
cairo_surface_t base;
int width, height;
CGImageRef image;
CGContextRef cgContext;
cairo_image_surface_t *imageSurface;
} cairo_quartz_image_surface_t;
@ -92,16 +91,11 @@ _cairo_quartz_verify_surface_size(int width, int height);
cairo_private cairo_bool_t
_cairo_surface_is_quartz (const cairo_surface_t *surface);
cairo_private CGImageRef
CairoQuartzCreateCGImage (cairo_format_t format,
unsigned int width,
unsigned int height,
unsigned int stride,
void *data,
cairo_bool_t interpolate,
CGColorSpaceRef colorSpaceOverride,
CGDataProviderReleaseDataCallback releaseCallback,
void *releaseInfo);
cairo_private cairo_bool_t
_cairo_surface_is_quartz_image (const cairo_surface_t *surface);
cairo_private CGContextRef
_cairo_quartz_image_surface_get_cg_context (cairo_surface_t *surface);
cairo_private CGFontRef
_cairo_quartz_scaled_font_get_cg_font_ref (cairo_scaled_font_t *sfont);

View file

@ -37,6 +37,7 @@
#include "cairoint.h"
#include "cairo-quartz-private.h"
#include "cairo-quartz-image.h"
#include "cairo-composite-rectangles-private.h"
#include "cairo-compositor-private.h"
@ -64,13 +65,24 @@
#define ND(_x) do {} while(0)
#endif
#define IS_EMPTY(s) ((s)->extents.width == 0 || (s)->extents.height == 0)
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1080
#define FONT_ORIENTATION_HORIZONTAL kCTFontHorizontalOrientation
#else
#define FONT_ORIENTATION_HORIZONTAL kCTFontOrientationHorizontal
#endif
static inline cairo_bool_t
IS_EMPTY(void *abstract_surface) {
if (_cairo_surface_is_quartz ((cairo_surface_t*)abstract_surface)) {
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t*)abstract_surface;
return surface->extents.width == 0 || surface->extents.height == 0;
}
if (_cairo_surface_is_quartz_image ((cairo_surface_t*)abstract_surface)) {
cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t*)abstract_surface;
return surface->width == 0 || surface->height == 0;
}
return TRUE;
}
/**
* SECTION:cairo-quartz
@ -119,9 +131,9 @@ typedef struct
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 cairo_surface_t* _cairo_quartz_snapshot_create (cairo_surface_t *surface);
static cairo_status_t _cairo_quartz_snapshot_finish (void *surface);
static CGImageRef _cairo_quartz_surface_snapshot_get_image (cairo_surface_t *surface);
static const cairo_surface_backend_t cairo_quartz_snapshot_backend = {
CAIRO_INTERNAL_SURFACE_TYPE_QUARTZ_SNAPSHOT,
@ -134,104 +146,6 @@ _cairo_quartz_surface_create_internal (CGContextRef cgContext,
unsigned int width,
unsigned int height);
CGImageRef
CairoQuartzCreateCGImage (cairo_format_t format,
unsigned int width,
unsigned int height,
unsigned int stride,
void *data,
cairo_bool_t interpolate,
CGColorSpaceRef colorSpaceOverride,
CGDataProviderReleaseDataCallback releaseCallback,
void *releaseInfo)
{
CGImageRef image = NULL;
CGDataProviderRef dataProvider = NULL;
CGColorSpaceRef colorSpace = colorSpaceOverride;
CGBitmapInfo bitinfo = kCGBitmapByteOrder32Host;
int bitsPerComponent, bitsPerPixel;
switch (format) {
case CAIRO_FORMAT_ARGB32:
if (colorSpace == NULL)
colorSpace = CGColorSpaceCreateDeviceRGB ();
bitinfo |= kCGImageAlphaPremultipliedFirst;
bitsPerComponent = 8;
bitsPerPixel = 32;
break;
case CAIRO_FORMAT_RGB24:
if (colorSpace == NULL)
colorSpace = CGColorSpaceCreateDeviceRGB ();
bitinfo |= kCGImageAlphaNoneSkipFirst;
bitsPerComponent = 8;
bitsPerPixel = 32;
break;
case CAIRO_FORMAT_A8:
bitsPerComponent = 8;
bitsPerPixel = 8;
break;
case CAIRO_FORMAT_A1:
#ifdef WORDS_BIGENDIAN
bitsPerComponent = 1;
bitsPerPixel = 1;
break;
#endif
case CAIRO_FORMAT_RGB30:
case CAIRO_FORMAT_RGB16_565:
case CAIRO_FORMAT_RGB96F:
case CAIRO_FORMAT_RGBA128F:
case CAIRO_FORMAT_INVALID:
default:
return NULL;
}
dataProvider = CGDataProviderCreateWithData (releaseInfo,
data,
height * stride,
releaseCallback);
if (unlikely (!dataProvider)) {
// manually release
if (releaseCallback)
releaseCallback (releaseInfo, data, height * stride);
goto FINISH;
}
if (format == CAIRO_FORMAT_A8 || format == CAIRO_FORMAT_A1) {
cairo_quartz_float_t decode[] = {1.0, 0.0};
image = CGImageMaskCreate (width, height,
bitsPerComponent,
bitsPerPixel,
stride,
dataProvider,
decode,
interpolate);
} else
image = CGImageCreate (width, height,
bitsPerComponent,
bitsPerPixel,
stride,
colorSpace,
bitinfo,
dataProvider,
NULL,
interpolate,
kCGRenderingIntentDefault);
FINISH:
CGDataProviderRelease (dataProvider);
if (colorSpace != colorSpaceOverride)
CGColorSpaceRelease (colorSpace);
return image;
}
static inline cairo_bool_t
_cairo_quartz_is_cgcontext_bitmap_context (CGContextRef cgc)
{
@ -682,12 +596,6 @@ CairoQuartzCreateGradientFunction (const cairo_gradient_pattern_t *gradient,
&gradient_callbacks);
}
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,
@ -697,25 +605,19 @@ _cairo_surface_to_cgimage (cairo_surface_t *source,
CGImageRef *image_out)
{
cairo_status_t status;
cairo_image_surface_t *image_surface;
void *image_data, *image_extra;
cairo_quartz_image_surface_t *image_surface;
void *image_extra;
cairo_bool_t acquired = FALSE;
if (source->backend && source->backend->type == CAIRO_SURFACE_TYPE_QUARTZ_IMAGE) {
cairo_quartz_image_surface_t *surface = (cairo_quartz_image_surface_t *) source;
*image_out = CGImageRetain (surface->image);
return CAIRO_STATUS_SUCCESS;
}
if (_cairo_surface_is_quartz (source)) {
cairo_quartz_surface_t *surface = (cairo_quartz_surface_t *) source;
if (IS_EMPTY (surface)) {
if (_cairo_surface_is_quartz (source) || _cairo_surface_is_quartz_image (source)) {
CGContextRef cgContext = cairo_quartz_surface_get_cg_context(source);
if (IS_EMPTY (source)) {
*image_out = NULL;
return CAIRO_INT_STATUS_NOTHING_TO_DO;
}
if (_cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext)) {
*image_out = _cairo_quartz_surface_snapshot_get_image (surface);
if (_cairo_quartz_is_cgcontext_bitmap_context (cgContext)) {
*image_out = _cairo_quartz_surface_snapshot_get_image (source);
return CAIRO_STATUS_SUCCESS;
}
@ -724,80 +626,60 @@ _cairo_surface_to_cgimage (cairo_surface_t *source,
}
if (source->type == CAIRO_SURFACE_TYPE_RECORDING) {
image_surface = (cairo_image_surface_t *)
cairo_image_surface_create (format, extents->width, extents->height);
if (unlikely (image_surface->base.status)) {
status = image_surface->base.status;
cairo_surface_destroy (&image_surface->base);
cairo_image_surface_t *surface =
(cairo_image_surface_t*)cairo_image_surface_create (format, extents->width,
extents->height);
if (unlikely (surface->base.status)) {
status = surface->base.status;
cairo_surface_destroy (&surface->base);
return status;
}
status = _cairo_recording_surface_replay_with_clip (source,
matrix,
&image_surface->base,
&surface->base,
NULL,
FALSE);
if (unlikely (status)) {
cairo_surface_destroy (&image_surface->base);
cairo_surface_destroy (&surface->base);
return status;
}
image_surface =
(cairo_quartz_image_surface_t*)cairo_quartz_image_surface_create (&surface->base);
cairo_matrix_init_identity (matrix);
}
else {
status = _cairo_surface_acquire_source_image (source, &image_surface,
cairo_image_surface_t *surface;
status = _cairo_surface_acquire_source_image (source, &surface,
&image_extra);
if (unlikely (status))
return status;
acquired = TRUE;
}
if (image_surface->width == 0 || image_surface->height == 0) {
*image_out = NULL;
if (acquired)
_cairo_surface_release_source_image (source, image_surface, image_extra);
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
cairo_surface_destroy (&image_surface->base);
return status;
acquired = TRUE;
}
image_data = _cairo_malloc_ab (image_surface->height, image_surface->stride);
if (unlikely (!image_data))
{
if (acquired)
_cairo_surface_release_source_image (source, image_surface, image_extra);
else
cairo_surface_destroy (&image_surface->base);
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
*image_out = NULL;
if (image_surface->width > 0 && image_surface->height > 0) {
*image_out = _cairo_quartz_surface_snapshot_get_image (&image_surface->base);
status = CAIRO_STATUS_SUCCESS;
}
// 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, image_surface->data,
(image_surface->height - 1) * image_surface->stride +
cairo_format_stride_for_width (image_surface->format,
image_surface->width));
*image_out = CairoQuartzCreateCGImage (image_surface->format,
image_surface->width,
image_surface->height,
image_surface->stride,
image_data,
TRUE,
NULL,
DataProviderReleaseCallback,
image_data);
if (acquired) {
_cairo_surface_release_source_image (source, image_surface->imageSurface, image_extra);
image_surface->imageSurface = NULL;
}
cairo_surface_destroy (&image_surface->base);
/* TODO: differentiate memory error and unsupported surface type */
if (unlikely (*image_out == NULL))
status = CAIRO_INT_STATUS_UNSUPPORTED;
if (acquired)
_cairo_surface_release_source_image (source, image_surface, image_extra);
else
cairo_surface_destroy (&image_surface->base);
return status;
}
@ -2372,8 +2254,14 @@ cairo_quartz_surface_get_cg_context (cairo_surface_t *surface)
if (surface && _cairo_surface_is_quartz (surface)) {
cairo_quartz_surface_t *quartz = (cairo_quartz_surface_t *) surface;
return quartz->cgContext;
} else
return NULL;
}
if (surface && _cairo_surface_is_quartz_image (surface)) {
cairo_quartz_image_surface_t *quartz = (cairo_quartz_image_surface_t *) surface;
return quartz->cgContext;
}
return NULL;
}
/**
@ -2391,12 +2279,15 @@ _cairo_surface_is_quartz (const cairo_surface_t *surface)
}
cairo_surface_t*
_cairo_quartz_snapshot_create (cairo_quartz_surface_t *surface)
_cairo_quartz_snapshot_create (cairo_surface_t *surface)
{
cairo_quartz_snapshot_t *snapshot = NULL;
CGContextRef cgContext;
if (!surface || IS_EMPTY (surface)) //IS_EMPTY returns true if the surface type is wrong.
return NULL;
if (!surface || !_cairo_surface_is_quartz (&surface->base) || IS_EMPTY (surface) ||
! _cairo_quartz_is_cgcontext_bitmap_context (surface->cgContext))
if (_cairo_surface_is_quartz (surface) &&
! _cairo_quartz_is_cgcontext_bitmap_context (((cairo_quartz_surface_t*)surface)->cgContext))
return NULL;
snapshot = _cairo_malloc (sizeof (cairo_quartz_snapshot_t));
@ -2405,10 +2296,13 @@ _cairo_quartz_snapshot_create (cairo_quartz_surface_t *surface)
return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
memset (snapshot, 0, sizeof (cairo_quartz_snapshot_t));
cgContext = cairo_quartz_surface_get_cg_context (surface);
_cairo_surface_init (&snapshot->base,
&cairo_quartz_snapshot_backend,
NULL, CAIRO_CONTENT_COLOR_ALPHA, FALSE);
snapshot->image = CGBitmapContextCreateImage (surface->cgContext);
snapshot->image = CGBitmapContextCreateImage (cgContext);
_cairo_surface_attach_snapshot (surface, &snapshot->base, NULL);
cairo_surface_destroy (&snapshot->base); // The surface has reffed the snapshot so we must unref it here.
return &snapshot->base;
}
@ -2423,18 +2317,19 @@ _cairo_quartz_snapshot_finish (void *surface)
}
CGImageRef
_cairo_quartz_surface_snapshot_get_image (cairo_quartz_surface_t *surface)
_cairo_quartz_surface_snapshot_get_image (cairo_surface_t *surface)
{
cairo_surface_t *snapshot =
_cairo_surface_has_snapshot (&surface->base, &cairo_quartz_snapshot_backend);
cairo_surface_t *snapshot;
assert (_cairo_surface_is_quartz (surface) ||
_cairo_surface_is_quartz_image (surface));
snapshot =
_cairo_surface_has_snapshot (surface, &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);
cairo_surface_destroy (snapshot);
}
return CGImageRetain (((cairo_quartz_snapshot_t*)snapshot)->image);