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.
This commit is contained in:
Přemysl Eric Janouch 2023-06-23 22:28:09 +02:00
parent add96b37a6
commit 074bd4d37f
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 287 additions and 366 deletions

183
fiv-io.c
View File

@ -180,7 +180,7 @@ add_warning(const FivIoOpenContext *ctx, const char *format, ...)
// --- Images ------------------------------------------------------------------ // --- Images ------------------------------------------------------------------
static FivIoImage * FivIoImage *
fiv_io_image_new(cairo_format_t format, uint32_t width, uint32_t height) fiv_io_image_new(cairo_format_t format, uint32_t width, uint32_t height)
{ {
// CAIRO_STRIDE_ALIGNMENT is 4 bytes, we only use multiples. // 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->xmp);
g_bytes_unref(image->thum); g_bytes_unref(image->thum);
if (image->text)
g_hash_table_unref(image->text);
if (image->render) if (image->render)
image->render->destroy(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); 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) fiv_io_image_to_surface_noref(const FivIoImage *image)
{ {
return cairo_image_surface_create_for_data( return cairo_image_surface_create_for_data(
@ -560,12 +563,11 @@ fiv_io_profile_argb32_premultiply(
static void static void
fiv_io_profile_argb32_premultiply_page( 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); fiv_io_profile_page(page, target, fiv_io_profile_xrgb32);
for (cairo_surface_t *frame = page; frame != NULL; for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next)
frame = cairo_surface_get_user_data(frame, &fiv_io_key_frame_next))
fiv_io_premultiply_argb32(frame); 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. // 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) fiv_io_open_png_thumbnail(const char *path, GError **error)
{ {
wuffs_png__decoder dec = {}; 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__image_config cfg = {};
wuffs_base__slice_u8 workbuf = {}; wuffs_base__slice_u8 workbuf = {};
cairo_surface_t *surface = NULL; FivIoImage *image = NULL;
bool success = false; bool success = false;
GHashTable *texts = 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) wuffs_base__image_config__first_frame_is_opaque(&cfg)
? CAIRO_FORMAT_RGB24 ? CAIRO_FORMAT_RGB24
: CAIRO_FORMAT_ARGB32, : CAIRO_FORMAT_ARGB32,
width, height); width, height);
if (!image) {
cairo_status_t surface_status = cairo_surface_status(surface); set_error(error, "image allocation failure");
if (surface_status != CAIRO_STATUS_SUCCESS) {
set_error(error, cairo_status_to_string(surface_status));
goto fail; goto fail;
} }
wuffs_base__pixel_buffer pb = {}; wuffs_base__pixel_buffer pb = {};
status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg,
wuffs_base__make_slice_u8(cairo_image_surface_get_data(surface), wuffs_base__make_slice_u8(image->data, image->stride * image->height));
cairo_image_surface_get_stride(surface) *
cairo_image_surface_get_height(surface)));
if (!wuffs_base__status__is_ok(&status)) { if (!wuffs_base__status__is_ok(&status)) {
set_error(error, wuffs_base__status__message(&status)); set_error(error, wuffs_base__status__message(&status));
goto fail; goto fail;
@ -1235,19 +1233,17 @@ fiv_io_open_png_thumbnail(const char *path, GError **error)
g_assert(key == NULL); g_assert(key == NULL);
cairo_surface_mark_dirty(surface); image->text = g_hash_table_ref(texts);
cairo_surface_set_user_data(surface, &fiv_io_key_text,
g_hash_table_ref(texts), (cairo_destroy_func_t) g_hash_table_unref);
success = true; success = true;
fail: fail:
if (!success) if (!success)
g_clear_pointer(&surface, cairo_surface_destroy); g_clear_pointer(&image, fiv_io_image_unref);
free(workbuf.ptr); free(workbuf.ptr);
g_free(data); g_free(data);
g_hash_table_unref(texts); g_hash_table_unref(texts);
return surface; return image;
} }
// --- Multi-Picture Format ---------------------------------------------------- // --- Multi-Picture Format ----------------------------------------------------
@ -2309,13 +2305,12 @@ load_resvg_render_internal(FivIoRenderClosureResvg *self,
return fiv_io_profile_finalize(image, target); return fiv_io_profile_finalize(image, target);
} }
static cairo_surface_t * static FivIoImage *
load_resvg_render(FivIoRenderClosure *closure, double scale) load_resvg_render(FivIoRenderClosure *closure, double scale)
{ {
FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure; FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure;
// TODO(p): Somehow get the target colour management profile. // TODO(p): Somehow get the target colour management profile.
return fiv_io_image_to_surface( return load_resvg_render_internal(self, scale, NULL, NULL);
load_resvg_render_internal(self, scale, NULL, NULL));
} }
static const char * static const char *
@ -2433,13 +2428,12 @@ load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale,
return fiv_io_profile_finalize(image, target); return fiv_io_profile_finalize(image, target);
} }
static cairo_surface_t * static FivIoImage *
load_librsvg_render(FivIoRenderClosure *closure, double scale) load_librsvg_render(FivIoRenderClosure *closure, double scale)
{ {
FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure; FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure;
// TODO(p): Somehow get the target colour management profile. // TODO(p): Somehow get the target colour management profile.
return fiv_io_image_to_surface( return load_librsvg_render_internal(self, scale, NULL, NULL);
load_librsvg_render_internal(self, scale, NULL, NULL));
} }
static FivIoImage * static FivIoImage *
@ -3139,24 +3133,6 @@ open_gdkpixbuf(
#endif // HAVE_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 * FivIoImage *
fiv_io_open(const FivIoOpenContext *ctx, GError **error) 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 * unsigned char *
fiv_io_encode_webp( 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); if (image->format != CAIRO_FORMAT_ARGB32 &&
int w = cairo_image_surface_get_width(surface); image->format != CAIRO_FORMAT_RGB24) {
int h = cairo_image_surface_get_height(surface); FivIoImage *converted =
if (format != CAIRO_FORMAT_ARGB32 && fiv_io_image_new(CAIRO_FORMAT_ARGB32, image->width, image->height);
format != CAIRO_FORMAT_RGB24) {
cairo_surface_t *converted = cairo_surface_t *surface = fiv_io_image_to_surface_noref(converted);
cairo_image_surface_create((format = CAIRO_FORMAT_ARGB32), w, h); cairo_t *cr = cairo_create(surface);
cairo_t *cr = cairo_create(converted); cairo_surface_destroy(surface);
surface = fiv_io_image_to_surface_noref(image);
cairo_set_source_surface(cr, surface, 0, 0); cairo_set_source_surface(cr, surface, 0, 0);
cairo_surface_destroy(surface);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr); cairo_paint(cr);
cairo_destroy(cr); cairo_destroy(cr);
surface = converted; image = converted;
} else { } else {
surface = cairo_surface_reference(surface); image = fiv_io_image_ref(image);
} }
WebPMemoryWriter writer = {}; WebPMemoryWriter writer = {};
@ -3505,27 +3484,26 @@ fiv_io_encode_webp(
goto fail; goto fail;
picture.use_argb = true; picture.use_argb = true;
picture.width = w; picture.width = image->width;
picture.height = h; picture.height = image->height;
if (!WebPPictureAlloc(&picture)) if (!WebPPictureAlloc(&picture))
goto fail; goto fail;
// Cairo uses a similar internal format, so we should be able to // Cairo uses a similar internal format, so we should be able to
// copy it over and fix up the minor differences. // copy it over and fix up the minor differences.
// This is written to be easy to follow rather than fast. // This is written to be easy to follow rather than fast.
int stride = cairo_image_surface_get_stride(surface); if (picture.argb_stride != (int) image->width ||
if (picture.argb_stride != w || picture.argb_stride * sizeof *picture.argb != image->stride ||
picture.argb_stride * (int) sizeof *picture.argb != stride || UINT32_MAX / picture.argb_stride < image->height)
INT_MAX / picture.argb_stride < h)
goto fail_compatibility; goto fail_compatibility;
uint32_t *argb = uint32_t *argb =
memcpy(picture.argb, cairo_image_surface_get_data(surface), stride * h); memcpy(picture.argb, image->data, image->stride * image->height);
if (format == CAIRO_FORMAT_ARGB32) if (image->format == CAIRO_FORMAT_ARGB32)
for (int i = h * picture.argb_stride; i-- > 0; argb++) for (int i = image->height * picture.argb_stride; i-- > 0; argb++)
*argb = wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul(*argb); *argb = wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul(*argb);
else else
for (int i = h * picture.argb_stride; i-- > 0; argb++) for (int i = image->height * picture.argb_stride; i-- > 0; argb++)
*argb |= 0xFF000000; *argb |= 0xFF000000;
// TODO(p): Prevent or propagate VP8_ENC_ERROR_BAD_DIMENSION. // TODO(p): Prevent or propagate VP8_ENC_ERROR_BAD_DIMENSION.
@ -3537,13 +3515,13 @@ fiv_io_encode_webp(
fail_compatibility: fail_compatibility:
WebPPictureFree(&picture); WebPPictureFree(&picture);
fail: fail:
cairo_surface_destroy(surface); fiv_io_image_unref(image);
*len = writer.size; *len = writer.size;
return writer.mem; return writer.mem;
} }
static WebPData static WebPData
encode_lossless_webp(cairo_surface_t *surface) encode_lossless_webp(FivIoImage *image)
{ {
WebPData bitstream = {}; WebPData bitstream = {};
WebPConfig config = {}; WebPConfig config = {};
@ -3554,12 +3532,12 @@ encode_lossless_webp(cairo_surface_t *surface)
if (!WebPValidateConfig(&config)) if (!WebPValidateConfig(&config))
return bitstream; return bitstream;
bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size); bitstream.bytes = fiv_io_encode_webp(image, &config, &bitstream.size);
return bitstream; return bitstream;
} }
static gboolean static gboolean
encode_webp_image(WebPMux *mux, cairo_surface_t *frame) encode_webp_image(WebPMux *mux, FivIoImage *frame)
{ {
WebPData bitstream = encode_lossless_webp(frame); WebPData bitstream = encode_lossless_webp(frame);
gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK; gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK;
@ -3568,15 +3546,13 @@ encode_webp_image(WebPMux *mux, cairo_surface_t *frame)
} }
static gboolean static gboolean
encode_webp_animation(WebPMux *mux, cairo_surface_t *page) encode_webp_animation(WebPMux *mux, FivIoImage *page)
{ {
gboolean ok = TRUE; gboolean ok = TRUE;
for (cairo_surface_t *frame = page; ok && frame; frame = for (FivIoImage *frame = page; ok && frame; frame = frame->frame_next) {
cairo_surface_get_user_data(frame, &fiv_io_key_frame_next)) {
WebPMuxFrameInfo info = { WebPMuxFrameInfo info = {
.bitstream = encode_lossless_webp(frame), .bitstream = encode_lossless_webp(frame),
.duration = (intptr_t) cairo_surface_get_user_data( .duration = frame->frame_duration,
frame, &fiv_io_key_frame_duration),
.id = WEBP_CHUNK_ANMF, .id = WEBP_CHUNK_ANMF,
.dispose_method = WEBP_MUX_DISPOSE_NONE, .dispose_method = WEBP_MUX_DISPOSE_NONE,
.blend_method = WEBP_MUX_NO_BLEND, .blend_method = WEBP_MUX_NO_BLEND,
@ -3586,8 +3562,7 @@ encode_webp_animation(WebPMux *mux, cairo_surface_t *page)
} }
WebPMuxAnimParams params = { WebPMuxAnimParams params = {
.bgcolor = 0x00000000, // BGRA, curiously. .bgcolor = 0x00000000, // BGRA, curiously.
.loop_count = (uintptr_t) .loop_count = page->loops,
cairo_surface_get_user_data(page, &fiv_io_key_loops),
}; };
return ok && WebPMuxSetAnimationParams(mux, &params) == WEBP_MUX_OK; return ok && WebPMuxSetAnimationParams(mux, &params) == WEBP_MUX_OK;
} }
@ -3605,7 +3580,7 @@ set_metadata(WebPMux *mux, const char *fourcc, GBytes *data)
} }
gboolean 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) const char *path, GError **error)
{ {
g_return_val_if_fail(page != NULL, FALSE); 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(); WebPMux *mux = WebPMuxNew();
if (frame) if (frame)
ok = encode_webp_image(mux, 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); ok = encode_webp_image(mux, page);
else else
ok = encode_webp_animation(mux, page); ok = encode_webp_animation(mux, page);
ok = ok && set_metadata(mux, "EXIF", ok = ok && set_metadata(mux, "EXIF", page->exif);
cairo_surface_get_user_data(page, &fiv_io_key_exif)); ok = ok && set_metadata(mux, "ICCP", page->icc);
ok = ok && set_metadata(mux, "ICCP", ok = ok && set_metadata(mux, "XMP ", page->xmp);
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));
GBytes *iccp = NULL; GBytes *iccp = NULL;
if (ok && target && (iccp = fiv_io_profile_to_bytes(target))) 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 ---------------------------------------------------------------- // --- Metadata ----------------------------------------------------------------
void void
fiv_io_orientation_dimensions(cairo_surface_t *surface, fiv_io_orientation_dimensions(
FivIoOrientation orientation, double *w, double *h) 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) { switch (orientation) {
case FivIoOrientation90: case FivIoOrientation90:
case FivIoOrientationMirror90: case FivIoOrientationMirror90:
case FivIoOrientation270: case FivIoOrientation270:
case FivIoOrientationMirror270: case FivIoOrientationMirror270:
*w = extents.height; *w = image->height;
*h = extents.width; *h = image->width;
break; break;
default: default:
*w = extents.width; *w = image->width;
*h = extents.height; *h = image->height;
} }
} }
cairo_matrix_t cairo_matrix_t
fiv_io_orientation_apply(cairo_surface_t *surface, fiv_io_orientation_apply(const FivIoImage *image,
FivIoOrientation orientation, double *width, double *height) 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_t matrix = {};
cairo_matrix_init_identity(&matrix); cairo_matrix_init_identity(&matrix);
@ -3749,7 +3706,7 @@ fiv_io_exif_orientation(const guint8 *tiff, gsize len)
} }
gboolean 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); 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. // (standalone) with trailing nonsense.
fprintf(fp, "\xFF\001Exiv2"); fprintf(fp, "\xFF\001Exiv2");
GBytes *data = NULL;
gsize len = 0; gsize len = 0;
gconstpointer p = NULL; gconstpointer p = NULL;
// Adobe XMP Specification Part 3: Storage in Files, 2020/1, 1.1.3 // Adobe XMP Specification Part 3: Storage in Files, 2020/1, 1.1.3
// I don't care if Exiv2 supports it this way. // I don't care if Exiv2 supports it this way.
if ((data = cairo_surface_get_user_data(page, &fiv_io_key_exif)) && if (page->exif && (p = g_bytes_get_data(page->exif, &len))) {
(p = g_bytes_get_data(data, &len))) {
while (len) { while (len) {
gsize chunk = MIN(len, 0xFFFF - 2 - 6); gsize chunk = MIN(len, 0xFFFF - 2 - 6);
uint8_t header[10] = "\xFF\xE1\000\000Exif\000\000"; 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 // https://www.color.org/specification/ICC1v43_2010-12.pdf B.4
if ((data = cairo_surface_get_user_data(page, &fiv_io_key_icc)) && if (page->icc && (p = g_bytes_get_data(page->icc, &len))) {
(p = g_bytes_get_data(data, &len))) {
gsize limit = 0xFFFF - 2 - 12; gsize limit = 0xFFFF - 2 - 12;
uint8_t current = 0, total = (len + limit - 1) / limit; uint8_t current = 0, total = (len + limit - 1) / limit;
while (len) { 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 // 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, // If the main segment overflows, then it's a sign of bad luck,
// because 1.1.3.1 is way too complex. // because 1.1.3.1 is way too complex.
if ((data = cairo_surface_get_user_data(page, &fiv_io_key_xmp)) && if (page->xmp && (p = g_bytes_get_data(page->xmp, &len))) {
(p = g_bytes_get_data(data, &len))) {
while (len) { while (len) {
gsize chunk = MIN(len, 0xFFFF - 2 - 29); gsize chunk = MIN(len, 0xFFFF - 2 - 29);
uint8_t header[33] = uint8_t header[33] =

133
fiv-io.h
View File

@ -33,10 +33,18 @@ void fiv_io_profile_free(FivIoProfile self);
// From libwebp, verified to exactly match [x * a / 255]. // From libwebp, verified to exactly match [x * a / 255].
#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23) #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 // https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf Table 6
typedef enum _FivIoOrientation { enum _FivIoOrientation {
FivIoOrientationUnknown = 0, FivIoOrientationUnknown = 0,
FivIoOrientation0 = 1, FivIoOrientation0 = 1,
FivIoOrientationMirror0 = 2, FivIoOrientationMirror0 = 2,
@ -46,35 +54,17 @@ typedef enum _FivIoOrientation {
FivIoOrientation90 = 6, FivIoOrientation90 = 6,
FivIoOrientationMirror90 = 7, FivIoOrientationMirror90 = 7,
FivIoOrientation270 = 8 FivIoOrientation270 = 8
} FivIoOrientation; };
/// Returns a rendering matrix for a surface (user space to pattern space), struct _FivIoRenderClosure {
/// 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. /// The rendering is allowed to fail, returning NULL.
cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale); FivIoImage *(*render)(FivIoRenderClosure *, double scale);
void (*destroy)(struct _FivIoRenderClosure *); void (*destroy)(FivIoRenderClosure *);
} FivIoRenderClosure; };
typedef struct _FivIoImage { // Metadata are typically attached to all Cairo surfaces in an animation.
struct _FivIoImage {
uint8_t *data; ///< Raw image data uint8_t *data; ///< Raw image data
cairo_format_t format; ///< Data format cairo_format_t format; ///< Data format
uint32_t width; ///< Width of the image in pixels uint32_t width; ///< Width of the image in pixels
@ -88,84 +78,55 @@ typedef struct _FivIoImage {
GBytes *xmp; ///< Raw XMP data GBytes *xmp; ///< Raw XMP data
GBytes *thum; ///< WebP THUM chunk, for our thumbnails 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. /// A FivIoRenderClosure for parametrized re-rendering of vector formats.
/// This is attached at the page level. /// This is attached at the page level.
FivIoRenderClosure *render; FivIoRenderClosure *render;
/// The first frame of the next page, in a chain. /// The first frame of the next page, in a chain.
/// There is no wrap-around. /// There is no wrap-around.
struct _FivIoImage *page_next; FivIoImage *page_next;
/// The first frame of the previous page, in a chain. /// The first frame of the previous page, in a chain.
/// There is no wrap-around. This is a weak pointer. /// 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. /// The next frame in a sequence, in a chain, pre-composited.
/// There is no wrap-around. /// There is no wrap-around.
struct _FivIoImage *frame_next; FivIoImage *frame_next;
/// The previous frame in a sequence, in a chain, pre-composited. /// The previous frame in a sequence, in a chain, pre-composited.
/// This is a weak pointer that wraps around, /// This is a weak pointer that wraps around,
/// and needn't be present for static images. /// and needn't be present for static images.
struct _FivIoImage *frame_previous; FivIoImage *frame_previous;
/// Frame duration in milliseconds. /// Frame duration in milliseconds.
int64_t frame_duration; int64_t frame_duration;
/// How many times to repeat the animation, or zero for +inf. /// How many times to repeat the animation, or zero for +inf.
uint64_t loops; uint64_t loops;
} FivIoImage; };
FivIoImage *fiv_io_image_ref(FivIoImage *image); FivIoImage *fiv_io_image_ref(FivIoImage *image);
void fiv_io_image_unref(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, /// Return a new Cairo image surface referencing the same data as the image,
/// eating the reference to it. /// eating the reference to it.
cairo_surface_t *fiv_io_image_to_surface(FivIoImage *image); 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 { typedef struct {
const char *uri; ///< Source URI const char *uri; ///< Source URI
FivIoProfile screen_profile; ///< Target colour space or NULL 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( FivIoImage *fiv_io_open_from_data(
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error); 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 --------------------------------------------- // --- Thumbnail passing utilities ---------------------------------------------
@ -192,12 +169,12 @@ GBytes *fiv_io_serialize_for_search(cairo_surface_t *surface, GError **error);
// --- Export ------------------------------------------------------------------ // --- 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(). /// The result needs to be freed using WebPFree/WebPDataClear().
unsigned char *fiv_io_encode_webp( 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. /// Saves the page as a lossless WebP still picture or animation.
/// If no exact frame is specified, this potentially creates an 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); FivIoProfile target, const char *path, GError **error);

View File

@ -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) render(GFile *target, GBytes *data, gboolean *color_managed, GError **error)
{ {
FivIoOpenContext ctx = { 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), .warnings = g_ptr_array_new_with_free_func(g_free),
}; };
cairo_surface_t *surface = fiv_io_image_to_surface(fiv_io_open_from_data( FivIoImage *image = fiv_io_open_from_data(
g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error)); g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error);
g_free((gchar *) ctx.uri); g_free((gchar *) ctx.uri);
g_ptr_array_free(ctx.warnings, TRUE); g_ptr_array_free(ctx.warnings, TRUE);
if ((*color_managed = !!ctx.screen_profile)) if ((*color_managed = !!ctx.screen_profile))
fiv_io_profile_free(ctx.screen_profile); fiv_io_profile_free(ctx.screen_profile);
g_bytes_unref(data); g_bytes_unref(data);
return surface; return image;
} }
// In principle similar to rescale_thumbnail() from fiv-browser.c. // In principle similar to rescale_thumbnail() from fiv-browser.c.
static cairo_surface_t * static FivIoImage *
adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) adjust_thumbnail(FivIoImage *thumbnail, double row_height)
{ {
// Hardcode orientation. // Hardcode orientation.
FivIoOrientation orientation = (uintptr_t) cairo_surface_get_user_data( FivIoOrientation orientation = thumbnail->orientation;
thumbnail, &fiv_io_key_orientation);
double w = 0, h = 0; double w = 0, h = 0;
cairo_matrix_t matrix = 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. // Vector images should not have orientation, this should handle them all.
FivIoRenderClosure *closure = FivIoRenderClosure *closure = thumbnail->render;
cairo_surface_get_user_data(thumbnail, &fiv_io_key_render);
if (closure && orientation <= FivIoOrientation0) { if (closure && orientation <= FivIoOrientation0) {
// This API doesn't accept non-uniform scaling; prefer a vertical fit. // 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) if (scaled)
return scaled; return scaled;
} }
// This will be CAIRO_FORMAT_INVALID with non-image surfaces, which is fine. if (orientation <= FivIoOrientation0 && scale_x == 1 && scale_y == 1)
cairo_format_t format = cairo_image_surface_get_format(thumbnail); return fiv_io_image_ref(thumbnail);
if (format != CAIRO_FORMAT_INVALID &&
orientation <= FivIoOrientation0 && scale_x == 1 && scale_y == 1)
return cairo_surface_reference(thumbnail);
cairo_format_t format = thumbnail->format;
int projected_width = round(scale_x * w); int projected_width = round(scale_x * w);
int projected_height = round(scale_y * h); 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) (format == CAIRO_FORMAT_RGB24 || format == CAIRO_FORMAT_RGB30)
? CAIRO_FORMAT_RGB24 ? CAIRO_FORMAT_RGB24
: CAIRO_FORMAT_ARGB32, : CAIRO_FORMAT_ARGB32,
projected_width, projected_height); 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_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_pattern_t *pattern = cairo_get_source(cr);
// CAIRO_FILTER_BEST, for some reason, works bad with CAIRO_FORMAT_RGB30. // CAIRO_FILTER_BEST, for some reason, works bad with CAIRO_FORMAT_RGB30.
cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD); 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, // Note that this doesn't get triggered with oversize input surfaces,
// even though nothing will be rendered. // even though nothing will be rendered.
if (cairo_surface_status(thumbnail) != CAIRO_STATUS_SUCCESS || if (cairo_pattern_status(pattern) != CAIRO_STATUS_SUCCESS ||
cairo_surface_status(scaled) != CAIRO_STATUS_SUCCESS ||
cairo_pattern_status(pattern) != CAIRO_STATUS_SUCCESS ||
cairo_status(cr) != CAIRO_STATUS_SUCCESS) cairo_status(cr) != CAIRO_STATUS_SUCCESS)
g_warning("thumbnail scaling failed"); g_warning("thumbnail scaling failed");
@ -218,27 +222,32 @@ adjust_thumbnail(cairo_surface_t *thumbnail, double row_height)
return scaled; return scaled;
} }
static cairo_surface_t * static FivIoImage *
orient_thumbnail(cairo_surface_t *surface) orient_thumbnail(FivIoImage *image)
{ {
int orientation = (intptr_t) cairo_surface_get_user_data( if (image->orientation <= FivIoOrientation0)
surface, &fiv_io_key_orientation); return image;
if (orientation <= FivIoOrientation0)
return surface;
double w = 0, h = 0; double w = 0, h = 0;
cairo_matrix_t matrix = cairo_matrix_t matrix =
fiv_io_orientation_apply(surface, orientation, &w, &h); fiv_io_orientation_apply(image, image->orientation, &w, &h);
cairo_surface_t *oriented = FivIoImage *oriented = fiv_io_image_new(image->format, w, h);
cairo_image_surface_create(CAIRO_FORMAT_RGB24, 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_set_source_surface(cr, surface, 0, 0);
cairo_surface_destroy(surface);
cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); cairo_pattern_set_matrix(cairo_get_source(cr), &matrix);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr); cairo_paint(cr);
cairo_destroy(cr); cairo_destroy(cr);
cairo_surface_destroy(surface);
return oriented; 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) extract_libraw_bitmap(libraw_processed_image_t *image, int flip, GError **error)
{ {
// Anything else is extremely rare. // Anything else is extremely rare.
@ -399,24 +408,26 @@ extract_libraw_bitmap(libraw_processed_image_t *image, int flip, GError **error)
return NULL; return NULL;
} }
cairo_surface_t *surface = cairo_image_surface_create( FivIoImage *I = fiv_io_image_new(
CAIRO_FORMAT_RGB24, image->width, image->height); 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; const unsigned char *in = image->data;
for (guint64 i = 0; i < image->width * image->height; in += 3) for (guint64 i = 0; i < image->width * image->height; in += 3)
out[i++] = in[0] << 16 | in[1] << 8 | in[2]; out[i++] = in[0] << 16 | in[1] << 8 | in[2];
cairo_surface_mark_dirty(surface);
FivIoOrientation orient = extract_libraw_unflip(flip); I->orientation = extract_libraw_unflip(flip);
cairo_surface_set_user_data( return I;
surface, &fiv_io_key_orientation, (void *) (intptr_t) orient, NULL);
return surface;
} }
static cairo_surface_t * static FivIoImage *
extract_libraw(GFile *target, GMappedFile *mf, GError **error) extract_libraw(GFile *target, GMappedFile *mf, GError **error)
{ {
cairo_surface_t *surface = NULL; FivIoImage *I = NULL;
libraw_data_t *iprc = libraw_init( libraw_data_t *iprc = libraw_init(
LIBRAW_OPIONS_NO_MEMERR_CALLBACK | LIBRAW_OPIONS_NO_DATAERR_CALLBACK); LIBRAW_OPIONS_NO_MEMERR_CALLBACK | LIBRAW_OPIONS_NO_DATAERR_CALLBACK);
if (!iprc) { if (!iprc) {
@ -467,11 +478,11 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error)
switch (image->type) { switch (image->type) {
gboolean dummy; gboolean dummy;
case LIBRAW_IMAGE_JPEG: case LIBRAW_IMAGE_JPEG:
surface = render( I = render(
target, g_bytes_new(image->data, image->data_size), &dummy, error); target, g_bytes_new(image->data, image->data_size), &dummy, error);
break; break;
case LIBRAW_IMAGE_BITMAP: case LIBRAW_IMAGE_BITMAP:
surface = extract_libraw_bitmap(image, flip, error); I = extract_libraw_bitmap(image, flip, error);
break; break;
default: default:
set_error(error, "unsupported embedded thumbnail"); set_error(error, "unsupported embedded thumbnail");
@ -480,7 +491,7 @@ extract_libraw(GFile *target, GMappedFile *mf, GError **error)
libraw_dcraw_clear_mem(image); libraw_dcraw_clear_mem(image);
fail: fail:
libraw_close(iprc); libraw_close(iprc);
return surface; return I;
} }
#endif // HAVE_LIBRAW #endif // HAVE_LIBRAW
@ -506,30 +517,30 @@ fiv_thumbnail_extract(GFile *target, FivThumbnailSize max_size, GError **error)
return NULL; return NULL;
} }
cairo_surface_t *surface = NULL; FivIoImage *image = NULL;
#ifdef HAVE_LIBRAW #ifdef HAVE_LIBRAW
surface = extract_libraw(target, mf, error); image = extract_libraw(target, mf, error);
#else // ! HAVE_LIBRAW #else // ! HAVE_LIBRAW
// TODO(p): Implement our own thumbnail extractors. // TODO(p): Implement our own thumbnail extractors.
set_error(error, "unsupported file"); set_error(error, "unsupported file");
#endif // ! HAVE_LIBRAW #endif // ! HAVE_LIBRAW
g_mapped_file_unref(mf); g_mapped_file_unref(mf);
if (!surface) if (!image)
return NULL; return NULL;
if (max_size < FIV_THUMBNAIL_SIZE_MIN || max_size > FIV_THUMBNAIL_SIZE_MAX) 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 = FivIoImage *result =
adjust_thumbnail(surface, fiv_thumbnail_sizes[max_size].size); adjust_thumbnail(image, fiv_thumbnail_sizes[max_size].size);
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return result; return fiv_io_image_to_surface(result);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static WebPData static WebPData
encode_thumbnail(cairo_surface_t *surface) encode_thumbnail(FivIoImage *image)
{ {
WebPData bitstream = {}; WebPData bitstream = {};
WebPConfig config = {}; WebPConfig config = {};
@ -541,12 +552,12 @@ encode_thumbnail(cairo_surface_t *surface)
if (!WebPValidateConfig(&config)) if (!WebPValidateConfig(&config))
return bitstream; return bitstream;
bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size); bitstream.bytes = fiv_io_encode_webp(image, &config, &bitstream.size);
return bitstream; return bitstream;
} }
static void 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(); WebPMux *mux = WebPMuxNew();
WebPData bitstream = encode_thumbnail(thumbnail); WebPData bitstream = encode_thumbnail(thumbnail);
@ -602,15 +613,15 @@ fiv_thumbnail_produce_for_search(
return NULL; return NULL;
gboolean color_managed = FALSE; gboolean color_managed = FALSE;
cairo_surface_t *surface = render(target, data, &color_managed, error); FivIoImage *image = render(target, data, &color_managed, error);
if (!surface) if (!image)
return NULL; return NULL;
// TODO(p): Might want to keep this a square. // TODO(p): Might want to keep this a square.
cairo_surface_t *result = FivIoImage *result =
adjust_thumbnail(surface, fiv_thumbnail_sizes[max_size].size); adjust_thumbnail(image, fiv_thumbnail_sizes[max_size].size);
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return result; return fiv_io_image_to_surface(result);
} }
static cairo_surface_t * static cairo_surface_t *
@ -638,14 +649,14 @@ produce_fallback(GFile *target, FivThumbnailSize size, GError **error)
return NULL; return NULL;
gboolean color_managed = FALSE; gboolean color_managed = FALSE;
cairo_surface_t *surface = render(target, data, &color_managed, error); FivIoImage *image = render(target, data, &color_managed, error);
if (!surface) if (!image)
return NULL; return NULL;
cairo_surface_t *result = FivIoImage *result =
adjust_thumbnail(surface, fiv_thumbnail_sizes[size].size); adjust_thumbnail(image, fiv_thumbnail_sizes[size].size);
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return result; return fiv_io_image_to_surface(result);
} }
cairo_surface_t * cairo_surface_t *
@ -690,10 +701,10 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
} }
gboolean color_managed = FALSE; gboolean color_managed = FALSE;
cairo_surface_t *surface = FivIoImage *image =
render(target, g_mapped_file_get_bytes(mf), &color_managed, error); render(target, g_mapped_file_get_bytes(mf), &color_managed, error);
g_mapped_file_unref(mf); g_mapped_file_unref(mf);
if (!surface) if (!image)
return NULL; return NULL;
// Boilerplate copied from fiv_thumbnail_lookup(). // Boilerplate copied from fiv_thumbnail_lookup().
@ -709,12 +720,10 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
g_string_append_printf( g_string_append_printf(
thum, "%s%c%llu%c", THUMB_SIZE, 0, (unsigned long long) filesize, 0); 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%u%c", THUMB_IMAGE_WIDTH, 0,
g_string_append_printf(thum, "%s%c%d%c", THUMB_IMAGE_WIDTH, 0, (unsigned) image->width, 0);
cairo_image_surface_get_width(surface), 0); g_string_append_printf(thum, "%s%c%u%c", THUMB_IMAGE_HEIGHT, 0,
g_string_append_printf(thum, "%s%c%d%c", THUMB_IMAGE_HEIGHT, 0, (unsigned) image->height, 0);
cairo_image_surface_get_height(surface), 0);
}
// Without a CMM, no conversion is attempted. // Without a CMM, no conversion is attempted.
if (color_managed) { 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); 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--) { for (int use = max_size; use >= FIV_THUMBNAIL_SIZE_MIN; use--) {
cairo_surface_t *scaled = FivIoImage *scaled =
adjust_thumbnail(surface, fiv_thumbnail_sizes[use].size); adjust_thumbnail(image, fiv_thumbnail_sizes[use].size);
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir, gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
fiv_thumbnail_sizes[use].thumbnail_spec_name, sum); fiv_thumbnail_sizes[use].thumbnail_spec_name, sum);
save_thumbnail(scaled, path, thum); save_thumbnail(scaled, path, thum);
g_free(path); g_free(path);
if (!max_size_surface) if (!max_size_image)
max_size_surface = scaled; max_size_image = scaled;
else else
cairo_surface_destroy(scaled); fiv_io_image_unref(scaled);
} }
g_string_free(thum, TRUE); 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(thumbnails_dir);
g_free(sum); g_free(sum);
g_free(uri); g_free(uri);
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return max_size_surface; 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) if (!thumbnail_uri)
return NULL; return NULL;
cairo_surface_t *surface = fiv_io_image_to_surface( FivIoImage *image =
fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error)); fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error);
g_free(thumbnail_uri); g_free(thumbnail_uri);
if (!surface) if (!image)
return NULL; return NULL;
bool sRGB = false; bool sRGB = false;
GBytes *thum = cairo_surface_get_user_data(surface, &fiv_io_key_thum); if (!image->thum) {
if (!thum) {
g_clear_error(error); g_clear_error(error);
set_error(error, "not a thumbnail"); 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); g_clear_error(error);
set_error(error, "mismatch"); set_error(error, "mismatch");
} else { } else {
// TODO(p): Add a function or a non-valueless define to check // TODO(p): Add a function or a non-valueless define to check
// for CMM presence, then remove this ifdef. // for CMM presence, then remove this ifdef.
cairo_surface_t *surface = fiv_io_image_to_surface(image);
#ifdef HAVE_LCMS2 #ifdef HAVE_LCMS2
if (!sRGB) if (!sRGB)
mark_thumbnail_lq(surface); mark_thumbnail_lq(surface);
@ -817,21 +826,21 @@ read_wide_thumbnail(const char *path, const Stat *st, GError **error)
return surface; return surface;
} }
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return NULL; return NULL;
} }
static cairo_surface_t * static cairo_surface_t *
read_png_thumbnail(const char *path, const Stat *st, GError **error) read_png_thumbnail(const char *path, const Stat *st, GError **error)
{ {
cairo_surface_t *surface = fiv_io_open_png_thumbnail(path, error); FivIoImage *image = fiv_io_open_png_thumbnail(path, error);
if (!surface) if (!image)
return NULL; return NULL;
GHashTable *texts = cairo_surface_get_user_data(surface, &fiv_io_key_text); GHashTable *texts = image->text;
if (!texts) { if (!texts) {
set_error(error, "not a thumbnail"); set_error(error, "not a thumbnail");
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return NULL; 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) || if (!text_uri || strcmp(text_uri, st->uri) ||
!text_mtime || atol(text_mtime) != st->mtime) { !text_mtime || atol(text_mtime) != st->mtime) {
set_error(error, "mismatch or not a thumbnail"); set_error(error, "mismatch or not a thumbnail");
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return NULL; return NULL;
} }
if (text_size && strtoull(text_size, NULL, 10) != st->size) { if (text_size && strtoull(text_size, NULL, 10) != st->size) {
set_error(error, "file size mismatch"); set_error(error, "file size mismatch");
cairo_surface_destroy(surface); fiv_io_image_unref(image);
return NULL; return NULL;
} }
return surface; return fiv_io_image_to_surface(image);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -63,10 +63,10 @@ struct _FivView {
gchar *messages; ///< Image load information gchar *messages; ///< Image load information
gchar *uri; ///< Path to the current image (if any) gchar *uri; ///< Path to the current image (if any)
cairo_surface_t *image; ///< The loaded image (sequence) FivIoImage *image; ///< The loaded image (sequence)
cairo_surface_t *page; ///< Current page within image, weak FivIoImage *page; ///< Current page within image, weak
cairo_surface_t *page_scaled; ///< Current page within image, scaled FivIoImage *page_scaled; ///< Current page within image, scaled
cairo_surface_t *frame; ///< Current frame within page, weak FivIoImage *frame; ///< Current frame within page, weak
FivIoOrientation orientation; ///< Current page orientation FivIoOrientation orientation; ///< Current page orientation
bool enable_cms : 1; ///< Smooth scaling toggle bool enable_cms : 1; ///< Smooth scaling toggle
bool filter : 1; ///< Smooth scaling toggle bool filter : 1; ///< Smooth scaling toggle
@ -77,7 +77,7 @@ struct _FivView {
double scale; ///< Scaling factor double scale; ///< Scaling factor
double drag_start[2]; ///< Adjustment values for drag origin 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 FivIoProfile screen_cms_profile; ///< Target colour profile for widget
int remaining_loops; ///< Greater than zero if limited int remaining_loops; ///< Greater than zero if limited
@ -234,9 +234,9 @@ fiv_view_finalize(GObject *gobject)
{ {
FivView *self = FIV_VIEW(gobject); FivView *self = FIV_VIEW(gobject);
g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free); g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free);
g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); g_clear_pointer(&self->enhance_swap, fiv_io_image_unref);
g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->image, fiv_io_image_unref);
g_clear_pointer(&self->page_scaled, cairo_surface_destroy); g_clear_pointer(&self->page_scaled, fiv_io_image_unref);
g_free(self->uri); g_free(self->uri);
g_free(self->messages); g_free(self->messages);
@ -283,15 +283,13 @@ fiv_view_get_property(
g_value_set_boolean(value, !!self->image); g_value_set_boolean(value, !!self->image);
break; break;
case PROP_CAN_ANIMATE: case PROP_CAN_ANIMATE:
g_value_set_boolean(value, self->page && g_value_set_boolean(value, self->page && self->page->frame_next);
cairo_surface_get_user_data(self->page, &fiv_io_key_frame_next));
break; break;
case PROP_HAS_PREVIOUS_PAGE: case PROP_HAS_PREVIOUS_PAGE:
g_value_set_boolean(value, self->image && self->page != self->image); g_value_set_boolean(value, self->image && self->page != self->image);
break; break;
case PROP_HAS_NEXT_PAGE: case PROP_HAS_NEXT_PAGE:
g_value_set_boolean(value, self->page && g_value_set_boolean(value, self->page && self->page->page_next);
cairo_surface_get_user_data(self->page, &fiv_io_key_page_next));
break; break;
case PROP_HADJUSTMENT: case PROP_HADJUSTMENT:
@ -403,20 +401,27 @@ static void
prescale_page(FivView *self) prescale_page(FivView *self)
{ {
FivIoRenderClosure *closure = NULL; FivIoRenderClosure *closure = NULL;
if (!self->image || !(closure = if (!self->image || !(closure = self->page->render))
cairo_surface_get_user_data(self->page, &fiv_io_key_render)))
return; return;
// TODO(p): Restart the animation. No vector formats currently animate. // TODO(p): Restart the animation. No vector formats currently animate.
g_return_if_fail(!self->frame_update_connection); g_return_if_fail(!self->frame_update_connection);
// If it fails, the previous frame pointer may become invalid. // 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); self->frame = self->page_scaled = closure->render(closure, self->scale);
if (!self->page_scaled) if (!self->page_scaled)
self->frame = self->page; 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 static void
fiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation) 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. // Then all frames are pre-scaled.
if (self->page_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_pattern_set_matrix(cairo_get_source(cr), &matrix);
cairo_paint(cr); cairo_paint(cr);
return TRUE; 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 // XXX: The rounding together with padding may result in up to
// a pixel's worth of made-up picture data. // a pixel's worth of made-up picture data.
cairo_rectangle(cr, 0, 0, dw, dh); cairo_rectangle(cr, 0, 0, dw, dh);
cairo_clip(cr); cairo_clip(cr);
cairo_scale(cr, self->scale, self->scale); 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_t *pattern = cairo_get_source(cr);
cairo_pattern_set_matrix(pattern, &matrix); cairo_pattern_set_matrix(pattern, &matrix);
@ -817,8 +804,7 @@ stop_animating(FivView *self)
static gboolean static gboolean
advance_frame(FivView *self) advance_frame(FivView *self)
{ {
cairo_surface_t *next = FivIoImage *next = self->frame->frame_next;
cairo_surface_get_user_data(self->frame, &fiv_io_key_frame_next);
if (next) { if (next) {
self->frame = next; self->frame = next;
} else { } else {
@ -836,8 +822,7 @@ advance_animation(FivView *self, GdkFrameClock *clock)
gint64 now = gdk_frame_clock_get_frame_time(clock); gint64 now = gdk_frame_clock_get_frame_time(clock);
while (true) { while (true) {
// TODO(p): See if infinite frames can actually happen, and how. // TODO(p): See if infinite frames can actually happen, and how.
intptr_t duration = (intptr_t) cairo_surface_get_user_data( int64_t duration = self->frame->frame_duration;
self->frame, &fiv_io_key_frame_duration);
if (duration < 0) if (duration < 0)
return FALSE; return FALSE;
@ -875,30 +860,28 @@ start_animating(FivView *self)
stop_animating(self); stop_animating(self);
GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self)); GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self));
if (!clock || !self->image || if (!clock || !self->image || !self->page->frame_next)
!cairo_surface_get_user_data(self->page, &fiv_io_key_frame_next))
return; return;
self->frame_time = gdk_frame_clock_get_frame_time(clock); self->frame_time = gdk_frame_clock_get_frame_time(clock);
self->frame_update_connection = g_signal_connect( self->frame_update_connection = g_signal_connect(
clock, "update", G_CALLBACK(on_frame_clock_update), self); clock, "update", G_CALLBACK(on_frame_clock_update), self);
self->remaining_loops = self->remaining_loops = self->page->loops;
(uintptr_t) cairo_surface_get_user_data(self->page, &fiv_io_key_loops);
gdk_frame_clock_begin_updating(clock); gdk_frame_clock_begin_updating(clock);
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_PLAYING]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_PLAYING]);
} }
static void 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; self->frame = self->page = page;
prescale_page(self); prescale_page(self);
if (!self->page || if (!self->page ||
(self->orientation = (uintptr_t) cairo_surface_get_user_data( (self->orientation = self->page->orientation) ==
self->page, &fiv_io_key_orientation)) == FivIoOrientationUnknown) FivIoOrientationUnknown)
self->orientation = FivIoOrientation0; self->orientation = FivIoOrientation0;
start_animating(self); start_animating(self);
@ -1027,7 +1010,7 @@ copy(FivView *self)
cairo_surface_t *transformed = cairo_surface_t *transformed =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
cairo_t *cr = cairo_create(transformed); 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_pattern_set_matrix(cairo_get_source(cr), &matrix);
cairo_paint(cr); cairo_paint(cr);
cairo_destroy(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_t *cr = gtk_print_context_get_cairo_context(context);
cairo_scale(cr, scale, scale); 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_pattern_set_matrix(cairo_get_source(cr), &matrix);
cairo_paint(cr); cairo_paint(cr);
} }
@ -1100,7 +1083,7 @@ print(FivView *self)
} }
static gboolean static gboolean
save_as(FivView *self, cairo_surface_t *frame) save_as(FivView *self, FivIoImage *frame)
{ {
GtkWindow *window = get_toplevel(GTK_WIDGET(self)); GtkWindow *window = get_toplevel(GTK_WIDGET(self));
FivIoProfile target = NULL; FivIoProfile target = NULL;
@ -1362,7 +1345,7 @@ fiv_view_init(FivView *self)
// --- Public interface -------------------------------------------------------- // --- Public interface --------------------------------------------------------
static cairo_surface_t * static FivIoImage *
open_without_swapping_in(FivView *self, const char *uri) open_without_swapping_in(FivView *self, const char *uri)
{ {
FivIoOpenContext ctx = { FivIoOpenContext ctx = {
@ -1374,8 +1357,7 @@ open_without_swapping_in(FivView *self, const char *uri)
}; };
GError *error = NULL; GError *error = NULL;
cairo_surface_t *surface = FivIoImage *image = fiv_io_open(&ctx, &error);
fiv_io_image_to_surface(fiv_io_open(&ctx, &error));
if (error) { if (error) {
g_ptr_array_add(ctx.warnings, g_strdup(error->message)); g_ptr_array_add(ctx.warnings, g_strdup(error->message));
g_error_free(error); g_error_free(error);
@ -1388,7 +1370,7 @@ open_without_swapping_in(FivView *self, const char *uri)
} }
g_ptr_array_free(ctx.warnings, TRUE); g_ptr_array_free(ctx.warnings, TRUE);
return surface; return image;
} }
// TODO(p): Progressive picture loading, or at least async/cancellable. // TODO(p): Progressive picture loading, or at least async/cancellable.
@ -1396,18 +1378,18 @@ gboolean
fiv_view_set_uri(FivView *self, const char *uri) fiv_view_set_uri(FivView *self, const char *uri)
{ {
// This is extremely expensive, and only works sometimes. // 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) { if (self->enhance) {
self->enhance = FALSE; self->enhance = FALSE;
g_object_notify_by_pspec( g_object_notify_by_pspec(
G_OBJECT(self), view_properties[PROP_ENHANCE]); G_OBJECT(self), view_properties[PROP_ENHANCE]);
} }
cairo_surface_t *surface = open_without_swapping_in(self, uri); FivIoImage *image = open_without_swapping_in(self, uri);
g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->image, fiv_io_image_unref);
self->frame = self->page = NULL; self->frame = self->page = NULL;
self->image = surface; self->image = image;
switch_page(self, self->image); switch_page(self, self->image);
// Otherwise, adjustment values and zoom are retained implicitly. // 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_MESSAGES]);
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]);
return surface != NULL; return image != NULL;
} }
static void static void
page_step(FivView *self, int step) page_step(FivView *self, int step)
{ {
cairo_user_data_key_t *key = FivIoImage *page = step < 0
step < 0 ? &fiv_io_key_page_previous : &fiv_io_key_page_next; ? self->page->page_previous
cairo_surface_t *page = cairo_surface_get_user_data(self->page, key); : self->page->page_next;
if (page) if (page)
switch_page(self, page); switch_page(self, page);
} }
@ -1436,9 +1418,10 @@ static void
frame_step(FivView *self, int step) frame_step(FivView *self, int step)
{ {
stop_animating(self); stop_animating(self);
cairo_user_data_key_t *key = FivIoImage *frame = step < 0
step < 0 ? &fiv_io_key_frame_previous : &fiv_io_key_frame_next; ? self->frame->frame_previous
if (!step || !(self->frame = cairo_surface_get_user_data(self->frame, key))) : self->frame->frame_next;
if (!step || !(self->frame = frame))
self->frame = self->page; self->frame = self->page;
gtk_widget_queue_draw(GTK_WIDGET(self)); gtk_widget_queue_draw(GTK_WIDGET(self));
} }
@ -1446,21 +1429,21 @@ frame_step(FivView *self, int step)
static gboolean static gboolean
reload(FivView *self) 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]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_MESSAGES]);
if (!surface) if (!image)
return FALSE; return FALSE;
g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->image, fiv_io_image_unref);
g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); g_clear_pointer(&self->enhance_swap, fiv_io_image_unref);
switch_page(self, (self->image = surface)); switch_page(self, (self->image = image));
return TRUE; return TRUE;
} }
static void static void
swap_enhanced_image(FivView *self) swap_enhanced_image(FivView *self)
{ {
cairo_surface_t *saved = self->image; FivIoImage *saved = self->image;
self->image = self->page = self->frame = NULL; self->image = self->page = self->frame = NULL;
if (self->enhance_swap) { if (self->enhance_swap) {
@ -1547,9 +1530,8 @@ fiv_view_command(FivView *self, FivViewCommand command)
break; case FIV_VIEW_COMMAND_PAGE_NEXT: break; case FIV_VIEW_COMMAND_PAGE_NEXT:
page_step(self, +1); page_step(self, +1);
break; case FIV_VIEW_COMMAND_PAGE_LAST: break; case FIV_VIEW_COMMAND_PAGE_LAST:
for (cairo_surface_t *s = self->page; for (FivIoImage *I = self->page; (I = I->page_next); )
(s = cairo_surface_get_user_data(s, &fiv_io_key_page_next)); ) self->page = I;
self->page = s;
switch_page(self, self->page); switch_page(self, self->page);
break; case FIV_VIEW_COMMAND_FRAME_FIRST: break; case FIV_VIEW_COMMAND_FRAME_FIRST: