From 074bd4d37f36fa8963e66aa0f8f9de3ff6cadba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Fri, 23 Jun 2023 22:28:09 +0200 Subject: [PATCH] Stop abusing Cairo user data, part 2 With the shift from cairo_surface_t, we've lost our ability to directly render vector surfaces, but it doesn't matter. --- fiv-io.c | 183 ++++++++++++++++-------------------------- fiv-io.h | 133 +++++++++++++------------------ fiv-thumbnail.c | 207 +++++++++++++++++++++++++----------------------- fiv-view.c | 130 +++++++++++++----------------- 4 files changed, 287 insertions(+), 366 deletions(-) diff --git a/fiv-io.c b/fiv-io.c index 9b34fe4..0efee93 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -180,7 +180,7 @@ add_warning(const FivIoOpenContext *ctx, const char *format, ...) // --- Images ------------------------------------------------------------------ -static FivIoImage * +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. @@ -230,6 +230,9 @@ fiv_io_image_finalize(FivIoImage *image) g_bytes_unref(image->xmp); g_bytes_unref(image->thum); + if (image->text) + g_hash_table_unref(image->text); + if (image->render) image->render->destroy(image->render); @@ -245,7 +248,7 @@ fiv_io_image_unref(FivIoImage *self) g_rc_box_release_full(self, (GDestroyNotify) fiv_io_image_finalize); } -static cairo_surface_t * +cairo_surface_t * fiv_io_image_to_surface_noref(const FivIoImage *image) { return cairo_image_surface_create_for_data( @@ -560,12 +563,11 @@ fiv_io_profile_argb32_premultiply( static void fiv_io_profile_argb32_premultiply_page( - cairo_surface_t *page, FivIoProfile target) + FivIoImage *page, FivIoProfile target) { fiv_io_profile_page(page, target, fiv_io_profile_xrgb32); - for (cairo_surface_t *frame = page; frame != NULL; - frame = cairo_surface_get_user_data(frame, &fiv_io_key_frame_next)) + for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next) fiv_io_premultiply_argb32(frame); } @@ -1121,7 +1123,7 @@ pull_metadata_kvp(wuffs_png__decoder *dec, wuffs_base__io_buffer *src, } // An uncomplicated variant of fiv_io_open(), might be up for refactoring. -cairo_surface_t * +FivIoImage * fiv_io_open_png_thumbnail(const char *path, GError **error) { wuffs_png__decoder dec = {}; @@ -1144,7 +1146,7 @@ fiv_io_open_png_thumbnail(const char *path, GError **error) wuffs_base__image_config cfg = {}; wuffs_base__slice_u8 workbuf = {}; - cairo_surface_t *surface = NULL; + FivIoImage *image = NULL; bool success = false; GHashTable *texts = @@ -1186,23 +1188,19 @@ fiv_io_open_png_thumbnail(const char *path, GError **error) } } - surface = cairo_image_surface_create( + image = fiv_io_image_new( wuffs_base__image_config__first_frame_is_opaque(&cfg) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32, 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)); + if (!image) { + set_error(error, "image allocation failure"); goto fail; } wuffs_base__pixel_buffer pb = {}; status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, - wuffs_base__make_slice_u8(cairo_image_surface_get_data(surface), - cairo_image_surface_get_stride(surface) * - 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; @@ -1235,19 +1233,17 @@ fiv_io_open_png_thumbnail(const char *path, GError **error) g_assert(key == NULL); - cairo_surface_mark_dirty(surface); - cairo_surface_set_user_data(surface, &fiv_io_key_text, - g_hash_table_ref(texts), (cairo_destroy_func_t) g_hash_table_unref); + image->text = g_hash_table_ref(texts); success = true; fail: if (!success) - g_clear_pointer(&surface, cairo_surface_destroy); + g_clear_pointer(&image, fiv_io_image_unref); free(workbuf.ptr); g_free(data); g_hash_table_unref(texts); - return surface; + return image; } // --- Multi-Picture Format ---------------------------------------------------- @@ -2309,13 +2305,12 @@ load_resvg_render_internal(FivIoRenderClosureResvg *self, return fiv_io_profile_finalize(image, target); } -static cairo_surface_t * +static FivIoImage * load_resvg_render(FivIoRenderClosure *closure, double scale) { FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure; // TODO(p): Somehow get the target colour management profile. - return fiv_io_image_to_surface( - load_resvg_render_internal(self, scale, NULL, NULL)); + return load_resvg_render_internal(self, scale, NULL, NULL); } static const char * @@ -2433,13 +2428,12 @@ load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale, return fiv_io_profile_finalize(image, target); } -static cairo_surface_t * +static FivIoImage * load_librsvg_render(FivIoRenderClosure *closure, double scale) { FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure; // TODO(p): Somehow get the target colour management profile. - return fiv_io_image_to_surface( - load_librsvg_render_internal(self, scale, NULL, NULL)); + return load_librsvg_render_internal(self, scale, NULL, NULL); } static FivIoImage * @@ -3139,24 +3133,6 @@ open_gdkpixbuf( #endif // HAVE_GDKPIXBUF ------------------------------------------------------ -// TODO(p): Check that all cairo_surface_set_user_data() calls succeed. -cairo_user_data_key_t fiv_io_key_exif; -cairo_user_data_key_t fiv_io_key_orientation; -cairo_user_data_key_t fiv_io_key_icc; -cairo_user_data_key_t fiv_io_key_xmp; -cairo_user_data_key_t fiv_io_key_thum; -cairo_user_data_key_t fiv_io_key_text; - -cairo_user_data_key_t fiv_io_key_frame_next; -cairo_user_data_key_t fiv_io_key_frame_previous; -cairo_user_data_key_t fiv_io_key_frame_duration; -cairo_user_data_key_t fiv_io_key_loops; - -cairo_user_data_key_t fiv_io_key_page_next; -cairo_user_data_key_t fiv_io_key_page_previous; - -cairo_user_data_key_t fiv_io_key_render; - FivIoImage * fiv_io_open(const FivIoOpenContext *ctx, GError **error) { @@ -3479,23 +3455,26 @@ fiv_io_serialize_for_search(cairo_surface_t *surface, GError **error) unsigned char * fiv_io_encode_webp( - cairo_surface_t *surface, const WebPConfig *config, size_t *len) + FivIoImage *image, const WebPConfig *config, size_t *len) { - cairo_format_t format = cairo_image_surface_get_format(surface); - int w = cairo_image_surface_get_width(surface); - int h = cairo_image_surface_get_height(surface); - if (format != CAIRO_FORMAT_ARGB32 && - format != CAIRO_FORMAT_RGB24) { - cairo_surface_t *converted = - cairo_image_surface_create((format = CAIRO_FORMAT_ARGB32), w, h); - cairo_t *cr = cairo_create(converted); + if (image->format != CAIRO_FORMAT_ARGB32 && + image->format != CAIRO_FORMAT_RGB24) { + FivIoImage *converted = + fiv_io_image_new(CAIRO_FORMAT_ARGB32, image->width, image->height); + + cairo_surface_t *surface = fiv_io_image_to_surface_noref(converted); + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + + surface = fiv_io_image_to_surface_noref(image); cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_destroy(cr); - surface = converted; + image = converted; } else { - surface = cairo_surface_reference(surface); + image = fiv_io_image_ref(image); } WebPMemoryWriter writer = {}; @@ -3505,27 +3484,26 @@ fiv_io_encode_webp( goto fail; picture.use_argb = true; - picture.width = w; - picture.height = h; + picture.width = image->width; + picture.height = image->height; if (!WebPPictureAlloc(&picture)) goto fail; // Cairo uses a similar internal format, so we should be able to // copy it over and fix up the minor differences. // This is written to be easy to follow rather than fast. - int stride = cairo_image_surface_get_stride(surface); - if (picture.argb_stride != w || - picture.argb_stride * (int) sizeof *picture.argb != stride || - INT_MAX / picture.argb_stride < h) + if (picture.argb_stride != (int) image->width || + picture.argb_stride * sizeof *picture.argb != image->stride || + UINT32_MAX / picture.argb_stride < image->height) goto fail_compatibility; uint32_t *argb = - memcpy(picture.argb, cairo_image_surface_get_data(surface), stride * h); - if (format == CAIRO_FORMAT_ARGB32) - for (int i = h * picture.argb_stride; i-- > 0; argb++) + memcpy(picture.argb, image->data, image->stride * image->height); + if (image->format == CAIRO_FORMAT_ARGB32) + for (int i = image->height * picture.argb_stride; i-- > 0; argb++) *argb = wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul(*argb); else - for (int i = h * picture.argb_stride; i-- > 0; argb++) + for (int i = image->height * picture.argb_stride; i-- > 0; argb++) *argb |= 0xFF000000; // TODO(p): Prevent or propagate VP8_ENC_ERROR_BAD_DIMENSION. @@ -3537,13 +3515,13 @@ fiv_io_encode_webp( fail_compatibility: WebPPictureFree(&picture); fail: - cairo_surface_destroy(surface); + fiv_io_image_unref(image); *len = writer.size; return writer.mem; } static WebPData -encode_lossless_webp(cairo_surface_t *surface) +encode_lossless_webp(FivIoImage *image) { WebPData bitstream = {}; WebPConfig config = {}; @@ -3554,12 +3532,12 @@ encode_lossless_webp(cairo_surface_t *surface) if (!WebPValidateConfig(&config)) return bitstream; - bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size); + bitstream.bytes = fiv_io_encode_webp(image, &config, &bitstream.size); return bitstream; } static gboolean -encode_webp_image(WebPMux *mux, cairo_surface_t *frame) +encode_webp_image(WebPMux *mux, FivIoImage *frame) { WebPData bitstream = encode_lossless_webp(frame); gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK; @@ -3568,15 +3546,13 @@ encode_webp_image(WebPMux *mux, cairo_surface_t *frame) } static gboolean -encode_webp_animation(WebPMux *mux, cairo_surface_t *page) +encode_webp_animation(WebPMux *mux, FivIoImage *page) { gboolean ok = TRUE; - for (cairo_surface_t *frame = page; ok && frame; frame = - cairo_surface_get_user_data(frame, &fiv_io_key_frame_next)) { + for (FivIoImage *frame = page; ok && frame; frame = frame->frame_next) { WebPMuxFrameInfo info = { .bitstream = encode_lossless_webp(frame), - .duration = (intptr_t) cairo_surface_get_user_data( - frame, &fiv_io_key_frame_duration), + .duration = frame->frame_duration, .id = WEBP_CHUNK_ANMF, .dispose_method = WEBP_MUX_DISPOSE_NONE, .blend_method = WEBP_MUX_NO_BLEND, @@ -3586,8 +3562,7 @@ encode_webp_animation(WebPMux *mux, cairo_surface_t *page) } WebPMuxAnimParams params = { .bgcolor = 0x00000000, // BGRA, curiously. - .loop_count = (uintptr_t) - cairo_surface_get_user_data(page, &fiv_io_key_loops), + .loop_count = page->loops, }; return ok && WebPMuxSetAnimationParams(mux, ¶ms) == WEBP_MUX_OK; } @@ -3605,7 +3580,7 @@ set_metadata(WebPMux *mux, const char *fourcc, GBytes *data) } gboolean -fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, FivIoProfile target, +fiv_io_save(FivIoImage *page, FivIoImage *frame, FivIoProfile target, const char *path, GError **error) { g_return_val_if_fail(page != NULL, FALSE); @@ -3615,17 +3590,14 @@ fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, FivIoProfile target, WebPMux *mux = WebPMuxNew(); if (frame) ok = encode_webp_image(mux, frame); - else if (!cairo_surface_get_user_data(page, &fiv_io_key_frame_next)) + else if (!page->frame_next) ok = encode_webp_image(mux, page); else ok = encode_webp_animation(mux, page); - ok = ok && set_metadata(mux, "EXIF", - cairo_surface_get_user_data(page, &fiv_io_key_exif)); - ok = ok && set_metadata(mux, "ICCP", - cairo_surface_get_user_data(page, &fiv_io_key_icc)); - ok = ok && set_metadata(mux, "XMP ", - cairo_surface_get_user_data(page, &fiv_io_key_xmp)); + ok = ok && set_metadata(mux, "EXIF", page->exif); + ok = ok && set_metadata(mux, "ICCP", page->icc); + ok = ok && set_metadata(mux, "XMP ", page->xmp); GBytes *iccp = NULL; if (ok && target && (iccp = fiv_io_profile_to_bytes(target))) @@ -3650,43 +3622,28 @@ fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, FivIoProfile target, // --- Metadata ---------------------------------------------------------------- void -fiv_io_orientation_dimensions(cairo_surface_t *surface, - FivIoOrientation orientation, double *w, double *h) +fiv_io_orientation_dimensions( + const FivIoImage *image, FivIoOrientation orientation, double *w, double *h) { - cairo_rectangle_t extents = {}; - switch (cairo_surface_get_type(surface)) { - case CAIRO_SURFACE_TYPE_IMAGE: - extents.width = cairo_image_surface_get_width(surface); - extents.height = cairo_image_surface_get_height(surface); - break; - case CAIRO_SURFACE_TYPE_RECORDING: - if (!cairo_recording_surface_get_extents(surface, &extents)) - cairo_recording_surface_ink_extents(surface, - &extents.x, &extents.y, &extents.width, &extents.height); - break; - default: - g_assert_not_reached(); - } - switch (orientation) { case FivIoOrientation90: case FivIoOrientationMirror90: case FivIoOrientation270: case FivIoOrientationMirror270: - *w = extents.height; - *h = extents.width; + *w = image->height; + *h = image->width; break; default: - *w = extents.width; - *h = extents.height; + *w = image->width; + *h = image->height; } } cairo_matrix_t -fiv_io_orientation_apply(cairo_surface_t *surface, +fiv_io_orientation_apply(const FivIoImage *image, FivIoOrientation orientation, double *width, double *height) { - fiv_io_orientation_dimensions(surface, orientation, width, height); + fiv_io_orientation_dimensions(image, orientation, width, height); cairo_matrix_t matrix = {}; cairo_matrix_init_identity(&matrix); @@ -3749,7 +3706,7 @@ fiv_io_exif_orientation(const guint8 *tiff, gsize len) } gboolean -fiv_io_save_metadata(cairo_surface_t *page, const char *path, GError **error) +fiv_io_save_metadata(const FivIoImage *page, const char *path, GError **error) { g_return_val_if_fail(page != NULL, FALSE); @@ -3764,14 +3721,12 @@ fiv_io_save_metadata(cairo_surface_t *page, const char *path, GError **error) // (standalone) with trailing nonsense. fprintf(fp, "\xFF\001Exiv2"); - GBytes *data = NULL; gsize len = 0; gconstpointer p = NULL; // Adobe XMP Specification Part 3: Storage in Files, 2020/1, 1.1.3 // I don't care if Exiv2 supports it this way. - if ((data = cairo_surface_get_user_data(page, &fiv_io_key_exif)) && - (p = g_bytes_get_data(data, &len))) { + if (page->exif && (p = g_bytes_get_data(page->exif, &len))) { while (len) { gsize chunk = MIN(len, 0xFFFF - 2 - 6); uint8_t header[10] = "\xFF\xE1\000\000Exif\000\000"; @@ -3787,8 +3742,7 @@ fiv_io_save_metadata(cairo_surface_t *page, const char *path, GError **error) } // https://www.color.org/specification/ICC1v43_2010-12.pdf B.4 - if ((data = cairo_surface_get_user_data(page, &fiv_io_key_icc)) && - (p = g_bytes_get_data(data, &len))) { + if (page->icc && (p = g_bytes_get_data(page->icc, &len))) { gsize limit = 0xFFFF - 2 - 12; uint8_t current = 0, total = (len + limit - 1) / limit; while (len) { @@ -3810,8 +3764,7 @@ fiv_io_save_metadata(cairo_surface_t *page, const char *path, GError **error) // Adobe XMP Specification Part 3: Storage in Files, 2020/1, 1.1.3 // If the main segment overflows, then it's a sign of bad luck, // because 1.1.3.1 is way too complex. - if ((data = cairo_surface_get_user_data(page, &fiv_io_key_xmp)) && - (p = g_bytes_get_data(data, &len))) { + if (page->xmp && (p = g_bytes_get_data(page->xmp, &len))) { while (len) { gsize chunk = MIN(len, 0xFFFF - 2 - 29); uint8_t header[33] = diff --git a/fiv-io.h b/fiv-io.h index 9bddedc..224aec0 100644 --- a/fiv-io.h +++ b/fiv-io.h @@ -33,10 +33,18 @@ 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 ---------------------------------------------------------------- +// --- Loading ----------------------------------------------------------------- + +extern const char *fiv_io_supported_media_types[]; + +gchar **fiv_io_all_supported_media_types(void); + +typedef enum _FivIoOrientation FivIoOrientation; +typedef struct _FivIoRenderClosure FivIoRenderClosure; +typedef struct _FivIoImage FivIoImage; // https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf Table 6 -typedef enum _FivIoOrientation { +enum _FivIoOrientation { FivIoOrientationUnknown = 0, FivIoOrientation0 = 1, FivIoOrientationMirror0 = 2, @@ -46,35 +54,17 @@ typedef enum _FivIoOrientation { 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 { +struct _FivIoRenderClosure { /// The rendering is allowed to fail, returning NULL. - cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale); - void (*destroy)(struct _FivIoRenderClosure *); -} FivIoRenderClosure; + FivIoImage *(*render)(FivIoRenderClosure *, double scale); + void (*destroy)(FivIoRenderClosure *); +}; -typedef struct _FivIoImage { +// Metadata are typically attached to all Cairo surfaces in an animation. + +struct _FivIoImage { uint8_t *data; ///< Raw image data cairo_format_t format; ///< Data format uint32_t width; ///< Width of the image in pixels @@ -88,84 +78,55 @@ typedef struct _FivIoImage { GBytes *xmp; ///< Raw XMP data GBytes *thum; ///< WebP THUM chunk, for our thumbnails + /// GHashTable with key-value pairs from PNG's tEXt, zTXt, iTXt chunks. + /// Currently only read by fiv_io_open_png_thumbnail(). + GHashTable *text; + /// 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; + 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; + FivIoImage *page_previous; /// The next frame in a sequence, in a chain, pre-composited. /// There is no wrap-around. - struct _FivIoImage *frame_next; + 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; + 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); +/// Analogous to cairo_image_surface_create(). May return NULL. +FivIoImage *fiv_io_image_new( + cairo_format_t format, uint32_t width, uint32_t height); + /// 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); +/// Return a new Cairo image surface referencing the same data as the image, +/// without eating the image's reference. +cairo_surface_t *fiv_io_image_to_surface_noref(const FivIoImage *image); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// Userdata are typically attached to all Cairo surfaces in an animation. - -/// GBytes with plain Exif/TIFF data. -extern cairo_user_data_key_t fiv_io_key_exif; -/// FivIoOrientation, as a uintptr_t. -extern cairo_user_data_key_t fiv_io_key_orientation; -/// GBytes with plain ICC profile data. -extern cairo_user_data_key_t fiv_io_key_icc; -/// GBytes with plain XMP data. -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; - -/// The next frame in a sequence, as a surface, in a chain, pre-composited. -/// There is no wrap-around. -extern cairo_user_data_key_t fiv_io_key_frame_next; -/// The previous frame in a sequence, as a surface, in a chain, pre-composited. -/// This is a weak pointer that wraps around, and needn't be present -/// for static images. -extern cairo_user_data_key_t fiv_io_key_frame_previous; -/// Frame duration in milliseconds as an intptr_t. -extern cairo_user_data_key_t fiv_io_key_frame_duration; -/// How many times to repeat the animation, or zero for +inf, as a uintptr_t. -extern cairo_user_data_key_t fiv_io_key_loops; - -/// The first frame of the next page, as a surface, in a chain. -/// There is no wrap-around. -extern cairo_user_data_key_t fiv_io_key_page_next; -/// The first frame of the previous page, as a surface, in a chain. -/// There is no wrap-around. This is a weak pointer. -extern cairo_user_data_key_t fiv_io_key_page_previous; - -/// A FivIoRenderClosure for parametrized re-rendering of vector formats. -/// This is attached at the page level. -/// The rendered image will not have this key. -extern cairo_user_data_key_t fiv_io_key_render; - typedef struct { const char *uri; ///< Source URI FivIoProfile screen_profile; ///< Target colour space or NULL @@ -179,7 +140,23 @@ 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); +FivIoImage *fiv_io_open_png_thumbnail(const char *path, GError **error); + +// --- Metadata ---------------------------------------------------------------- + +/// Returns a rendering matrix for an image (user space to pattern space), +/// and its target dimensions. +cairo_matrix_t fiv_io_orientation_apply(const FivIoImage *image, + FivIoOrientation orientation, double *width, double *height); +void fiv_io_orientation_dimensions(const FivIoImage *image, + 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( + const FivIoImage *page, const char *path, GError **error); // --- Thumbnail passing utilities --------------------------------------------- @@ -192,12 +169,12 @@ GBytes *fiv_io_serialize_for_search(cairo_surface_t *surface, GError **error); // --- Export ------------------------------------------------------------------ -/// Encodes a Cairo surface as a WebP bitstream, following the configuration. +/// Encodes an image as a WebP bitstream, following the configuration. /// The result needs to be freed using WebPFree/WebPDataClear(). unsigned char *fiv_io_encode_webp( - cairo_surface_t *surface, const WebPConfig *config, size_t *len); + FivIoImage *image, const WebPConfig *config, size_t *len); /// Saves the page as a lossless WebP still picture or animation. /// If no exact frame is specified, this potentially creates an animation. -gboolean fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, +gboolean fiv_io_save(FivIoImage *page, FivIoImage *frame, FivIoProfile target, const char *path, GError **error); diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index b1f0554..26d21f1 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -125,7 +125,7 @@ might_be_a_thumbnail(const char *path_or_uri) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static cairo_surface_t * +static FivIoImage * render(GFile *target, GBytes *data, gboolean *color_managed, GError **error) { FivIoOpenContext ctx = { @@ -137,23 +137,22 @@ 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_image_to_surface(fiv_io_open_from_data( - g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error)); + FivIoImage *image = 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)) fiv_io_profile_free(ctx.screen_profile); g_bytes_unref(data); - return surface; + return image; } // In principle similar to rescale_thumbnail() from fiv-browser.c. -static cairo_surface_t * -adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) +static FivIoImage * +adjust_thumbnail(FivIoImage *thumbnail, double row_height) { // Hardcode orientation. - FivIoOrientation orientation = (uintptr_t) cairo_surface_get_user_data( - thumbnail, &fiv_io_key_orientation); + FivIoOrientation orientation = thumbnail->orientation; double w = 0, h = 0; cairo_matrix_t matrix = @@ -170,33 +169,40 @@ adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) } // Vector images should not have orientation, this should handle them all. - FivIoRenderClosure *closure = - cairo_surface_get_user_data(thumbnail, &fiv_io_key_render); + FivIoRenderClosure *closure = thumbnail->render; if (closure && orientation <= FivIoOrientation0) { // This API doesn't accept non-uniform scaling; prefer a vertical fit. - cairo_surface_t *scaled = closure->render(closure, scale_y); + FivIoImage *scaled = closure->render(closure, scale_y); if (scaled) return scaled; } - // This will be CAIRO_FORMAT_INVALID with non-image surfaces, which is fine. - cairo_format_t format = cairo_image_surface_get_format(thumbnail); - if (format != CAIRO_FORMAT_INVALID && - orientation <= FivIoOrientation0 && scale_x == 1 && scale_y == 1) - return cairo_surface_reference(thumbnail); + if (orientation <= FivIoOrientation0 && scale_x == 1 && scale_y == 1) + return fiv_io_image_ref(thumbnail); + cairo_format_t format = thumbnail->format; int projected_width = round(scale_x * w); int projected_height = round(scale_y * h); - cairo_surface_t *scaled = cairo_image_surface_create( + FivIoImage *scaled = fiv_io_image_new( (format == CAIRO_FORMAT_RGB24 || format == CAIRO_FORMAT_RGB30) ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32, projected_width, projected_height); + if (!scaled) { + g_warning("image allocation failure"); + return fiv_io_image_ref(thumbnail); + } + + cairo_surface_t *surface = fiv_io_image_to_surface_noref(scaled); + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); - cairo_t *cr = cairo_create(scaled); cairo_scale(cr, scale_x, scale_y); - cairo_set_source_surface(cr, thumbnail, 0, 0); + surface = fiv_io_image_to_surface_noref(thumbnail); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); + cairo_pattern_t *pattern = cairo_get_source(cr); // CAIRO_FILTER_BEST, for some reason, works bad with CAIRO_FORMAT_RGB30. cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD); @@ -208,9 +214,7 @@ adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) // Note that this doesn't get triggered with oversize input surfaces, // even though nothing will be rendered. - if (cairo_surface_status(thumbnail) != CAIRO_STATUS_SUCCESS || - cairo_surface_status(scaled) != CAIRO_STATUS_SUCCESS || - cairo_pattern_status(pattern) != CAIRO_STATUS_SUCCESS || + if (cairo_pattern_status(pattern) != CAIRO_STATUS_SUCCESS || cairo_status(cr) != CAIRO_STATUS_SUCCESS) g_warning("thumbnail scaling failed"); @@ -218,27 +222,32 @@ adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) return scaled; } -static cairo_surface_t * -orient_thumbnail(cairo_surface_t *surface) +static FivIoImage * +orient_thumbnail(FivIoImage *image) { - int orientation = (intptr_t) cairo_surface_get_user_data( - surface, &fiv_io_key_orientation); - if (orientation <= FivIoOrientation0) - return surface; + if (image->orientation <= FivIoOrientation0) + return image; double w = 0, h = 0; cairo_matrix_t matrix = - fiv_io_orientation_apply(surface, orientation, &w, &h); - cairo_surface_t *oriented = - cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h); + fiv_io_orientation_apply(image, image->orientation, &w, &h); + FivIoImage *oriented = fiv_io_image_new(image->format, w, h); + if (!oriented) { + g_warning("image allocation failure"); + return image; + } - cairo_t *cr = cairo_create(oriented); + cairo_surface_t *surface = fiv_io_image_to_surface_noref(oriented); + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + + surface = fiv_io_image_to_surface(image); cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); cairo_destroy(cr); - cairo_surface_destroy(surface); return oriented; } @@ -390,7 +399,7 @@ extract_libraw_unflip(int flip) } } -static cairo_surface_t * +static FivIoImage * extract_libraw_bitmap(libraw_processed_image_t *image, int flip, GError **error) { // Anything else is extremely rare. @@ -399,24 +408,26 @@ extract_libraw_bitmap(libraw_processed_image_t *image, int flip, GError **error) return NULL; } - cairo_surface_t *surface = cairo_image_surface_create( + FivIoImage *I = fiv_io_image_new( CAIRO_FORMAT_RGB24, image->width, image->height); - guint32 *out = (guint32 *) cairo_image_surface_get_data(surface); + if (!I) { + set_error(error, "image allocation failure"); + return NULL; + } + + guint32 *out = (guint32 *) I->data; const unsigned char *in = image->data; for (guint64 i = 0; i < image->width * image->height; in += 3) out[i++] = in[0] << 16 | in[1] << 8 | in[2]; - cairo_surface_mark_dirty(surface); - FivIoOrientation orient = extract_libraw_unflip(flip); - cairo_surface_set_user_data( - surface, &fiv_io_key_orientation, (void *) (intptr_t) orient, NULL); - return surface; + I->orientation = extract_libraw_unflip(flip); + return I; } -static cairo_surface_t * +static FivIoImage * extract_libraw(GFile *target, GMappedFile *mf, GError **error) { - cairo_surface_t *surface = NULL; + FivIoImage *I = NULL; libraw_data_t *iprc = libraw_init( LIBRAW_OPIONS_NO_MEMERR_CALLBACK | LIBRAW_OPIONS_NO_DATAERR_CALLBACK); if (!iprc) { @@ -467,11 +478,11 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) switch (image->type) { gboolean dummy; case LIBRAW_IMAGE_JPEG: - surface = render( + I = render( target, g_bytes_new(image->data, image->data_size), &dummy, error); break; case LIBRAW_IMAGE_BITMAP: - surface = extract_libraw_bitmap(image, flip, error); + I = extract_libraw_bitmap(image, flip, error); break; default: set_error(error, "unsupported embedded thumbnail"); @@ -480,7 +491,7 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error) libraw_dcraw_clear_mem(image); fail: libraw_close(iprc); - return surface; + return I; } #endif // HAVE_LIBRAW @@ -506,30 +517,30 @@ fiv_thumbnail_extract(GFile *target, FivThumbnailSize max_size, GError **error) return NULL; } - cairo_surface_t *surface = NULL; + FivIoImage *image = NULL; #ifdef HAVE_LIBRAW - surface = extract_libraw(target, mf, error); + image = extract_libraw(target, mf, error); #else // ! HAVE_LIBRAW // TODO(p): Implement our own thumbnail extractors. set_error(error, "unsupported file"); #endif // ! HAVE_LIBRAW g_mapped_file_unref(mf); - if (!surface) + if (!image) return NULL; if (max_size < FIV_THUMBNAIL_SIZE_MIN || max_size > FIV_THUMBNAIL_SIZE_MAX) - return orient_thumbnail(surface); + return fiv_io_image_to_surface(orient_thumbnail(image)); - cairo_surface_t *result = - adjust_thumbnail(surface, fiv_thumbnail_sizes[max_size].size); - cairo_surface_destroy(surface); - return result; + FivIoImage *result = + adjust_thumbnail(image, fiv_thumbnail_sizes[max_size].size); + fiv_io_image_unref(image); + return fiv_io_image_to_surface(result); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static WebPData -encode_thumbnail(cairo_surface_t *surface) +encode_thumbnail(FivIoImage *image) { WebPData bitstream = {}; WebPConfig config = {}; @@ -541,12 +552,12 @@ encode_thumbnail(cairo_surface_t *surface) if (!WebPValidateConfig(&config)) return bitstream; - bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size); + bitstream.bytes = fiv_io_encode_webp(image, &config, &bitstream.size); return bitstream; } static void -save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum) +save_thumbnail(FivIoImage *thumbnail, const char *path, GString *thum) { WebPMux *mux = WebPMuxNew(); WebPData bitstream = encode_thumbnail(thumbnail); @@ -602,15 +613,15 @@ fiv_thumbnail_produce_for_search( return NULL; gboolean color_managed = FALSE; - cairo_surface_t *surface = render(target, data, &color_managed, error); - if (!surface) + FivIoImage *image = render(target, data, &color_managed, error); + if (!image) return NULL; // TODO(p): Might want to keep this a square. - cairo_surface_t *result = - adjust_thumbnail(surface, fiv_thumbnail_sizes[max_size].size); - cairo_surface_destroy(surface); - return result; + FivIoImage *result = + adjust_thumbnail(image, fiv_thumbnail_sizes[max_size].size); + fiv_io_image_unref(image); + return fiv_io_image_to_surface(result); } static cairo_surface_t * @@ -638,14 +649,14 @@ produce_fallback(GFile *target, FivThumbnailSize size, GError **error) return NULL; gboolean color_managed = FALSE; - cairo_surface_t *surface = render(target, data, &color_managed, error); - if (!surface) + FivIoImage *image = render(target, data, &color_managed, error); + if (!image) return NULL; - cairo_surface_t *result = - adjust_thumbnail(surface, fiv_thumbnail_sizes[size].size); - cairo_surface_destroy(surface); - return result; + FivIoImage *result = + adjust_thumbnail(image, fiv_thumbnail_sizes[size].size); + fiv_io_image_unref(image); + return fiv_io_image_to_surface(result); } cairo_surface_t * @@ -690,10 +701,10 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) } gboolean color_managed = FALSE; - cairo_surface_t *surface = + FivIoImage *image = render(target, g_mapped_file_get_bytes(mf), &color_managed, error); g_mapped_file_unref(mf); - if (!surface) + if (!image) return NULL; // Boilerplate copied from fiv_thumbnail_lookup(). @@ -709,12 +720,10 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) g_string_append_printf( thum, "%s%c%llu%c", THUMB_SIZE, 0, (unsigned long long) filesize, 0); - if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE) { - g_string_append_printf(thum, "%s%c%d%c", THUMB_IMAGE_WIDTH, 0, - cairo_image_surface_get_width(surface), 0); - g_string_append_printf(thum, "%s%c%d%c", THUMB_IMAGE_HEIGHT, 0, - cairo_image_surface_get_height(surface), 0); - } + g_string_append_printf(thum, "%s%c%u%c", THUMB_IMAGE_WIDTH, 0, + (unsigned) image->width, 0); + g_string_append_printf(thum, "%s%c%u%c", THUMB_IMAGE_HEIGHT, 0, + (unsigned) image->height, 0); // Without a CMM, no conversion is attempted. if (color_managed) { @@ -722,19 +731,19 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) thum, "%s%c%s%c", THUMB_COLORSPACE, 0, THUMB_COLORSPACE_SRGB, 0); } - cairo_surface_t *max_size_surface = NULL; + FivIoImage *max_size_image = NULL; for (int use = max_size; use >= FIV_THUMBNAIL_SIZE_MIN; use--) { - cairo_surface_t *scaled = - adjust_thumbnail(surface, fiv_thumbnail_sizes[use].size); + FivIoImage *scaled = + adjust_thumbnail(image, fiv_thumbnail_sizes[use].size); gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir, fiv_thumbnail_sizes[use].thumbnail_spec_name, sum); save_thumbnail(scaled, path, thum); g_free(path); - if (!max_size_surface) - max_size_surface = scaled; + if (!max_size_image) + max_size_image = scaled; else - cairo_surface_destroy(scaled); + fiv_io_image_unref(scaled); } g_string_free(thum, TRUE); @@ -742,8 +751,8 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) g_free(thumbnails_dir); g_free(sum); g_free(uri); - cairo_surface_destroy(surface); - return max_size_surface; + fiv_io_image_unref(image); + return fiv_io_image_to_surface(max_size_image); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -793,23 +802,23 @@ read_wide_thumbnail(const char *path, const Stat *st, GError **error) if (!thumbnail_uri) return NULL; - cairo_surface_t *surface = fiv_io_image_to_surface( - fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error)); + FivIoImage *image = + fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error); g_free(thumbnail_uri); - if (!surface) + if (!image) return NULL; bool sRGB = false; - GBytes *thum = cairo_surface_get_user_data(surface, &fiv_io_key_thum); - if (!thum) { + if (!image->thum) { g_clear_error(error); set_error(error, "not a thumbnail"); - } else if (!check_wide_thumbnail_texts(thum, st, &sRGB)) { + } else if (!check_wide_thumbnail_texts(image->thum, st, &sRGB)) { g_clear_error(error); set_error(error, "mismatch"); } else { // TODO(p): Add a function or a non-valueless define to check // for CMM presence, then remove this ifdef. + cairo_surface_t *surface = fiv_io_image_to_surface(image); #ifdef HAVE_LCMS2 if (!sRGB) mark_thumbnail_lq(surface); @@ -817,21 +826,21 @@ read_wide_thumbnail(const char *path, const Stat *st, GError **error) return surface; } - cairo_surface_destroy(surface); + fiv_io_image_unref(image); return NULL; } static cairo_surface_t * read_png_thumbnail(const char *path, const Stat *st, GError **error) { - cairo_surface_t *surface = fiv_io_open_png_thumbnail(path, error); - if (!surface) + FivIoImage *image = fiv_io_open_png_thumbnail(path, error); + if (!image) return NULL; - GHashTable *texts = cairo_surface_get_user_data(surface, &fiv_io_key_text); + GHashTable *texts = image->text; if (!texts) { set_error(error, "not a thumbnail"); - cairo_surface_destroy(surface); + fiv_io_image_unref(image); return NULL; } @@ -843,16 +852,16 @@ read_png_thumbnail(const char *path, const Stat *st, GError **error) if (!text_uri || strcmp(text_uri, st->uri) || !text_mtime || atol(text_mtime) != st->mtime) { set_error(error, "mismatch or not a thumbnail"); - cairo_surface_destroy(surface); + fiv_io_image_unref(image); return NULL; } if (text_size && strtoull(text_size, NULL, 10) != st->size) { set_error(error, "file size mismatch"); - cairo_surface_destroy(surface); + fiv_io_image_unref(image); return NULL; } - return surface; + return fiv_io_image_to_surface(image); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/fiv-view.c b/fiv-view.c index 43b4055..4005e40 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -63,10 +63,10 @@ struct _FivView { gchar *messages; ///< Image load information gchar *uri; ///< Path to the current image (if any) - cairo_surface_t *image; ///< The loaded image (sequence) - cairo_surface_t *page; ///< Current page within image, weak - cairo_surface_t *page_scaled; ///< Current page within image, scaled - cairo_surface_t *frame; ///< Current frame within page, weak + FivIoImage *image; ///< The loaded image (sequence) + FivIoImage *page; ///< Current page within image, weak + FivIoImage *page_scaled; ///< Current page within image, scaled + FivIoImage *frame; ///< Current frame within page, weak FivIoOrientation orientation; ///< Current page orientation bool enable_cms : 1; ///< Smooth scaling toggle bool filter : 1; ///< Smooth scaling toggle @@ -77,7 +77,7 @@ struct _FivView { double scale; ///< Scaling factor double drag_start[2]; ///< Adjustment values for drag origin - cairo_surface_t *enhance_swap; ///< Quick swap in/out + FivIoImage *enhance_swap; ///< Quick swap in/out FivIoProfile screen_cms_profile; ///< Target colour profile for widget int remaining_loops; ///< Greater than zero if limited @@ -234,9 +234,9 @@ fiv_view_finalize(GObject *gobject) { FivView *self = FIV_VIEW(gobject); g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free); - g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); - g_clear_pointer(&self->image, cairo_surface_destroy); - g_clear_pointer(&self->page_scaled, cairo_surface_destroy); + g_clear_pointer(&self->enhance_swap, fiv_io_image_unref); + g_clear_pointer(&self->image, fiv_io_image_unref); + g_clear_pointer(&self->page_scaled, fiv_io_image_unref); g_free(self->uri); g_free(self->messages); @@ -283,15 +283,13 @@ fiv_view_get_property( g_value_set_boolean(value, !!self->image); break; case PROP_CAN_ANIMATE: - g_value_set_boolean(value, self->page && - cairo_surface_get_user_data(self->page, &fiv_io_key_frame_next)); + g_value_set_boolean(value, self->page && self->page->frame_next); break; case PROP_HAS_PREVIOUS_PAGE: g_value_set_boolean(value, self->image && self->page != self->image); break; case PROP_HAS_NEXT_PAGE: - g_value_set_boolean(value, self->page && - cairo_surface_get_user_data(self->page, &fiv_io_key_page_next)); + g_value_set_boolean(value, self->page && self->page->page_next); break; case PROP_HADJUSTMENT: @@ -403,20 +401,27 @@ static void prescale_page(FivView *self) { FivIoRenderClosure *closure = NULL; - if (!self->image || !(closure = - cairo_surface_get_user_data(self->page, &fiv_io_key_render))) + if (!self->image || !(closure = self->page->render)) return; // TODO(p): Restart the animation. No vector formats currently animate. g_return_if_fail(!self->frame_update_connection); // If it fails, the previous frame pointer may become invalid. - g_clear_pointer(&self->page_scaled, cairo_surface_destroy); + g_clear_pointer(&self->page_scaled, fiv_io_image_unref); self->frame = self->page_scaled = closure->render(closure, self->scale); if (!self->page_scaled) self->frame = self->page; } +static void +set_source_image(FivView *self, cairo_t *cr) +{ + cairo_surface_t *surface = fiv_io_image_to_surface_noref(self->frame); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); +} + static void fiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { @@ -606,37 +611,19 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr) // Then all frames are pre-scaled. if (self->page_scaled) { - cairo_set_source_surface(cr, self->frame, 0, 0); + set_source_image(self, cr); cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); cairo_paint(cr); return TRUE; } - // FIXME: Recording surfaces do not work well with CAIRO_SURFACE_TYPE_XLIB, - // we always get a shitty pixmap, where transparency contains junk. - if (cairo_surface_get_type(self->frame) == CAIRO_SURFACE_TYPE_RECORDING) { - cairo_surface_t *image = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw, dh); - cairo_t *tcr = cairo_create(image); - cairo_scale(tcr, self->scale, self->scale); - cairo_set_source_surface(tcr, self->frame, 0, 0); - cairo_pattern_set_matrix(cairo_get_source(tcr), &matrix); - cairo_paint(tcr); - cairo_destroy(tcr); - - cairo_set_source_surface(cr, image, 0, 0); - cairo_paint(cr); - cairo_surface_destroy(image); - return TRUE; - } - // XXX: The rounding together with padding may result in up to // a pixel's worth of made-up picture data. cairo_rectangle(cr, 0, 0, dw, dh); cairo_clip(cr); cairo_scale(cr, self->scale, self->scale); - cairo_set_source_surface(cr, self->frame, 0, 0); + set_source_image(self, cr); cairo_pattern_t *pattern = cairo_get_source(cr); cairo_pattern_set_matrix(pattern, &matrix); @@ -817,8 +804,7 @@ stop_animating(FivView *self) static gboolean advance_frame(FivView *self) { - cairo_surface_t *next = - cairo_surface_get_user_data(self->frame, &fiv_io_key_frame_next); + FivIoImage *next = self->frame->frame_next; if (next) { self->frame = next; } else { @@ -836,8 +822,7 @@ advance_animation(FivView *self, GdkFrameClock *clock) gint64 now = gdk_frame_clock_get_frame_time(clock); while (true) { // TODO(p): See if infinite frames can actually happen, and how. - intptr_t duration = (intptr_t) cairo_surface_get_user_data( - self->frame, &fiv_io_key_frame_duration); + int64_t duration = self->frame->frame_duration; if (duration < 0) return FALSE; @@ -875,30 +860,28 @@ start_animating(FivView *self) stop_animating(self); GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self)); - if (!clock || !self->image || - !cairo_surface_get_user_data(self->page, &fiv_io_key_frame_next)) + if (!clock || !self->image || !self->page->frame_next) return; self->frame_time = gdk_frame_clock_get_frame_time(clock); self->frame_update_connection = g_signal_connect( clock, "update", G_CALLBACK(on_frame_clock_update), self); - self->remaining_loops = - (uintptr_t) cairo_surface_get_user_data(self->page, &fiv_io_key_loops); + self->remaining_loops = self->page->loops; gdk_frame_clock_begin_updating(clock); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_PLAYING]); } static void -switch_page(FivView *self, cairo_surface_t *page) +switch_page(FivView *self, FivIoImage *page) { - g_clear_pointer(&self->page_scaled, cairo_surface_destroy); + g_clear_pointer(&self->page_scaled, fiv_io_image_unref); self->frame = self->page = page; prescale_page(self); if (!self->page || - (self->orientation = (uintptr_t) cairo_surface_get_user_data( - self->page, &fiv_io_key_orientation)) == FivIoOrientationUnknown) + (self->orientation = self->page->orientation) == + FivIoOrientationUnknown) self->orientation = FivIoOrientation0; start_animating(self); @@ -1027,7 +1010,7 @@ copy(FivView *self) cairo_surface_t *transformed = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); cairo_t *cr = cairo_create(transformed); - cairo_set_source_surface(cr, self->frame, 0, 0); + set_source_image(self, cr); cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); cairo_paint(cr); cairo_destroy(cr); @@ -1065,7 +1048,7 @@ on_draw_page(G_GNUC_UNUSED GtkPrintOperation *operation, cairo_t *cr = gtk_print_context_get_cairo_context(context); cairo_scale(cr, scale, scale); - cairo_set_source_surface(cr, self->frame, 0, 0); + set_source_image(self, cr); cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); cairo_paint(cr); } @@ -1100,7 +1083,7 @@ print(FivView *self) } static gboolean -save_as(FivView *self, cairo_surface_t *frame) +save_as(FivView *self, FivIoImage *frame) { GtkWindow *window = get_toplevel(GTK_WIDGET(self)); FivIoProfile target = NULL; @@ -1362,7 +1345,7 @@ fiv_view_init(FivView *self) // --- Public interface -------------------------------------------------------- -static cairo_surface_t * +static FivIoImage * open_without_swapping_in(FivView *self, const char *uri) { FivIoOpenContext ctx = { @@ -1374,8 +1357,7 @@ open_without_swapping_in(FivView *self, const char *uri) }; GError *error = NULL; - cairo_surface_t *surface = - fiv_io_image_to_surface(fiv_io_open(&ctx, &error)); + FivIoImage *image = fiv_io_open(&ctx, &error); if (error) { g_ptr_array_add(ctx.warnings, g_strdup(error->message)); g_error_free(error); @@ -1388,7 +1370,7 @@ open_without_swapping_in(FivView *self, const char *uri) } g_ptr_array_free(ctx.warnings, TRUE); - return surface; + return image; } // TODO(p): Progressive picture loading, or at least async/cancellable. @@ -1396,18 +1378,18 @@ gboolean fiv_view_set_uri(FivView *self, const char *uri) { // This is extremely expensive, and only works sometimes. - g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); + g_clear_pointer(&self->enhance_swap, fiv_io_image_unref); if (self->enhance) { self->enhance = FALSE; g_object_notify_by_pspec( G_OBJECT(self), view_properties[PROP_ENHANCE]); } - cairo_surface_t *surface = open_without_swapping_in(self, uri); - g_clear_pointer(&self->image, cairo_surface_destroy); + FivIoImage *image = open_without_swapping_in(self, uri); + g_clear_pointer(&self->image, fiv_io_image_unref); self->frame = self->page = NULL; - self->image = surface; + self->image = image; switch_page(self, self->image); // Otherwise, adjustment values and zoom are retained implicitly. @@ -1419,15 +1401,15 @@ fiv_view_set_uri(FivView *self, const char *uri) g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_MESSAGES]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]); - return surface != NULL; + return image != NULL; } static void page_step(FivView *self, int step) { - cairo_user_data_key_t *key = - step < 0 ? &fiv_io_key_page_previous : &fiv_io_key_page_next; - cairo_surface_t *page = cairo_surface_get_user_data(self->page, key); + FivIoImage *page = step < 0 + ? self->page->page_previous + : self->page->page_next; if (page) switch_page(self, page); } @@ -1436,9 +1418,10 @@ static void frame_step(FivView *self, int step) { stop_animating(self); - cairo_user_data_key_t *key = - step < 0 ? &fiv_io_key_frame_previous : &fiv_io_key_frame_next; - if (!step || !(self->frame = cairo_surface_get_user_data(self->frame, key))) + FivIoImage *frame = step < 0 + ? self->frame->frame_previous + : self->frame->frame_next; + if (!step || !(self->frame = frame)) self->frame = self->page; gtk_widget_queue_draw(GTK_WIDGET(self)); } @@ -1446,21 +1429,21 @@ frame_step(FivView *self, int step) static gboolean reload(FivView *self) { - cairo_surface_t *surface = open_without_swapping_in(self, self->uri); + FivIoImage *image = open_without_swapping_in(self, self->uri); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_MESSAGES]); - if (!surface) + if (!image) return FALSE; - g_clear_pointer(&self->image, cairo_surface_destroy); - g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); - switch_page(self, (self->image = surface)); + g_clear_pointer(&self->image, fiv_io_image_unref); + g_clear_pointer(&self->enhance_swap, fiv_io_image_unref); + switch_page(self, (self->image = image)); return TRUE; } static void swap_enhanced_image(FivView *self) { - cairo_surface_t *saved = self->image; + FivIoImage *saved = self->image; self->image = self->page = self->frame = NULL; if (self->enhance_swap) { @@ -1547,9 +1530,8 @@ fiv_view_command(FivView *self, FivViewCommand command) break; case FIV_VIEW_COMMAND_PAGE_NEXT: page_step(self, +1); break; case FIV_VIEW_COMMAND_PAGE_LAST: - for (cairo_surface_t *s = self->page; - (s = cairo_surface_get_user_data(s, &fiv_io_key_page_next)); ) - self->page = s; + for (FivIoImage *I = self->page; (I = I->page_next); ) + self->page = I; switch_page(self, self->page); break; case FIV_VIEW_COMMAND_FRAME_FIRST: