aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2023-06-23 14:37:12 +0200
committerPřemysl Eric Janouch <p@janouch.name>2023-06-24 13:56:36 +0200
commitadd96b37a65fa40e87fd1f3eb9bead896fc2ae8f (patch)
tree78a7bfde8dbd776c1cb6002f33ddbd0ab1313648
parentc2e8b65d0f77e7b93c4676df74bb0cf706e5665b (diff)
downloadfiv-add96b37a65fa40e87fd1f3eb9bead896fc2ae8f.tar.gz
fiv-add96b37a65fa40e87fd1f3eb9bead896fc2ae8f.tar.xz
fiv-add96b37a65fa40e87fd1f3eb9bead896fc2ae8f.zip
Stop abusing Cairo user data, part 1
This commit temporarily breaks multi-page images and animations.
-rw-r--r--fiv-io.c913
-rw-r--r--fiv-io.h126
-rw-r--r--fiv-thumbnail.c8
-rw-r--r--fiv-view.c3
-rw-r--r--tools/benchmark-io.c4
5 files changed, 542 insertions, 512 deletions
diff --git a/fiv-io.c b/fiv-io.c
index 5c18daf..9b34fe4 100644
--- a/fiv-io.c
+++ b/fiv-io.c
@@ -178,21 +178,110 @@ add_warning(const FivIoOpenContext *ctx, const char *format, ...)
va_end(ap);
}
+// --- Images ------------------------------------------------------------------
+
+static FivIoImage *
+fiv_io_image_new(cairo_format_t format, uint32_t width, uint32_t height)
+{
+ // CAIRO_STRIDE_ALIGNMENT is 4 bytes, we only use multiples.
+ size_t unit = 0;
+ switch (format) {
+ case CAIRO_FORMAT_RGB24:
+ case CAIRO_FORMAT_RGB30:
+ case CAIRO_FORMAT_ARGB32:
+ unit = 4;
+ break;
+ case CAIRO_FORMAT_RGB96F:
+ unit = 12;
+ break;
+ case CAIRO_FORMAT_RGBA128F:
+ unit = 16;
+ break;
+ default:
+ return NULL;
+ }
+
+ uint8_t *data = g_try_malloc0(unit * width * height);
+ if (!data)
+ return NULL;
+
+ FivIoImage *image = g_rc_box_new0(FivIoImage);
+ image->data = data;
+ image->format = format;
+ image->width = width;
+ image->stride = width * unit;
+ image->height = height;
+ return image;
+}
+
+FivIoImage *
+fiv_io_image_ref(FivIoImage *self)
+{
+ return g_rc_box_acquire(self);
+}
+
+static void
+fiv_io_image_finalize(FivIoImage *image)
+{
+ g_free(image->data);
+
+ g_bytes_unref(image->exif);
+ g_bytes_unref(image->icc);
+ g_bytes_unref(image->xmp);
+ g_bytes_unref(image->thum);
+
+ if (image->render)
+ image->render->destroy(image->render);
+
+ if (image->page_next)
+ fiv_io_image_unref(image->page_next);
+ if (image->frame_next)
+ fiv_io_image_unref(image->frame_next);
+}
+
+void
+fiv_io_image_unref(FivIoImage *self)
+{
+ g_rc_box_release_full(self, (GDestroyNotify) fiv_io_image_finalize);
+}
+
+static cairo_surface_t *
+fiv_io_image_to_surface_noref(const FivIoImage *image)
+{
+ return cairo_image_surface_create_for_data(
+ image->data, image->format, image->width, image->height, image->stride);
+}
+
+cairo_surface_t *
+fiv_io_image_to_surface(FivIoImage *image)
+{
+ // TODO(p): Remove this shortcut eventually. And the function.
+ if (!image)
+ return NULL;
+
+ static cairo_user_data_key_t key_image;
+ cairo_surface_t *surface = cairo_image_surface_create_for_data(
+ image->data, image->format, image->width, image->height, image->stride);
+ cairo_surface_set_user_data(surface, &key_image,
+ image, (cairo_destroy_func_t) fiv_io_image_unref);
+ return surface;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
static bool
-try_append_page(cairo_surface_t *surface, cairo_surface_t **result,
- cairo_surface_t **result_tail)
+try_append_page(
+ FivIoImage *image, FivIoImage **result, FivIoImage **result_tail)
{
- if (!surface)
+ if (!image)
return false;
if (*result) {
- cairo_surface_set_user_data(*result_tail, &fiv_io_key_page_next,
- surface, (cairo_destroy_func_t) cairo_surface_destroy);
- cairo_surface_set_user_data(
- surface, &fiv_io_key_page_previous, *result_tail, NULL);
- *result_tail = surface;
+ (*result_tail)->page_next = image;
+ image->page_previous = *result_tail;
+ *result_tail = image;
} else {
- *result = *result_tail = surface;
+ *result = *result_tail = image;
}
return true;
}
@@ -322,12 +411,8 @@ trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len)
static void
fiv_io_profile_cmyk(
- cairo_surface_t *surface, FivIoProfile source, FivIoProfile target)
+ FivIoImage *image, FivIoProfile source, FivIoProfile target)
{
- unsigned char *data = cairo_image_surface_get_data(surface);
- int w = cairo_image_surface_get_width(surface);
- int h = cairo_image_surface_get_height(surface);
-
#ifndef HAVE_LCMS2
(void) source;
(void) target;
@@ -338,12 +423,14 @@ fiv_io_profile_cmyk(
FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0);
}
if (transform) {
- cmsDoTransform(transform, data, data, w * h);
+ cmsDoTransform(
+ transform, image->data, image->data, image->width * image->height);
cmsDeleteTransform(transform);
return;
}
#endif
- trivial_cmyk_to_host_byte_order_argb(data, w * h);
+ trivial_cmyk_to_host_byte_order_argb(
+ image->data, image->width * image->height);
}
static bool
@@ -382,21 +469,11 @@ fiv_io_profile_rgb_direct(unsigned char *data, int w, int h,
}
static void
-fiv_io_profile_xrgb32_direct(
- unsigned char *data, int w, int h, FivIoProfile source, FivIoProfile target)
-{
- fiv_io_profile_rgb_direct(data, w, h, source, target,
- FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32);
-}
-
-static void
fiv_io_profile_xrgb32(
- cairo_surface_t *surface, FivIoProfile source, FivIoProfile target)
+ FivIoImage *image, FivIoProfile source, FivIoProfile target)
{
- unsigned char *data = cairo_image_surface_get_data(surface);
- int w = cairo_image_surface_get_width(surface);
- int h = cairo_image_surface_get_height(surface);
- fiv_io_profile_xrgb32_direct(data, w, h, source, target);
+ fiv_io_profile_rgb_direct(image->data, image->width, image->height,
+ source, target, FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32);
}
static void
@@ -410,36 +487,30 @@ fiv_io_profile_4x16le_direct(
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
-fiv_io_profile_page(cairo_surface_t *page, FivIoProfile target,
- void (*frame_cb) (cairo_surface_t *, FivIoProfile, FivIoProfile))
+fiv_io_profile_page(FivIoImage *page, FivIoProfile target,
+ void (*frame_cb) (FivIoImage *, FivIoProfile, FivIoProfile))
{
- GBytes *bytes = NULL;
FivIoProfile source = NULL;
- if ((bytes = cairo_surface_get_user_data(page, &fiv_io_key_icc)))
- source = fiv_io_profile_new_from_bytes(bytes);
+ if (page->icc)
+ source = fiv_io_profile_new_from_bytes(page->icc);
// TODO(p): All animations need to be composited in a linear colour space.
- for (cairo_surface_t *frame = page; frame != NULL;
- frame = cairo_surface_get_user_data(frame, &fiv_io_key_frame_next))
- frame_cb(frame, source, target);
+ for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next)
+ frame_cb(page, source, target);
if (source)
fiv_io_profile_free(source);
}
static void
-fiv_io_premultiply_argb32(cairo_surface_t *surface)
+fiv_io_premultiply_argb32(FivIoImage *image)
{
- int w = cairo_image_surface_get_width(surface);
- int h = cairo_image_surface_get_height(surface);
- unsigned char *data = cairo_image_surface_get_data(surface);
- int stride = cairo_image_surface_get_stride(surface);
- if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32)
+ if (image->format != CAIRO_FORMAT_ARGB32)
return;
- for (int y = 0; y < h; y++) {
- uint32_t *dstp = (uint32_t *) (data + stride * y);
- for (int x = 0; x < w; x++) {
+ for (uint32_t y = 0; y < image->height; y++) {
+ uint32_t *dstp = (uint32_t *) (image->data + image->stride * y);
+ for (uint32_t x = 0; x < image->width; x++) {
uint32_t argb = dstp[x], a = argb >> 24;
dstp[x] = a << 24 |
PREMULTIPLY8(a, 0xFF & (argb >> 16)) << 16 |
@@ -455,32 +526,27 @@ fiv_io_premultiply_argb32(cairo_surface_t *surface)
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8_PREMUL : TYPE_ARGB_8_PREMUL)
static void
-fiv_io_profile_argb32(cairo_surface_t *surface,
+fiv_io_profile_argb32(FivIoImage *image,
FivIoProfile source, FivIoProfile target)
{
- g_return_if_fail(
- cairo_image_surface_get_format(surface) == CAIRO_FORMAT_ARGB32);
+ g_return_if_fail(image->format == CAIRO_FORMAT_ARGB32);
- unsigned char *data = cairo_image_surface_get_data(surface);
- int w = cairo_image_surface_get_width(surface);
- int h = cairo_image_surface_get_height(surface);
- fiv_io_profile_rgb_direct(data, w, h, source, target,
+ fiv_io_profile_rgb_direct(image->data, image->width, image->height,
+ source, target,
FIV_IO_PROFILE_ARGB32_PREMUL, FIV_IO_PROFILE_ARGB32_PREMUL);
}
static void
-fiv_io_profile_argb32_premultiply(cairo_surface_t *surface,
- FivIoProfile source, FivIoProfile target)
+fiv_io_profile_argb32_premultiply(
+ FivIoImage *image, FivIoProfile source, FivIoProfile target)
{
- unsigned char *data = cairo_image_surface_get_data(surface);
- int w = cairo_image_surface_get_width(surface);
- int h = cairo_image_surface_get_height(surface);
- if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32) {
- fiv_io_profile_xrgb32_direct(data, w, h, source, target);
- } else if (!fiv_io_profile_rgb_direct(data, w, h, source, target,
+ if (image->format != CAIRO_FORMAT_ARGB32) {
+ fiv_io_profile_xrgb32(image, source, target);
+ } else if (!fiv_io_profile_rgb_direct(image->data,
+ image->width, image->height, source, target,
FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32_PREMUL)) {
g_debug("failed to create a premultiplying transform");
- fiv_io_premultiply_argb32(surface);
+ fiv_io_premultiply_argb32(image);
}
}
@@ -508,31 +574,29 @@ fiv_io_profile_argb32_premultiply_page(
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
-fiv_io_profile_any(cairo_surface_t *surface,
- FivIoProfile source, FivIoProfile target)
+fiv_io_profile_any(FivIoImage *image, FivIoProfile source, FivIoProfile target)
{
// TODO(p): Ensure we do colour management early enough, so that
// no avoidable increase of quantization error occurs beforehands,
// and also for correct alpha compositing.
- switch (cairo_image_surface_get_format(surface)) {
+ switch (image->format) {
break; case CAIRO_FORMAT_RGB24:
- fiv_io_profile_xrgb32(surface, source, target);
+ fiv_io_profile_xrgb32(image, source, target);
break; case CAIRO_FORMAT_ARGB32:
- fiv_io_profile_argb32(surface, source, target);
+ fiv_io_profile_argb32(image, source, target);
break; default:
g_debug("CM attempted on an unsupported surface format");
}
}
// TODO(p): Offer better integration, upgrade the bit depth if appropriate.
-static cairo_surface_t *
-fiv_io_profile_finalize(cairo_surface_t *image, FivIoProfile target)
+static FivIoImage *
+fiv_io_profile_finalize(FivIoImage *image, FivIoProfile target)
{
if (!target)
return image;
- for (cairo_surface_t *page = image; page != NULL;
- page = cairo_surface_get_user_data(page, &fiv_io_key_page_next))
+ for (FivIoImage *page = image; page != NULL; page = page->page_next)
fiv_io_profile_page(page, target, fiv_io_profile_any);
return image;
}
@@ -633,8 +697,8 @@ struct load_wuffs_frame_context {
FivIoProfile target; ///< Target device profile, if any
FivIoProfile source; ///< Source colour profile, if any
- cairo_surface_t *result; ///< The resulting surface (referenced)
- cairo_surface_t *result_tail; ///< The final animation frame
+ FivIoImage *result; ///< The resulting image (referenced)
+ FivIoImage *result_tail; ///< The final animation frame
};
static bool
@@ -661,38 +725,32 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
decode_format = CAIRO_FORMAT_ARGB32;
unsigned char *targetbuf = NULL;
- cairo_surface_t *surface =
- cairo_image_surface_create(decode_format, ctx->width, ctx->height);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
+ FivIoImage *image =
+ fiv_io_image_new(decode_format, ctx->width, ctx->height);
+ if (!image) {
+ set_error(error, "image allocation failure");
goto fail;
}
- // CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with
- // ARGB/BGR/XRGB/BGRX. This function does not support a stride different
- // from the width, maybe Wuffs internals do not either.
- unsigned char *surface_data = cairo_image_surface_get_data(surface);
- int surface_stride = cairo_image_surface_get_stride(surface);
+ // There is no padding with ARGB/BGR/XRGB/BGRX.
+ // This function does not support a stride different from the width,
+ // maybe Wuffs internals do not either.
wuffs_base__pixel_buffer pb = {0};
if (ctx->expand_16_float || ctx->pack_16_10) {
- uint32_t targetbuf_size = ctx->height * ctx->width * 8;
+ uint32_t targetbuf_size = image->height * image->width * 8;
targetbuf = g_malloc(targetbuf_size);
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg,
wuffs_base__make_slice_u8(targetbuf, targetbuf_size));
} else {
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg,
- wuffs_base__make_slice_u8(surface_data,
- surface_stride * cairo_image_surface_get_height(surface)));
+ wuffs_base__make_slice_u8(
+ image->data, image->stride * image->height));
}
if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status));
goto fail;
}
- // Starting to modify pixel data directly. Probably an unnecessary call.
- cairo_surface_flush(surface);
-
status = wuffs_base__image_decoder__decode_frame(ctx->dec, &pb, ctx->src,
WUFFS_BASE__PIXEL_BLEND__SRC, ctx->workbuf, NULL);
if (!wuffs_base__status__is_ok(&status)) {
@@ -708,18 +766,17 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
targetbuf, ctx->width, ctx->height, ctx->source, ctx->target);
// The first one premultiplies below, the second doesn't need to.
} else {
- fiv_io_profile_xrgb32_direct(surface_data, ctx->width, ctx->height,
- ctx->source, ctx->target);
- fiv_io_premultiply_argb32(surface);
+ fiv_io_profile_xrgb32(image, ctx->source, ctx->target);
+ fiv_io_premultiply_argb32(image);
}
}
if (ctx->expand_16_float) {
g_debug("Wuffs to Cairo RGBA128F");
uint16_t *in = (uint16_t *) targetbuf;
- float *out = (float *) surface_data;
- for (uint32_t y = 0; y < ctx->height; y++) {
- for (uint32_t x = 0; x < ctx->width; x++) {
+ float *out = (float *) image->data;
+ for (uint32_t y = 0; y < image->height; y++) {
+ for (uint32_t x = 0; x < image->width; x++) {
float b = *in++ / 65535., g = *in++ / 65535.,
r = *in++ / 65535., a = *in++ / 65535.;
*out++ = r * a;
@@ -731,9 +788,9 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
} else if (ctx->pack_16_10) {
g_debug("Wuffs to Cairo RGB30");
uint16_t *in = (uint16_t *) targetbuf;
- uint32_t *out = (uint32_t *) surface_data;
- for (uint32_t y = 0; y < ctx->height; y++) {
- for (uint32_t x = 0; x < ctx->width; x++) {
+ uint32_t *out = (uint32_t *) image->data;
+ for (uint32_t y = 0; y < image->height; y++) {
+ for (uint32_t x = 0; x < image->width; x++) {
uint32_t b = *in++, g = *in++, r = *in++, X = *in++;
*out++ = (X >> 14) << 30 |
(r >> 6) << 20 | (g >> 6) << 10 | (b >> 6);
@@ -741,19 +798,17 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
}
}
- // Pixel data has been written, need to let Cairo know.
- cairo_surface_mark_dirty(surface);
-
// Single-frame images get a fast path, animations are are handled slowly:
if (wuffs_base__frame_config__index(&fc) > 0) {
- // Copy the previous frame to a new surface.
- cairo_surface_t *canvas = cairo_image_surface_create(
- ctx->cairo_format, ctx->width, ctx->height);
- int stride = cairo_image_surface_get_stride(canvas);
- int height = cairo_image_surface_get_height(canvas);
- memcpy(cairo_image_surface_get_data(canvas),
- cairo_image_surface_get_data(ctx->result_tail), stride * height);
- cairo_surface_mark_dirty(canvas);
+ // Copy the previous frame to a new image.
+ FivIoImage *prev = ctx->result_tail, *canvas = fiv_io_image_new(
+ prev->format, prev->width, prev->height);
+ if (!canvas) {
+ set_error(error, "image allocation failure");
+ goto fail;
+ }
+
+ memcpy(canvas->data, prev->data, prev->stride * prev->height);
// Apply that frame's disposal method.
// XXX: We do not expect opaque pictures to receive holes this way.
@@ -770,7 +825,9 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
b = (uint8_t) (bg) / 255. / a;
}
- cairo_t *cr = cairo_create(canvas);
+ cairo_surface_t *surface = fiv_io_image_to_surface_noref(canvas);
+ cairo_t *cr = cairo_create(surface);
+ cairo_surface_destroy(surface);
switch (wuffs_base__frame_config__disposal(&ctx->last_fc)) {
case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND:
cairo_rectangle(cr, bounds.min_incl_x, bounds.min_incl_y,
@@ -800,46 +857,41 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
? CAIRO_OPERATOR_SOURCE
: CAIRO_OPERATOR_OVER);
+ surface = fiv_io_image_to_surface_noref(image);
cairo_set_source_surface(cr, surface, 0, 0);
+ cairo_surface_destroy(surface);
cairo_paint(cr);
cairo_destroy(cr);
- cairo_surface_destroy(surface);
- surface = canvas;
+
+ fiv_io_image_unref(image);
+ image = canvas;
}
if (ctx->meta_exif)
- cairo_surface_set_user_data(surface, &fiv_io_key_exif,
- g_bytes_ref(ctx->meta_exif), (cairo_destroy_func_t) g_bytes_unref);
+ image->exif = g_bytes_ref(ctx->meta_exif);
if (ctx->meta_iccp)
- cairo_surface_set_user_data(surface, &fiv_io_key_icc,
- g_bytes_ref(ctx->meta_iccp), (cairo_destroy_func_t) g_bytes_unref);
+ image->icc = g_bytes_ref(ctx->meta_iccp);
if (ctx->meta_xmp)
- cairo_surface_set_user_data(surface, &fiv_io_key_xmp,
- g_bytes_ref(ctx->meta_xmp), (cairo_destroy_func_t) g_bytes_unref);
+ image->xmp = g_bytes_ref(ctx->meta_xmp);
- cairo_surface_set_user_data(surface, &fiv_io_key_loops,
- (void *) (uintptr_t) wuffs_base__image_decoder__num_animation_loops(
- ctx->dec), NULL);
- cairo_surface_set_user_data(surface, &fiv_io_key_frame_duration,
- (void *) (intptr_t) (wuffs_base__frame_config__duration(&fc) /
- WUFFS_BASE__FLICKS_PER_MILLISECOND), NULL);
+ image->loops = wuffs_base__image_decoder__num_animation_loops(ctx->dec);
+ image->frame_duration = wuffs_base__frame_config__duration(&fc) /
+ WUFFS_BASE__FLICKS_PER_MILLISECOND;
- cairo_surface_set_user_data(
- surface, &fiv_io_key_frame_previous, ctx->result_tail, NULL);
+ image->frame_previous = ctx->result_tail;
if (ctx->result_tail)
- cairo_surface_set_user_data(ctx->result_tail, &fiv_io_key_frame_next,
- surface, (cairo_destroy_func_t) cairo_surface_destroy);
+ ctx->result_tail->frame_next = image;
else
- ctx->result = surface;
+ ctx->result = image;
- ctx->result_tail = surface;
+ ctx->result_tail = image;
ctx->last_fc = fc;
g_free(targetbuf);
return wuffs_base__status__is_ok(&status);
fail:
- cairo_surface_destroy(surface);
- g_clear_pointer(&ctx->result, cairo_surface_destroy);
+ g_clear_pointer(&image, fiv_io_image_unref);
+ g_clear_pointer(&ctx->result, fiv_io_image_unref);
ctx->result_tail = NULL;
g_free(targetbuf);
return false;
@@ -848,7 +900,7 @@ fail:
// https://github.com/google/wuffs/blob/main/example/gifplayer/gifplayer.c
// is pure C, and a good reference. I can't use the auxiliary libraries,
// since they depend on C++, which is undesirable.
-static cairo_surface_t *
+static FivIoImage *
open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
const FivIoOpenContext *ioctx, GError **error)
{
@@ -1010,8 +1062,7 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
// Wrap the chain around, since our caller receives only one pointer.
if (ctx.result)
- cairo_surface_set_user_data(
- ctx.result, &fiv_io_key_frame_previous, ctx.result_tail, NULL);
+ ctx.result->frame_previous = ctx.result_tail;
fail:
free(ctx.workbuf.ptr);
@@ -1022,7 +1073,7 @@ fail:
return ctx.result;
}
-static cairo_surface_t *
+static FivIoImage *
open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -1032,11 +1083,11 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
return NULL;
}
- cairo_surface_t *surface =
+ FivIoImage *image =
open_wuffs(dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE),
ctx, error);
free(dec);
- return surface;
+ return image;
}
// --- Wuffs for PNG thumbnails ------------------------------------------------
@@ -1388,11 +1439,11 @@ parse_jpeg_metadata(const char *data, size_t len, struct jpeg_metadata *meta)
g_byte_array_set_size(meta->icc, 0);
}
-static cairo_surface_t *open_libjpeg_turbo(
+static FivIoImage *open_libjpeg_turbo(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error);
static void
-load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
+load_jpeg_finalize(FivIoImage *image, bool cmyk,
const FivIoOpenContext *ctx, const char *data, size_t len)
{
struct jpeg_metadata meta = {
@@ -1406,13 +1457,13 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
if (!ctx->first_frame_only) {
// XXX: This is ugly, as it relies on just the first individual image
// having any follow-up entries (as it should be).
- cairo_surface_t *surface_tail = surface;
+ FivIoImage *image_tail = image;
for (guint i = 0; i < meta.mpf->len; i++) {
const char *jpeg = meta.mpf->pdata[i];
GError *error = NULL;
if (!try_append_page(
open_libjpeg_turbo(jpeg, len - (jpeg - data), ctx, &error),
- &surface, &surface_tail)) {
+ &image, &image_tail)) {
add_warning(ctx, "MPF image %d: %s", i + 2, error->message);
g_error_free(error);
}
@@ -1421,17 +1472,13 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
g_ptr_array_free(meta.mpf, TRUE);
if (meta.exif->len)
- cairo_surface_set_user_data(surface, &fiv_io_key_exif,
- g_byte_array_free_to_bytes(meta.exif),
- (cairo_destroy_func_t) g_bytes_unref);
+ image->exif = g_byte_array_free_to_bytes(meta.exif);
else
g_byte_array_free(meta.exif, TRUE);
GBytes *icc_profile = NULL;
if (meta.icc->len)
- cairo_surface_set_user_data(surface, &fiv_io_key_icc,
- (icc_profile = g_byte_array_free_to_bytes(meta.icc)),
- (cairo_destroy_func_t) g_bytes_unref);
+ image->icc = icc_profile = g_byte_array_free_to_bytes(meta.icc);
else
g_byte_array_free(meta.icc, TRUE);
@@ -1441,15 +1488,12 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile));
if (cmyk)
- fiv_io_profile_cmyk(surface, source, ctx->screen_profile);
+ fiv_io_profile_cmyk(image, source, ctx->screen_profile);
else
- fiv_io_profile_any(surface, source, ctx->screen_profile);
+ fiv_io_profile_any(image, source, ctx->screen_profile);
if (source)
fiv_io_profile_free(source);
-
- // Pixel data has been written, need to let Cairo know.
- cairo_surface_mark_dirty(surface);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1480,18 +1524,18 @@ libjpeg_output_message(j_common_ptr cinfo)
add_warning(err->ctx, "%s", buf);
}
-static cairo_surface_t *
+static FivIoImage *
load_libjpeg_turbo(const char *data, gsize len, const FivIoOpenContext *ctx,
void (*loop)(struct jpeg_decompress_struct *, JSAMPARRAY), GError **error)
{
- cairo_surface_t *volatile surface = NULL;
+ FivIoImage *volatile image = NULL;
struct libjpeg_error_mgr jerr = {.error = error, .ctx = ctx};
struct jpeg_decompress_struct cinfo = {.err = jpeg_std_error(&jerr.pub)};
jerr.pub.error_exit = libjpeg_error_exit;
jerr.pub.output_message = libjpeg_output_message;
if (setjmp(jerr.buf)) {
- g_clear_pointer(&surface, cairo_surface_destroy);
+ g_clear_pointer(&image, fiv_io_image_unref);
jpeg_destroy_decompress(&cinfo);
return NULL;
}
@@ -1537,26 +1581,23 @@ load_libjpeg_turbo(const char *data, gsize len, const FivIoOpenContext *ctx,
cinfo.scale_denom = f.denom;
}
- surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
+ image = fiv_io_image_new(CAIRO_FORMAT_RGB24, width, height);
+ if (!image) {
+ set_error(error, "image allocation failure");
longjmp(jerr.buf, 1);
}
- unsigned char *surface_data = cairo_image_surface_get_data(surface);
- int surface_stride = cairo_image_surface_get_stride(surface);
JSAMPARRAY lines = (*cinfo.mem->alloc_small)(
(j_common_ptr) &cinfo, JPOOL_IMAGE, sizeof *lines * height);
for (int i = 0; i < height; i++)
- lines[i] = surface_data + i * surface_stride;
+ lines[i] = image->data + i * image->stride;
// Slightly unfortunate generalization.
loop(&cinfo, lines);
- load_jpeg_finalize(surface, use_cmyk, ctx, data, len);
+ load_jpeg_finalize(image, use_cmyk, ctx, data, len);
jpeg_destroy_decompress(&cinfo);
- return surface;
+ return image;
}
static void
@@ -1594,7 +1635,7 @@ load_libjpeg_enhanced(
#define load_libjpeg_enhanced libjpeg_turbo_load_simple
#endif
-static cairo_surface_t *
+static FivIoImage *
open_libjpeg_turbo(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -1630,17 +1671,15 @@ load_libwebp_error(VP8StatusCode err)
}
}
-static cairo_surface_t *
+static FivIoImage *
load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
const FivIoOpenContext *ctx, GError **error)
{
- cairo_surface_t *surface = cairo_image_surface_create(
+ FivIoImage *image = fiv_io_image_new(
config->input.has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
config->input.width, config->input.height);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ if (!image) {
+ set_error(error, "image allocation failure");
return NULL;
}
@@ -1649,10 +1688,9 @@ load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
config->output.width = config->input.width;
config->output.height = config->input.height;
config->output.is_external_memory = true;
- config->output.u.RGBA.rgba = cairo_image_surface_get_data(surface);
- config->output.u.RGBA.stride = cairo_image_surface_get_stride(surface);
- config->output.u.RGBA.size =
- config->output.u.RGBA.stride * cairo_image_surface_get_height(surface);
+ config->output.u.RGBA.rgba = image->data;
+ config->output.u.RGBA.stride = image->stride;
+ config->output.u.RGBA.size = config->output.u.RGBA.stride * image->height;
bool premultiply = !ctx->screen_profile;
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
@@ -1663,43 +1701,34 @@ load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
WebPIDecoder *idec = WebPIDecode(NULL, 0, config);
if (!idec) {
set_error(error, "WebP decoding error");
- cairo_surface_destroy(surface);
+ fiv_io_image_unref(image);
return NULL;
}
VP8StatusCode err = WebPIUpdate(idec, wd->bytes, wd->size);
- cairo_surface_mark_dirty(surface);
int x = 0, y = 0, w = 0, h = 0;
(void) WebPIDecodedArea(idec, &x, &y, &w, &h);
WebPIDelete(idec);
if (err == VP8_STATUS_OK)
- return surface;
+ return image;
if (err != VP8_STATUS_SUSPENDED) {
g_set_error(error, FIV_IO_ERROR, FIV_IO_ERROR_OPEN, "%s: %s",
"WebP decoding error", load_libwebp_error(err));
- cairo_surface_destroy(surface);
+ fiv_io_image_unref(image);
return NULL;
}
add_warning(ctx, "image file is truncated");
if (config->input.has_alpha)
- return surface;
+ return image;
// Always use transparent black, rather than opaque black.
- cairo_surface_t *masked = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, config->input.width, config->input.height);
- cairo_t *cr = cairo_create(masked);
- cairo_set_source_surface(cr, surface, 0, 0);
- cairo_rectangle(cr, x, y, w, h);
- cairo_clip(cr);
- cairo_paint(cr);
- cairo_destroy(cr);
- cairo_surface_destroy(surface);
- return masked;
+ image->format = CAIRO_FORMAT_ARGB32;
+ return image;
}
-static cairo_surface_t *
+static FivIoImage *
load_libwebp_frame(WebPAnimDecoder *dec, const WebPAnimInfo *info,
int *last_timestamp, GError **error)
{
@@ -1712,18 +1741,15 @@ load_libwebp_frame(WebPAnimDecoder *dec, const WebPAnimInfo *info,
bool is_opaque = (info->bgcolor & 0xFF) == 0xFF;
uint64_t area = info->canvas_width * info->canvas_height;
- cairo_surface_t *surface = cairo_image_surface_create(
+ FivIoImage *image = fiv_io_image_new(
is_opaque ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
info->canvas_width, info->canvas_height);
-
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ if (!image) {
+ set_error(error, "image allocation failure");
return NULL;
}
- uint32_t *dst = (uint32_t *) cairo_image_surface_get_data(surface);
+ uint32_t *dst = (uint32_t *) image->data;
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
memcpy(dst, buf, area * sizeof *dst);
} else {
@@ -1732,16 +1758,13 @@ load_libwebp_frame(WebPAnimDecoder *dec, const WebPAnimInfo *info,
*dst++ = GUINT32_FROM_LE(*src++);
}
- cairo_surface_mark_dirty(surface);
-
// This API is confusing and awkward.
- cairo_surface_set_user_data(surface, &fiv_io_key_frame_duration,
- (void *) (intptr_t) (timestamp - *last_timestamp), NULL);
+ image->frame_duration = timestamp - *last_timestamp;
*last_timestamp = timestamp;
- return surface;
+ return image;
}
-static cairo_surface_t *
+static FivIoImage *
load_libwebp_animated(
const WebPData *wd, const FivIoOpenContext *ctx, GError **error)
{
@@ -1755,7 +1778,7 @@ load_libwebp_animated(
WebPAnimDecoder *dec = WebPAnimDecoderNew(wd, &options);
WebPAnimDecoderGetInfo(dec, &info);
- cairo_surface_t *frames = NULL, *frames_tail = NULL;
+ FivIoImage *frames = NULL, *frames_tail = NULL;
if (info.canvas_width > INT_MAX || info.canvas_height > INT_MAX) {
set_error(error, "image dimensions overflow");
goto fail;
@@ -1763,30 +1786,27 @@ load_libwebp_animated(
int last_timestamp = 0;
while (WebPAnimDecoderHasMoreFrames(dec)) {
- cairo_surface_t *surface =
+ FivIoImage *image =
load_libwebp_frame(dec, &info, &last_timestamp, error);
- if (!surface) {
- g_clear_pointer(&frames, cairo_surface_destroy);
+ if (!image) {
+ g_clear_pointer(&frames, fiv_io_image_unref);
goto fail;
}
if (frames_tail)
- cairo_surface_set_user_data(frames_tail, &fiv_io_key_frame_next,
- surface, (cairo_destroy_func_t) cairo_surface_destroy);
+ frames_tail->frame_next = image;
else
- frames = surface;
+ frames = image;
- cairo_surface_set_user_data(
- surface, &fiv_io_key_frame_previous, frames_tail, NULL);
- frames_tail = surface;
+ image->frame_previous = frames_tail;
+ frames_tail = image;
}
if (frames) {
- cairo_surface_set_user_data(
- frames, &fiv_io_key_frame_previous, frames_tail, NULL);
+ frames->frame_previous = frames_tail;
} else {
set_error(error, "the animation has no frames");
- g_clear_pointer(&frames, cairo_surface_destroy);
+ g_clear_pointer(&frames, fiv_io_image_unref);
}
fail:
@@ -1794,7 +1814,7 @@ fail:
return frames;
}
-static cairo_surface_t *
+static FivIoImage *
open_libwebp(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -1814,7 +1834,7 @@ open_libwebp(
return NULL;
}
- cairo_surface_t *result = config.input.has_animation
+ FivIoImage *result = config.input.has_animation
? load_libwebp_animated(&wd, ctx, error)
: load_libwebp_nonanimated(&config, &wd, ctx, error);
if (!result)
@@ -1829,41 +1849,33 @@ open_libwebp(
}
// Releasing the demux chunk iterator is actually a no-op.
- // TODO(p): Avoid copy-pasting the chunk transfer code.
WebPChunkIterator chunk_iter = {};
uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
if ((flags & EXIF_FLAG) &&
WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter)) {
- cairo_surface_set_user_data(result, &fiv_io_key_exif,
- g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size),
- (cairo_destroy_func_t) g_bytes_unref);
+ result->exif =
+ g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
}
if ((flags & ICCP_FLAG) &&
WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter)) {
- cairo_surface_set_user_data(result, &fiv_io_key_icc,
- g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size),
- (cairo_destroy_func_t) g_bytes_unref);
+ result->icc =
+ g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
}
if ((flags & XMP_FLAG) &&
WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter)) {
- cairo_surface_set_user_data(result, &fiv_io_key_xmp,
- g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size),
- (cairo_destroy_func_t) g_bytes_unref);
+ result->xmp =
+ g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
}
if (WebPDemuxGetChunk(demux, "THUM", 1, &chunk_iter)) {
- cairo_surface_set_user_data(result, &fiv_io_key_thum,
- g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size),
- (cairo_destroy_func_t) g_bytes_unref);
+ result->thum =
+ g_bytes_new(chunk_iter.chunk.bytes, chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
}
- if (flags & ANIMATION_FLAG) {
- cairo_surface_set_user_data(result, &fiv_io_key_loops,
- (void *) (uintptr_t) WebPDemuxGetI(demux, WEBP_FF_LOOP_COUNT),
- NULL);
- }
+ if (flags & ANIMATION_FLAG)
+ result->loops = WebPDemuxGetI(demux, WEBP_FF_LOOP_COUNT);
WebPDemuxDelete(demux);
if (ctx->screen_profile)
@@ -2038,7 +2050,7 @@ tiff_ep_find_jpeg(const struct tiffer *T, struct tiff_ep_jpeg *out)
return true;
}
-static cairo_surface_t *
+static FivIoImage *
load_tiff_ep(
const struct tiffer *T, const FivIoOpenContext *ctx, GError **error)
{
@@ -2091,9 +2103,9 @@ load_tiff_ep(
return NULL;
}
- cairo_surface_t *surface = open_libjpeg_turbo(
+ FivIoImage *image = open_libjpeg_turbo(
(const char *) out.jpeg, out.jpeg_length, ctx, error);
- if (!surface)
+ if (!image)
return NULL;
// Note that Exif may override this later in fiv_io_open_from_data().
@@ -2102,13 +2114,12 @@ load_tiff_ep(
int64_t orientation = 0;
if (tiffer_find_integer(T, TIFF_Orientation, &orientation) &&
orientation >= 1 && orientation <= 8) {
- cairo_surface_set_user_data(surface, &fiv_io_key_orientation,
- (void *) (uintptr_t) orientation, NULL);
+ image->orientation = orientation;
}
- return surface;
+ return image;
}
-static cairo_surface_t *
+static FivIoImage *
open_tiff_ep(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -2121,11 +2132,11 @@ open_tiff_ep(
return NULL;
}
- cairo_surface_t *result = NULL, *result_tail = NULL;
+ FivIoImage *result = NULL, *result_tail = NULL;
while (tiffer_next_ifd(&T)) {
if (!try_append_page(
load_tiff_ep(&T, ctx, error), &result, &result_tail)) {
- g_clear_pointer(&result, cairo_surface_destroy);
+ g_clear_pointer(&result, fiv_io_image_unref);
return NULL;
}
if (ctx->first_frame_only)
@@ -2143,7 +2154,7 @@ open_tiff_ep(
#ifdef HAVE_LIBRAW // ---------------------------------------------------------
-static cairo_surface_t *
+static FivIoImage *
load_libraw(libraw_data_t *iprc, GError **error)
{
int err = 0;
@@ -2181,21 +2192,15 @@ load_libraw(libraw_data_t *iprc, GError **error)
return NULL;
}
- int width = image->width, height = image->height;
- cairo_surface_t *surface =
- cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ FivIoImage *I =
+ fiv_io_image_new(CAIRO_FORMAT_RGB24, image->width, image->height);
+ if (!I) {
+ set_error(error, "image allocation failure");
libraw_dcraw_clear_mem(image);
return NULL;
}
- // Starting to modify pixel data directly. Probably an unnecessary call.
- cairo_surface_flush(surface);
-
- uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface);
+ uint32_t *pixels = (uint32_t *) I->data;
unsigned char *p = image->data;
for (ushort y = 0; y < image->height; y++) {
for (ushort x = 0; x < image->width; x++) {
@@ -2205,14 +2210,11 @@ load_libraw(libraw_data_t *iprc, GError **error)
}
}
- // Pixel data has been written, need to let Cairo know.
- cairo_surface_mark_dirty(surface);
-
libraw_dcraw_clear_mem(image);
- return surface;
+ return I;
}
-static cairo_surface_t *
+static FivIoImage *
open_libraw(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -2230,7 +2232,7 @@ open_libraw(
iprc->params.output_bps = 8; // This should be the default value.
int err = 0;
- cairo_surface_t *result = NULL, *result_tail = NULL;
+ FivIoImage *result = NULL, *result_tail = NULL;
if ((err = libraw_open_buffer(iprc, (const void *) data, len))) {
set_error(error, libraw_strerror(err));
goto out;
@@ -2245,11 +2247,11 @@ open_libraw(
// This library is terrible, we need to start again.
if ((err = libraw_open_buffer(iprc, (const void *) data, len))) {
set_error(error, libraw_strerror(err));
- g_clear_pointer(&result, cairo_surface_destroy);
+ g_clear_pointer(&result, fiv_io_image_unref);
goto out;
}
if (!try_append_page(load_libraw(iprc, error), &result, &result_tail)) {
- g_clear_pointer(&result, cairo_surface_destroy);
+ g_clear_pointer(&result, fiv_io_image_unref);
goto out;
}
}
@@ -2270,14 +2272,14 @@ typedef struct {
} FivIoRenderClosureResvg;
static void
-load_resvg_destroy(void *closure)
+load_resvg_destroy(FivIoRenderClosure *closure)
{
- FivIoRenderClosureResvg *self = closure;
+ FivIoRenderClosureResvg *self = (void *) closure;
resvg_tree_destroy(self->tree);
g_free(self);
}
-static cairo_surface_t *
+static FivIoImage *
load_resvg_render_internal(FivIoRenderClosureResvg *self,
double scale, FivIoProfile target, GError **error)
{
@@ -2287,30 +2289,24 @@ load_resvg_render_internal(FivIoRenderClosureResvg *self,
return NULL;
}
- cairo_surface_t *surface =
- cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ FivIoImage *image = fiv_io_image_new(CAIRO_FORMAT_ARGB32, w, h);
+ if (!image) {
+ set_error(error, "image allocation failure");
return NULL;
}
- uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface);
+ uint32_t *pixels = (uint32_t *) image->data;
resvg_fit_to fit_to = {
scale == 1 ? RESVG_FIT_TO_TYPE_ORIGINAL : RESVG_FIT_TO_TYPE_ZOOM,
scale};
resvg_render(self->tree, fit_to, resvg_transform_identity(),
- cairo_image_surface_get_width(surface),
- cairo_image_surface_get_height(surface), (char *) pixels);
+ image->width, image->height, (char *) pixels);
for (int i = 0; i < w * h; i++) {
uint32_t rgba = g_ntohl(pixels[i]);
pixels[i] = rgba << 24 | rgba >> 8;
}
-
- cairo_surface_mark_dirty(surface);
- return fiv_io_profile_finalize(surface, target);
+ return fiv_io_profile_finalize(image, target);
}
static cairo_surface_t *
@@ -2318,7 +2314,8 @@ load_resvg_render(FivIoRenderClosure *closure, double scale)
{
FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure;
// TODO(p): Somehow get the target colour management profile.
- return load_resvg_render_internal(self, scale, NULL, NULL);
+ return fiv_io_image_to_surface(
+ load_resvg_render_internal(self, scale, NULL, NULL));
}
static const char *
@@ -2342,7 +2339,7 @@ load_resvg_error(int err)
}
}
-static cairo_surface_t *
+static FivIoImage *
open_resvg(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -2371,20 +2368,20 @@ open_resvg(
FivIoRenderClosureResvg *closure = g_malloc0(sizeof *closure);
closure->parent.render = load_resvg_render;
+ closure->parent.destroy = load_resvg_destroy;
closure->tree = tree;
closure->width = size.width;
closure->height = size.height;
- cairo_surface_t *surface =
+ FivIoImage *image =
load_resvg_render_internal(closure, 1., ctx->screen_profile, error);
- if (!surface) {
- load_resvg_destroy(closure);
+ if (!image) {
+ load_resvg_destroy(&closure->parent);
return NULL;
}
- cairo_surface_set_user_data(
- surface, &fiv_io_key_render, closure, load_resvg_destroy);
- return surface;
+ image->render = &closure->parent;
+ return image;
}
#endif // HAVE_RESVG ----------------------------------------------------------
@@ -2398,45 +2395,54 @@ typedef struct {
} FivIoRenderClosureLibrsvg;
static void
-load_librsvg_destroy(void *closure)
+load_librsvg_destroy(FivIoRenderClosure *closure)
{
- FivIoRenderClosureLibrsvg *self = closure;
+ FivIoRenderClosureLibrsvg *self = (void *) closure;
g_object_unref(self->handle);
g_free(self);
}
-static cairo_surface_t *
-load_librsvg_render(FivIoRenderClosure *closure, double scale)
+static FivIoImage *
+load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale,
+ FivIoProfile target, GError **error)
{
- FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure;
RsvgRectangle viewport = {.x = 0, .y = 0,
.width = self->width * scale, .height = self->height * scale};
- cairo_surface_t *surface = cairo_image_surface_create(
+ FivIoImage *image = fiv_io_image_new(
CAIRO_FORMAT_ARGB32, ceil(viewport.width), ceil(viewport.height));
+ if (!image) {
+ set_error(error, "image allocation failure");
+ return NULL;
+ }
- GError *error = NULL;
+ cairo_surface_t *surface = fiv_io_image_to_surface_noref(image);
cairo_t *cr = cairo_create(surface);
- (void) rsvg_handle_render_document(self->handle, cr, &viewport, &error);
+ cairo_surface_destroy(surface);
+ (void) rsvg_handle_render_document(self->handle, cr, &viewport, error);
+ cairo_status_t status = cairo_status(cr);
cairo_destroy(cr);
if (error) {
- g_debug("%s", error->message);
- g_error_free(error);
- cairo_surface_destroy(surface);
+ fiv_io_image_unref(image);
return NULL;
}
-
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- g_debug("%s", cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ set_error(error, cairo_status_to_string(status));
+ fiv_io_image_unref(image);
return NULL;
}
+ return fiv_io_profile_finalize(image, target);
+}
+static cairo_surface_t *
+load_librsvg_render(FivIoRenderClosure *closure, double scale)
+{
+ FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure;
// TODO(p): Somehow get the target colour management profile.
- return surface;
+ return fiv_io_image_to_surface(
+ load_librsvg_render_internal(self, scale, NULL, NULL));
}
-static cairo_surface_t *
+static FivIoImage *
open_librsvg(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -2473,32 +2479,24 @@ open_librsvg(
h = viewbox.height;
}
- // librsvg rasterizes filters, so this method isn't fully appropriate.
- // It might be worth removing altogether.
- cairo_rectangle_t extents = {
- .x = 0, .y = 0, .width = ceil(w), .height = ceil(h)};
- cairo_surface_t *surface =
- cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &extents);
-
- cairo_t *cr = cairo_create(surface);
- RsvgRectangle viewport = {.x = 0, .y = 0, .width = w, .height = h};
- if (!rsvg_handle_render_document(handle, cr, &viewport, error)) {
- cairo_surface_destroy(surface);
- cairo_destroy(cr);
- g_object_unref(handle);
- return NULL;
- }
-
- cairo_destroy(cr);
-
FivIoRenderClosureLibrsvg *closure = g_malloc0(sizeof *closure);
closure->parent.render = load_librsvg_render;
+ closure->parent.destroy = load_librsvg_destroy;
closure->handle = handle;
closure->width = w;
closure->height = h;
- cairo_surface_set_user_data(
- surface, &fiv_io_key_render, closure, load_librsvg_destroy);
- return fiv_io_profile_finalize(surface, ctx->screen_profile);
+
+ // librsvg rasterizes filters, so rendering to a recording Cairo surface
+ // has been abandoned.
+ FivIoImage *image =
+ load_librsvg_render_internal(closure, 1., ctx->screen_profile, error);
+ if (!image) {
+ load_librsvg_destroy(&closure->parent);
+ return NULL;
+ }
+
+ image->render = &closure->parent;
+ return fiv_io_profile_finalize(image, ctx->screen_profile);
}
#endif // HAVE_LIBRSVG --------------------------------------------------------
@@ -2571,8 +2569,9 @@ static const XcursorFile fiv_io_xcursor_adaptor = {
.seek = fiv_io_xcursor_seek,
};
-static cairo_surface_t *
-open_xcursor(const char *data, gsize len, GError **error)
+static FivIoImage *
+open_xcursor(
+ const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
if (len > G_MAXLONG) {
set_error(error, "size overflow");
@@ -2593,54 +2592,46 @@ open_xcursor(const char *data, gsize len, GError **error)
}
// Interpret cursors as animated pages.
- cairo_surface_t *pages = NULL, *frames_head = NULL, *frames_tail = NULL;
+ FivIoImage *pages = NULL, *frames_head = NULL, *frames_tail = NULL;
// XXX: Assuming that all "nominal sizes" have the same dimensions.
XcursorDim last_nominal = -1;
for (int i = 0; i < images->nimage; i++) {
XcursorImage *image = images->images[i];
+ FivIoImage *I =
+ fiv_io_image_new(CAIRO_FORMAT_ARGB32, image->width, image->height);
+ if (!I) {
+ add_warning(ctx, "%s", "image allocation failure");
+ break;
+ }
+
// The library automatically byte swaps in _XcursorReadImage().
- cairo_surface_t *surface = cairo_image_surface_create_for_data(
- (unsigned char *) image->pixels, CAIRO_FORMAT_ARGB32,
- image->width, image->height, image->width * sizeof *image->pixels);
- cairo_surface_set_user_data(surface, &fiv_io_key_frame_duration,
- (void *) (intptr_t) image->delay, NULL);
+ memcpy(I->data, image->pixels, I->stride * I->height);
+ I->frame_duration = image->delay;
if (pages && image->size == last_nominal) {
- cairo_surface_set_user_data(
- surface, &fiv_io_key_frame_previous, frames_tail, NULL);
- cairo_surface_set_user_data(frames_tail, &fiv_io_key_frame_next,
- surface, (cairo_destroy_func_t) cairo_surface_destroy);
+ I->frame_previous = frames_tail;
+ frames_tail->frame_next = I;
} else if (frames_head) {
- cairo_surface_set_user_data(
- frames_head, &fiv_io_key_frame_previous, frames_tail, NULL);
-
- cairo_surface_set_user_data(frames_head, &fiv_io_key_page_next,
- surface, (cairo_destroy_func_t) cairo_surface_destroy);
- cairo_surface_set_user_data(
- surface, &fiv_io_key_page_previous, frames_head, NULL);
- frames_head = surface;
+ frames_head->frame_previous = frames_tail;
+
+ frames_head->page_next = I;
+ I->page_previous = frames_head;
+ frames_head = I;
} else {
- pages = frames_head = surface;
+ pages = frames_head = I;
}
- frames_tail = surface;
+ frames_tail = I;
last_nominal = image->size;
}
- if (!pages) {
- XcursorImagesDestroy(images);
+ XcursorImagesDestroy(images);
+ if (!pages)
return NULL;
- }
// Wrap around animations in the last page.
- cairo_surface_set_user_data(
- frames_head, &fiv_io_key_frame_previous, frames_tail, NULL);
-
- // There is no need to copy data, assign it to the surface.
- static cairo_user_data_key_t key = {};
- cairo_surface_set_user_data(
- pages, &key, images, (cairo_destroy_func_t) XcursorImagesDestroy);
+ frames_head->frame_previous = frames_tail;
// Do not bother doing colour correction, there is no correct rendering.
return pages;
@@ -2649,10 +2640,10 @@ open_xcursor(const char *data, gsize len, GError **error)
#endif // HAVE_XCURSOR --------------------------------------------------------
#ifdef HAVE_LIBHEIF //---------------------------------------------------------
-static cairo_surface_t *
+static FivIoImage *
load_libheif_image(struct heif_image_handle *handle, GError **error)
{
- cairo_surface_t *surface = NULL;
+ FivIoImage *I = NULL;
int has_alpha = heif_image_handle_has_alpha_channel(handle);
int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
if (bit_depth < 0) {
@@ -2675,13 +2666,10 @@ load_libheif_image(struct heif_image_handle *handle, GError **error)
int w = heif_image_get_width(image, heif_channel_interleaved);
int h = heif_image_get_height(image, heif_channel_interleaved);
- surface = cairo_image_surface_create(
+ I = fiv_io_image_new(
has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, w, h);
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
- surface = NULL;
+ if (!I) {
+ set_error(error, "image allocation failure");
goto fail_process;
}
@@ -2689,11 +2677,8 @@ load_libheif_image(struct heif_image_handle *handle, GError **error)
int src_stride = 0;
const uint8_t *src = heif_image_get_plane_readonly(
image, heif_channel_interleaved, &src_stride);
- int dst_stride = cairo_image_surface_get_stride(surface);
- const uint8_t *dst = cairo_image_surface_get_data(surface);
-
for (int y = 0; y < h; y++) {
- uint32_t *dstp = (uint32_t *) (dst + dst_stride * y);
+ uint32_t *dstp = (uint32_t *) (I->data + I->stride * y);
const uint32_t *srcp = (const uint32_t *) (src + src_stride * y);
for (int x = 0; x < w; x++) {
uint32_t rgba = g_ntohl(srcp[x]);
@@ -2703,7 +2688,7 @@ load_libheif_image(struct heif_image_handle *handle, GError **error)
// TODO(p): Test real behaviour on real transparent images.
if (has_alpha && !heif_image_handle_is_premultiplied_alpha(handle))
- fiv_io_premultiply_argb32(surface);
+ fiv_io_premultiply_argb32(I);
heif_item_id exif_id = 0;
if (heif_image_handle_get_list_of_metadata_block_IDs(
@@ -2715,9 +2700,7 @@ load_libheif_image(struct heif_image_handle *handle, GError **error)
g_warning("%s", err.message);
g_free(exif);
} else {
- cairo_surface_set_user_data(surface, &fiv_io_key_exif,
- g_bytes_new_take(exif, exif_len),
- (cairo_destroy_func_t) g_bytes_unref);
+ I->exif = g_bytes_new_take(exif, exif_len);
}
}
@@ -2731,26 +2714,22 @@ load_libheif_image(struct heif_image_handle *handle, GError **error)
g_warning("%s", err.message);
g_free(icc);
} else {
- cairo_surface_set_user_data(surface, &fiv_io_key_icc,
- g_bytes_new_take(icc, icc_len),
- (cairo_destroy_func_t) g_bytes_unref);
+ I->icc = g_bytes_new_take(icc, icc_len);
}
}
- cairo_surface_mark_dirty(surface);
-
fail_process:
heif_image_release(image);
fail_decode:
heif_decoding_options_free(opts);
fail:
- return surface;
+ return I;
}
static void
load_libheif_aux_images(const FivIoOpenContext *ioctx,
- struct heif_image_handle *top, cairo_surface_t **result,
- cairo_surface_t **result_tail)
+ struct heif_image_handle *top,
+ FivIoImage **result, FivIoImage **result_tail)
{
// Include the depth image, we have no special processing for it now.
int filter = LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA;
@@ -2780,14 +2759,14 @@ load_libheif_aux_images(const FivIoOpenContext *ioctx,
g_free(ids);
}
-static cairo_surface_t *
+static FivIoImage *
open_libheif(
const char *data, gsize len, const FivIoOpenContext *ioctx, GError **error)
{
// libheif will throw C++ exceptions on allocation failures.
// The library is generally awful through and through.
struct heif_context *ctx = heif_context_alloc();
- cairo_surface_t *result = NULL, *result_tail = NULL;
+ FivIoImage *result = NULL, *result_tail = NULL;
struct heif_error err;
err = heif_context_read_from_memory_without_copy(ctx, data, len, NULL);
@@ -2819,7 +2798,7 @@ open_libheif(
heif_image_handle_release(handle);
}
if (!result) {
- g_clear_pointer(&result, cairo_surface_destroy);
+ g_clear_pointer(&result, fiv_io_image_unref);
set_error(error, "empty or unsupported image");
}
@@ -2933,7 +2912,7 @@ fiv_io_tiff_warning(G_GNUC_UNUSED thandle_t h,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-static cairo_surface_t *
+static FivIoImage *
load_libtiff_directory(TIFF *tiff, GError **error)
{
char emsg[1024] = "";
@@ -2949,22 +2928,26 @@ load_libtiff_directory(TIFF *tiff, GError **error)
return NULL;
}
- cairo_surface_t *surface = NULL;
+ FivIoImage *I = NULL;
if (image.width > G_MAXINT || image.height >= G_MAXINT ||
G_MAXUINT32 / image.width < image.height) {
set_error(error, "image dimensions too large");
goto fail;
}
- surface = cairo_image_surface_create(image.alpha != EXTRASAMPLE_UNSPECIFIED
+ I = fiv_io_image_new(image.alpha != EXTRASAMPLE_UNSPECIFIED
? CAIRO_FORMAT_ARGB32
: CAIRO_FORMAT_RGB24,
image.width, image.height);
+ if (!I) {
+ set_error(error, "image allocation failure");
+ goto fail;
+ }
image.req_orientation = ORIENTATION_LEFTTOP;
- uint32_t *raster = (uint32_t *) cairo_image_surface_get_data(surface);
+ uint32_t *raster = (uint32_t *) I->data;
if (!TIFFRGBAImageGet(&image, raster, image.width, image.height)) {
- g_clear_pointer(&surface, cairo_surface_destroy);
+ g_clear_pointer(&I, fiv_io_image_unref);
goto fail;
}
@@ -2976,9 +2959,8 @@ load_libtiff_directory(TIFF *tiff, GError **error)
}
// It seems that neither GIMP nor Photoshop use unassociated alpha.
if (image.alpha == EXTRASAMPLE_UNASSALPHA)
- fiv_io_premultiply_argb32(surface);
+ fiv_io_premultiply_argb32(I);
- cairo_surface_mark_dirty(surface);
// XXX: The whole file is essentially an Exif, any ideas?
// TODO(p): TIFF has a number of fields that an ICC profile can be
@@ -2986,33 +2968,27 @@ load_libtiff_directory(TIFF *tiff, GError **error)
// if we don't find an ICC profile.
const uint32_t meta_len = 0;
const void *meta = NULL;
- if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &meta_len, &meta)) {
- cairo_surface_set_user_data(surface, &fiv_io_key_icc,
- g_bytes_new(meta, meta_len), (cairo_destroy_func_t) g_bytes_unref);
- }
- if (TIFFGetField(tiff, TIFFTAG_XMLPACKET, &meta_len, &meta)) {
- cairo_surface_set_user_data(surface, &fiv_io_key_xmp,
- g_bytes_new(meta, meta_len), (cairo_destroy_func_t) g_bytes_unref);
- }
+ if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &meta_len, &meta))
+ I->icc = g_bytes_new(meta, meta_len);
+ if (TIFFGetField(tiff, TIFFTAG_XMLPACKET, &meta_len, &meta))
+ I->xmp = g_bytes_new(meta, meta_len);
// Don't ask. The API is high, alright, I'm just not sure about the level.
uint16_t orientation = 0;
if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientation)) {
if (orientation == 5 || orientation == 7)
- cairo_surface_set_user_data(
- surface, &fiv_io_key_orientation, (void *) (uintptr_t) 5, NULL);
+ I->orientation = 5;
if (orientation == 6 || orientation == 8)
- cairo_surface_set_user_data(
- surface, &fiv_io_key_orientation, (void *) (uintptr_t) 7, NULL);
+ I->orientation = 7;
}
fail:
TIFFRGBAImageEnd(&image);
// TODO(p): It's possible to implement ClipPath easily with Cairo.
- return surface;
+ return I;
}
-static cairo_surface_t *
+static FivIoImage *
open_libtiff(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -3028,7 +3004,7 @@ open_libtiff(
.len = len,
};
- cairo_surface_t *result = NULL, *result_tail = NULL;
+ FivIoImage *result = NULL, *result_tail = NULL;
TIFF *tiff = TIFFClientOpen(ctx->uri, "rm" /* Avoid mmap. */, &h,
fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek,
fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL);
@@ -3049,7 +3025,7 @@ open_libtiff(
fail:
if (h.error) {
- g_clear_pointer(&result, cairo_surface_destroy);
+ g_clear_pointer(&result, fiv_io_image_unref);
set_error(error, h.error);
g_free(h.error);
} else if (!result) {
@@ -3066,18 +3042,19 @@ fail:
#endif // HAVE_LIBTIFF --------------------------------------------------------
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
-static cairo_surface_t *
+static FivIoImage *
load_gdkpixbuf_argb32_unpremultiplied(GdkPixbuf *pixbuf)
{
int w = gdk_pixbuf_get_width(pixbuf);
int h = gdk_pixbuf_get_height(pixbuf);
- cairo_surface_t *surface =
- cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
+ FivIoImage *image = fiv_io_image_new(CAIRO_FORMAT_ARGB32, w, h);
+ if (!image)
+ return NULL;
guint length = 0;
guchar *src = gdk_pixbuf_get_pixels_with_length(pixbuf, &length);
int src_stride = gdk_pixbuf_get_rowstride(pixbuf);
- uint32_t *dst = (uint32_t *) cairo_image_surface_get_data(surface);
+ uint32_t *dst = (uint32_t *) image->data;
for (int y = 0; y < h; y++) {
const guchar *p = src + y * src_stride;
for (int x = 0; x < w; x++) {
@@ -3085,11 +3062,10 @@ load_gdkpixbuf_argb32_unpremultiplied(GdkPixbuf *pixbuf)
p += 4;
}
}
- cairo_surface_mark_dirty(surface);
- return surface;
+ return image;
}
-static cairo_surface_t *
+static FivIoImage *
open_gdkpixbuf(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{
@@ -3107,26 +3083,33 @@ open_gdkpixbuf(
gdk_pixbuf_get_n_channels(pixbuf) == 4 &&
gdk_pixbuf_get_bits_per_sample(pixbuf) == 8;
- cairo_surface_t *surface = NULL;
+ FivIoImage *image = NULL;
if (custom_argb32) {
- surface = load_gdkpixbuf_argb32_unpremultiplied(pixbuf);
- } else {
+ image = load_gdkpixbuf_argb32_unpremultiplied(pixbuf);
+ } else if ((image = fiv_io_image_new(CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width(pixbuf), gdk_pixbuf_get_height(pixbuf)))) {
+ // TODO(p): Ideally, don't go through Cairo at all.
+ cairo_surface_t *surface = fiv_io_image_to_surface_noref(image);
+ cairo_t *cr = cairo_create(surface);
+ cairo_surface_destroy(surface);
+
// Don't depend on GDK being initialized, to speed up thumbnailing
// (calling gdk_cairo_surface_create_from_pixbuf() would).
- cairo_surface_t *dummy =
- cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
- cairo_t *cr = cairo_create(dummy);
- cairo_surface_destroy(dummy);
gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
- (void) cairo_pattern_get_surface(cairo_get_source(cr), &surface);
- cairo_surface_reference(surface);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cr);
+
+ // If the source was opaque, so will be the destination.
+ if (cairo_pattern_get_surface(cairo_get_source(cr), &surface) ==
+ CAIRO_STATUS_SUCCESS) {
+ if (cairo_surface_get_content(surface) == CAIRO_CONTENT_COLOR)
+ image->format = CAIRO_FORMAT_RGB24;
+ }
cairo_destroy(cr);
}
- cairo_status_t surface_status = cairo_surface_status(surface);
- if (surface_status != CAIRO_STATUS_SUCCESS) {
- set_error(error, cairo_status_to_string(surface_status));
- cairo_surface_destroy(surface);
+ if (!image) {
+ set_error(error, "image allocation failure");
g_object_unref(pixbuf);
return NULL;
}
@@ -3135,27 +3118,23 @@ open_gdkpixbuf(
if (orientation && strlen(orientation) == 1) {
int n = *orientation - '0';
if (n >= 1 && n <= 8)
- cairo_surface_set_user_data(
- surface, &fiv_io_key_orientation, (void *) (uintptr_t) n, NULL);
+ image->orientation = n;
}
const char *icc_profile = gdk_pixbuf_get_option(pixbuf, "icc-profile");
if (icc_profile) {
gsize out_len = 0;
guchar *raw = g_base64_decode(icc_profile, &out_len);
- if (raw) {
- cairo_surface_set_user_data(surface, &fiv_io_key_icc,
- g_bytes_new_take(raw, out_len),
- (cairo_destroy_func_t) g_bytes_unref);
- }
+ if (raw)
+ image->icc = g_bytes_new_take(raw, out_len);
}
g_object_unref(pixbuf);
if (custom_argb32)
- fiv_io_profile_argb32_premultiply_page(surface, ctx->screen_profile);
+ fiv_io_profile_argb32_premultiply_page(image, ctx->screen_profile);
else
- surface = fiv_io_profile_finalize(surface, ctx->screen_profile);
- return surface;
+ image = fiv_io_profile_finalize(image, ctx->screen_profile);
+ return image;
}
#endif // HAVE_GDKPIXBUF ------------------------------------------------------
@@ -3178,7 +3157,7 @@ cairo_user_data_key_t fiv_io_key_page_previous;
cairo_user_data_key_t fiv_io_key_render;
-cairo_surface_t *
+FivIoImage *
fiv_io_open(const FivIoOpenContext *ctx, GError **error)
{
// TODO(p): Don't always load everything into memory, test type first,
@@ -3205,47 +3184,47 @@ fiv_io_open(const FivIoOpenContext *ctx, GError **error)
if (!success)
return NULL;
- cairo_surface_t *surface = fiv_io_open_from_data(data, len, ctx, error);
+ FivIoImage *image = fiv_io_open_from_data(data, len, ctx, error);
g_free(data);
- return surface;
+ return image;
}
-cairo_surface_t *
+FivIoImage *
fiv_io_open_from_data(
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error)
{
wuffs_base__slice_u8 prefix =
wuffs_base__make_slice_u8((uint8_t *) data, len);
- cairo_surface_t *surface = NULL;
+ FivIoImage *image = NULL;
switch (wuffs_base__magic_number_guess_fourcc(prefix, true /* closed */)) {
case WUFFS_BASE__FOURCC__BMP:
// Note that BMP can redirect into another format,
// which is so far unsupported here.
- surface = open_wuffs_using(
+ image = open_wuffs_using(
wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder, data, len,
ctx, error);
break;
case WUFFS_BASE__FOURCC__GIF:
- surface = open_wuffs_using(
+ image = open_wuffs_using(
wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder, data, len,
ctx, error);
break;
case WUFFS_BASE__FOURCC__PNG:
- surface = open_wuffs_using(
+ image = open_wuffs_using(
wuffs_png__decoder__alloc_as__wuffs_base__image_decoder, data, len,
ctx, error);
break;
case WUFFS_BASE__FOURCC__TGA:
- surface = open_wuffs_using(
+ image = open_wuffs_using(
wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder, data, len,
ctx, error);
break;
case WUFFS_BASE__FOURCC__JPEG:
- surface = open_libjpeg_turbo(data, len, ctx, error);
+ image = open_libjpeg_turbo(data, len, ctx, error);
break;
case WUFFS_BASE__FOURCC__WEBP:
- surface = open_libwebp(data, len, ctx, error);
+ image = open_libwebp(data, len, ctx, error);
break;
default:
// Try to extract full-size previews from TIFF/EP-compatible raws,
@@ -3253,7 +3232,7 @@ fiv_io_open_from_data(
#ifdef HAVE_LIBRAW // ---------------------------------------------------------
if (!ctx->enhance) {
#endif // HAVE_LIBRAW ---------------------------------------------------------
- if ((surface = open_tiff_ep(data, len, ctx, error)))
+ if ((image = open_tiff_ep(data, len, ctx, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -3261,7 +3240,7 @@ fiv_io_open_from_data(
}
#ifdef HAVE_LIBRAW // ---------------------------------------------------------
}
- if ((surface = open_libraw(data, len, ctx, error)))
+ if ((image = open_libraw(data, len, ctx, error)))
break;
// TODO(p): We should try to pass actual processing errors through,
@@ -3272,7 +3251,7 @@ fiv_io_open_from_data(
}
#endif // HAVE_LIBRAW ---------------------------------------------------------
#ifdef HAVE_RESVG // ----------------------------------------------------------
- if ((surface = open_resvg(data, len, ctx, error)))
+ if ((image = open_resvg(data, len, ctx, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -3280,7 +3259,7 @@ fiv_io_open_from_data(
}
#endif // HAVE_RESVG ----------------------------------------------------------
#ifdef HAVE_LIBRSVG // --------------------------------------------------------
- if ((surface = open_librsvg(data, len, ctx, error)))
+ if ((image = open_librsvg(data, len, ctx, error)))
break;
// XXX: It doesn't look like librsvg can return sensible errors.
@@ -3290,7 +3269,7 @@ fiv_io_open_from_data(
}
#endif // HAVE_LIBRSVG --------------------------------------------------------
#ifdef HAVE_XCURSOR //---------------------------------------------------------
- if ((surface = open_xcursor(data, len, error)))
+ if ((image = open_xcursor(data, len, ctx, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -3298,7 +3277,7 @@ fiv_io_open_from_data(
}
#endif // HAVE_XCURSOR --------------------------------------------------------
#ifdef HAVE_LIBHEIF //---------------------------------------------------------
- if ((surface = open_libheif(data, len, ctx, error)))
+ if ((image = open_libheif(data, len, ctx, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -3307,7 +3286,7 @@ fiv_io_open_from_data(
#endif // HAVE_LIBHEIF --------------------------------------------------------
#ifdef HAVE_LIBTIFF //---------------------------------------------------------
// This needs to be positioned after LibRaw.
- if ((surface = open_libtiff(data, len, ctx, error)))
+ if ((image = open_libtiff(data, len, ctx, error)))
break;
if (error) {
g_debug("%s", (*error)->message);
@@ -3320,9 +3299,9 @@ fiv_io_open_from_data(
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
// This is used as a last resort, the rest above is special-cased.
- if (!surface) {
+ if (!image) {
GError *err = NULL;
- if ((surface = open_gdkpixbuf(data, len, ctx, &err))) {
+ if ((image = open_gdkpixbuf(data, len, ctx, &err))) {
g_clear_error(error);
} else if (!err) {
// Contrary to documentation, this is a possible outcome (libheif).
@@ -3338,17 +3317,13 @@ fiv_io_open_from_data(
// gdk-pixbuf only gives out this single field--cater to its limitations,
// since we'd really like to have it.
// TODO(p): The Exif orientation should be ignored in JPEG-XL at minimum.
- GBytes *exif = NULL;
gsize exif_len = 0;
gconstpointer exif_data = NULL;
- if (surface &&
- (exif = cairo_surface_get_user_data(surface, &fiv_io_key_exif)) &&
- (exif_data = g_bytes_get_data(exif, &exif_len))) {
- cairo_surface_set_user_data(surface, &fiv_io_key_orientation,
- (void *) (uintptr_t) fiv_io_exif_orientation(exif_data, exif_len),
- NULL);
+ if (image && image->exif &&
+ (exif_data = g_bytes_get_data(image->exif, &exif_len))) {
+ image->orientation = fiv_io_exif_orientation(exif_data, exif_len);
}
- return surface;
+ return image;
}
// --- Thumbnail passing utilities ---------------------------------------------
diff --git a/fiv-io.h b/fiv-io.h
index 582b907..9bddedc 100644
--- a/fiv-io.h
+++ b/fiv-io.h
@@ -33,12 +33,98 @@ void fiv_io_profile_free(FivIoProfile self);
// From libwebp, verified to exactly match [x * a / 255].
#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23)
+// --- Metadata ----------------------------------------------------------------
+
+// https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf Table 6
+typedef enum _FivIoOrientation {
+ FivIoOrientationUnknown = 0,
+ FivIoOrientation0 = 1,
+ FivIoOrientationMirror0 = 2,
+ FivIoOrientation180 = 3,
+ FivIoOrientationMirror180 = 4,
+ FivIoOrientationMirror270 = 5,
+ FivIoOrientation90 = 6,
+ FivIoOrientationMirror90 = 7,
+ FivIoOrientation270 = 8
+} FivIoOrientation;
+
+/// Returns a rendering matrix for a surface (user space to pattern space),
+/// and its target dimensions.
+cairo_matrix_t fiv_io_orientation_apply(cairo_surface_t *surface,
+ FivIoOrientation orientation, double *width, double *height);
+void fiv_io_orientation_dimensions(cairo_surface_t *surface,
+ FivIoOrientation orientation, double *width, double *height);
+
+/// Extracts the orientation field from Exif, if there's any.
+FivIoOrientation fiv_io_exif_orientation(const guint8 *exif, gsize len);
+
+/// Save metadata attached by this module in Exiv2 format.
+gboolean fiv_io_save_metadata(
+ cairo_surface_t *page, const char *path, GError **error);
+
// --- Loading -----------------------------------------------------------------
extern const char *fiv_io_supported_media_types[];
gchar **fiv_io_all_supported_media_types(void);
+typedef struct _FivIoRenderClosure {
+ /// The rendering is allowed to fail, returning NULL.
+ cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale);
+ void (*destroy)(struct _FivIoRenderClosure *);
+} FivIoRenderClosure;
+
+typedef struct _FivIoImage {
+ uint8_t *data; ///< Raw image data
+ cairo_format_t format; ///< Data format
+ uint32_t width; ///< Width of the image in pixels
+ uint32_t stride; ///< Row stride in bytes
+ uint32_t height; ///< Height of the image in pixels
+
+ FivIoOrientation orientation; ///< Orientation to use for display
+
+ GBytes *exif; ///< Raw Exif/TIFF segment
+ GBytes *icc; ///< Raw ICC profile data
+ GBytes *xmp; ///< Raw XMP data
+ GBytes *thum; ///< WebP THUM chunk, for our thumbnails
+
+ /// A FivIoRenderClosure for parametrized re-rendering of vector formats.
+ /// This is attached at the page level.
+ FivIoRenderClosure *render;
+
+ /// The first frame of the next page, in a chain.
+ /// There is no wrap-around.
+ struct _FivIoImage *page_next;
+
+ /// The first frame of the previous page, in a chain.
+ /// There is no wrap-around. This is a weak pointer.
+ struct _FivIoImage *page_previous;
+
+ /// The next frame in a sequence, in a chain, pre-composited.
+ /// There is no wrap-around.
+ struct _FivIoImage *frame_next;
+
+ /// The previous frame in a sequence, in a chain, pre-composited.
+ /// This is a weak pointer that wraps around,
+ /// and needn't be present for static images.
+ struct _FivIoImage *frame_previous;
+
+ /// Frame duration in milliseconds.
+ int64_t frame_duration;
+
+ /// How many times to repeat the animation, or zero for +inf.
+ uint64_t loops;
+} FivIoImage;
+
+FivIoImage *fiv_io_image_ref(FivIoImage *image);
+void fiv_io_image_unref(FivIoImage *image);
+
+/// Return a new Cairo image surface referencing the same data as the image,
+/// eating the reference to it.
+cairo_surface_t *fiv_io_image_to_surface(FivIoImage *image);
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
// Userdata are typically attached to all Cairo surfaces in an animation.
/// GBytes with plain Exif/TIFF data.
@@ -51,6 +137,7 @@ extern cairo_user_data_key_t fiv_io_key_icc;
extern cairo_user_data_key_t fiv_io_key_xmp;
/// GBytes with a WebP's THUM chunk, used for our thumbnails.
extern cairo_user_data_key_t fiv_io_key_thum;
+
/// GHashTable with key-value pairs from PNG's tEXt, zTXt, iTXt chunks.
/// Currently only read by fiv_io_open_png_thumbnail().
extern cairo_user_data_key_t fiv_io_key_text;
@@ -74,11 +161,6 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
/// There is no wrap-around. This is a weak pointer.
extern cairo_user_data_key_t fiv_io_key_page_previous;
-typedef struct _FivIoRenderClosure {
- /// The rendering is allowed to fail, returning NULL.
- cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale);
-} FivIoRenderClosure;
-
/// A FivIoRenderClosure for parametrized re-rendering of vector formats.
/// This is attached at the page level.
/// The rendered image will not have this key.
@@ -93,9 +175,10 @@ typedef struct {
GPtrArray *warnings; ///< String vector for non-fatal errors
} FivIoOpenContext;
-cairo_surface_t *fiv_io_open(const FivIoOpenContext *ctx, GError **error);
-cairo_surface_t *fiv_io_open_from_data(
+FivIoImage *fiv_io_open(const FivIoOpenContext *ctx, GError **error);
+FivIoImage *fiv_io_open_from_data(
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error);
+
cairo_surface_t *fiv_io_open_png_thumbnail(const char *path, GError **error);
// --- Thumbnail passing utilities ---------------------------------------------
@@ -118,32 +201,3 @@ unsigned char *fiv_io_encode_webp(
/// If no exact frame is specified, this potentially creates an animation.
gboolean fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame,
FivIoProfile target, const char *path, GError **error);
-
-// --- Metadata ----------------------------------------------------------------
-
-// https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf Table 6
-typedef enum _FivIoOrientation {
- FivIoOrientationUnknown = 0,
- FivIoOrientation0 = 1,
- FivIoOrientationMirror0 = 2,
- FivIoOrientation180 = 3,
- FivIoOrientationMirror180 = 4,
- FivIoOrientationMirror270 = 5,
- FivIoOrientation90 = 6,
- FivIoOrientationMirror90 = 7,
- FivIoOrientation270 = 8
-} FivIoOrientation;
-
-/// Returns a rendering matrix for a surface (user space to pattern space),
-/// and its target dimensions.
-cairo_matrix_t fiv_io_orientation_apply(cairo_surface_t *surface,
- FivIoOrientation orientation, double *width, double *height);
-void fiv_io_orientation_dimensions(cairo_surface_t *surface,
- FivIoOrientation orientation, double *width, double *height);
-
-/// Extracts the orientation field from Exif, if there's any.
-FivIoOrientation fiv_io_exif_orientation(const guint8 *exif, gsize len);
-
-/// Save metadata attached by this module in Exiv2 format.
-gboolean fiv_io_save_metadata(
- cairo_surface_t *page, const char *path, GError **error);
diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c
index 9d58140..b1f0554 100644
--- a/fiv-thumbnail.c
+++ b/fiv-thumbnail.c
@@ -137,8 +137,8 @@ render(GFile *target, GBytes *data, gboolean *color_managed, GError **error)
.warnings = g_ptr_array_new_with_free_func(g_free),
};
- cairo_surface_t *surface = fiv_io_open_from_data(
- g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error);
+ cairo_surface_t *surface = fiv_io_image_to_surface(fiv_io_open_from_data(
+ g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error));
g_free((gchar *) ctx.uri);
g_ptr_array_free(ctx.warnings, TRUE);
if ((*color_managed = !!ctx.screen_profile))
@@ -793,8 +793,8 @@ read_wide_thumbnail(const char *path, const Stat *st, GError **error)
if (!thumbnail_uri)
return NULL;
- cairo_surface_t *surface =
- fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error);
+ cairo_surface_t *surface = fiv_io_image_to_surface(
+ fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error));
g_free(thumbnail_uri);
if (!surface)
return NULL;
diff --git a/fiv-view.c b/fiv-view.c
index 22894ee..43b4055 100644
--- a/fiv-view.c
+++ b/fiv-view.c
@@ -1374,7 +1374,8 @@ open_without_swapping_in(FivView *self, const char *uri)
};
GError *error = NULL;
- cairo_surface_t *surface = fiv_io_open(&ctx, &error);
+ cairo_surface_t *surface =
+ fiv_io_image_to_surface(fiv_io_open(&ctx, &error));
if (error) {
g_ptr_array_add(ctx.warnings, g_strdup(error->message));
g_error_free(error);
diff --git a/tools/benchmark-io.c b/tools/benchmark-io.c
index 3acb140..3dadaae 100644
--- a/tools/benchmark-io.c
+++ b/tools/benchmark-io.c
@@ -41,14 +41,14 @@ one_file(const char *filename)
.warnings = g_ptr_array_new_with_free_func(g_free),
};
- cairo_surface_t *loaded_by_us = fiv_io_open(&ctx, NULL);
+ FivIoImage *loaded_by_us = fiv_io_open(&ctx, NULL);
g_clear_object(&file);
g_free((char *) ctx.uri);
g_ptr_array_free(ctx.warnings, TRUE);
if (!loaded_by_us)
return;
- cairo_surface_destroy(loaded_by_us);
+ fiv_io_image_unref(loaded_by_us);
us = timestamp() - since_us;
double since_pixbuf = timestamp(), pixbuf = 0;