Redirect warnings to the info bar

And speed up thumbnailing of animated images while at it.

Also, fix thumbnailing SVGs with external links.
This commit is contained in:
Přemysl Eric Janouch 2022-01-24 04:03:19 +01:00
parent 991e74b99b
commit 788485d81e
Signed by: p
GPG Key ID: A0420B94F92B9493
5 changed files with 168 additions and 109 deletions

View File

@ -1,7 +1,7 @@
// //
// fiv-io-benchmark.c: see if we're worth the name // fiv-io-benchmark.c: see if we suck
// //
// Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name> // Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
// //
// Permission to use, copy, modify, and/or distribute this software for any // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted. // purpose with or without fee is hereby granted.
@ -33,9 +33,16 @@ static void
one_file(const char *filename) one_file(const char *filename)
{ {
double since_us = timestamp(); double since_us = timestamp();
gchar *uri = g_filename_to_uri(filename, NULL, NULL); FivIoOpenContext ctx = {
cairo_surface_t *loaded_by_us = fiv_io_open(uri, NULL, FALSE, NULL); .uri = g_filename_to_uri(filename, NULL, NULL),
g_free(uri); .screen_dpi = 96,
// Only using this array as a redirect.
.warnings = g_ptr_array_new_with_free_func(g_free),
};
cairo_surface_t *loaded_by_us = fiv_io_open(&ctx, NULL);
g_free((char *) ctx.uri);
g_ptr_array_free(ctx.warnings, TRUE);
if (!loaded_by_us) if (!loaded_by_us)
return; return;

164
fiv-io.c
View File

@ -151,6 +151,21 @@ set_error(GError **error, const char *message)
g_set_error_literal(error, FIV_IO_ERROR, FIV_IO_ERROR_OPEN, message); g_set_error_literal(error, FIV_IO_ERROR, FIV_IO_ERROR_OPEN, message);
} }
static void add_warning(const FivIoOpenContext *ctx, const char *format, ...)
G_GNUC_PRINTF(2, 3);
static void
add_warning(const FivIoOpenContext *ctx, const char *format, ...)
{
va_list ap;
va_start(ap, format);
if (ctx->warnings)
g_ptr_array_add(ctx->warnings, g_strdup_vprintf(format, ap));
else
g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, format, ap);
va_end(ap);
}
static bool static bool
try_append_page(cairo_surface_t *surface, cairo_surface_t **result, try_append_page(cairo_surface_t *surface, cairo_surface_t **result,
cairo_surface_t **result_tail) cairo_surface_t **result_tail)
@ -763,10 +778,10 @@ fail:
// since they depend on C++, which is undesirable. // since they depend on C++, which is undesirable.
static cairo_surface_t * static cairo_surface_t *
open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
FivIoProfile profile, GError **error) const FivIoOpenContext *ioctx, GError **error)
{ {
struct load_wuffs_frame_context ctx = { struct load_wuffs_frame_context ctx = {
.dec = dec, .src = &src, .target = profile}; .dec = dec, .src = &src, .target = ioctx->screen_profile};
// TODO(p): PNG text chunks (Wuffs #58). // TODO(p): PNG text chunks (Wuffs #58).
// TODO(p): See if something could and should be done about // TODO(p): See if something could and should be done about
@ -801,21 +816,21 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
switch (wuffs_base__more_information__metadata__fourcc(&minfo)) { switch (wuffs_base__more_information__metadata__fourcc(&minfo)) {
case WUFFS_BASE__FOURCC__EXIF: case WUFFS_BASE__FOURCC__EXIF:
if (ctx.meta_exif) { if (ctx.meta_exif) {
g_warning("ignoring repeated Exif"); add_warning(ioctx, "ignoring repeated Exif");
break; break;
} }
ctx.meta_exif = bytes; ctx.meta_exif = bytes;
continue; continue;
case WUFFS_BASE__FOURCC__ICCP: case WUFFS_BASE__FOURCC__ICCP:
if (ctx.meta_iccp) { if (ctx.meta_iccp) {
g_warning("ignoring repeated ICC profile"); add_warning(ioctx, "ignoring repeated ICC profile");
break; break;
} }
ctx.meta_iccp = bytes; ctx.meta_iccp = bytes;
continue; continue;
case WUFFS_BASE__FOURCC__XMP: case WUFFS_BASE__FOURCC__XMP:
if (ctx.meta_xmp) { if (ctx.meta_xmp) {
g_warning("ignoring repeated XMP"); add_warning(ioctx, "ignoring repeated XMP");
break; break;
} }
ctx.meta_xmp = bytes; ctx.meta_xmp = bytes;
@ -918,7 +933,8 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
} }
while (load_wuffs_frame(&ctx, error)) while (load_wuffs_frame(&ctx, error))
; if (ioctx->first_frame_only)
break;
// Wrap the chain around, since our caller receives only one pointer. // Wrap the chain around, since our caller receives only one pointer.
if (ctx.result) if (ctx.result)
@ -936,7 +952,7 @@ fail:
static cairo_surface_t * static cairo_surface_t *
open_wuffs_using(wuffs_base__image_decoder *(*allocate)(), open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
const gchar *data, gsize len, FivIoProfile profile, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
wuffs_base__image_decoder *dec = allocate(); wuffs_base__image_decoder *dec = allocate();
if (!dec) { if (!dec) {
@ -946,7 +962,7 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
cairo_surface_t *surface = cairo_surface_t *surface =
open_wuffs(dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE), open_wuffs(dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE),
profile, error); ctx, error);
free(dec); free(dec);
return surface; return surface;
} }
@ -1069,7 +1085,7 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
static cairo_surface_t * static cairo_surface_t *
open_libjpeg_turbo( open_libjpeg_turbo(
const gchar *data, gsize len, FivIoProfile profile, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
// Note that there doesn't seem to be much of a point in using this // Note that there doesn't seem to be much of a point in using this
// simplified API anymore, because JPEG-QS needs the original libjpeg API. // simplified API anymore, because JPEG-QS needs the original libjpeg API.
@ -1112,7 +1128,7 @@ open_libjpeg_turbo(
cairo_image_surface_get_data(surface), width, stride, height, cairo_image_surface_get_data(surface), width, stride, height,
pixel_format, TJFLAG_ACCURATEDCT)) { pixel_format, TJFLAG_ACCURATEDCT)) {
if (tjGetErrorCode(dec) == TJERR_WARNING) { if (tjGetErrorCode(dec) == TJERR_WARNING) {
g_warning("%s", tjGetErrorStr2(dec)); add_warning(ctx, "%s", tjGetErrorStr2(dec));
} else { } else {
set_error(error, tjGetErrorStr2(dec)); set_error(error, tjGetErrorStr2(dec));
cairo_surface_destroy(surface); cairo_surface_destroy(surface);
@ -1121,7 +1137,7 @@ open_libjpeg_turbo(
} }
} }
load_jpeg_finalize(surface, use_cmyk, profile, data, len); load_jpeg_finalize(surface, use_cmyk, ctx->screen_profile, data, len);
tjDestroy(dec); tjDestroy(dec);
return surface; return surface;
} }
@ -1148,7 +1164,7 @@ libjpeg_error_exit(j_common_ptr cinfo)
static cairo_surface_t * static cairo_surface_t *
open_libjpeg_enhanced( open_libjpeg_enhanced(
const gchar *data, gsize len, FivIoProfile profile, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
cairo_surface_t *volatile surface = NULL; cairo_surface_t *volatile surface = NULL;
@ -1208,7 +1224,7 @@ open_libjpeg_enhanced(
surface_data, cinfo.output_width * cinfo.output_height); surface_data, cinfo.output_width * cinfo.output_height);
(void) jpegqs_finish_decompress(&cinfo); (void) jpegqs_finish_decompress(&cinfo);
load_jpeg_finalize(surface, use_cmyk, profile, data, len); load_jpeg_finalize(surface, use_cmyk, ctx->screen_profile, data, len);
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&cinfo);
return surface; return surface;
} }
@ -1246,7 +1262,7 @@ load_libwebp_error(VP8StatusCode err)
static cairo_surface_t * static cairo_surface_t *
load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd, load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
bool premultiply, GError **error) const FivIoOpenContext *ctx, GError **error)
{ {
cairo_surface_t *surface = cairo_image_surface_create( cairo_surface_t *surface = cairo_image_surface_create(
config->input.has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, config->input.has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
@ -1268,6 +1284,7 @@ load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
config->output.u.RGBA.size = config->output.u.RGBA.size =
config->output.u.RGBA.stride * cairo_image_surface_get_height(surface); config->output.u.RGBA.stride * cairo_image_surface_get_height(surface);
bool premultiply = !ctx->screen_profile;
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
config->output.colorspace = premultiply ? MODE_bgrA : MODE_BGRA; config->output.colorspace = premultiply ? MODE_bgrA : MODE_BGRA;
else else
@ -1295,7 +1312,7 @@ load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd,
return NULL; return NULL;
} }
g_warning("partial WebP"); add_warning(ctx, "image file is truncated");
if (config->input.has_alpha) if (config->input.has_alpha)
return surface; return surface;
@ -1355,8 +1372,10 @@ load_libwebp_frame(WebPAnimDecoder *dec, const WebPAnimInfo *info,
} }
static cairo_surface_t * static cairo_surface_t *
load_libwebp_animated(const WebPData *wd, bool premultiply, GError **error) load_libwebp_animated(
const WebPData *wd, const FivIoOpenContext *ctx, GError **error)
{ {
bool premultiply = !ctx->screen_profile;
WebPAnimDecoderOptions options = {}; WebPAnimDecoderOptions options = {};
WebPAnimDecoderOptionsInit(&options); WebPAnimDecoderOptionsInit(&options);
options.use_threads = true; options.use_threads = true;
@ -1406,8 +1425,8 @@ fail:
} }
static cairo_surface_t * static cairo_surface_t *
open_libwebp(const gchar *data, gsize len, const gchar *uri, open_libwebp(
FivIoProfile target, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
// It is wholly zero-initialized by libwebp. // It is wholly zero-initialized by libwebp.
WebPDecoderConfig config = {}; WebPDecoderConfig config = {};
@ -1426,8 +1445,8 @@ open_libwebp(const gchar *data, gsize len, const gchar *uri,
} }
cairo_surface_t *result = config.input.has_animation cairo_surface_t *result = config.input.has_animation
? load_libwebp_animated(&wd, !target, error) ? load_libwebp_animated(&wd, ctx, error)
: load_libwebp_nonanimated(&config, &wd, !target, error); : load_libwebp_nonanimated(&config, &wd, ctx, error);
if (!result) if (!result)
goto fail; goto fail;
@ -1435,7 +1454,7 @@ open_libwebp(const gchar *data, gsize len, const gchar *uri,
WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR; WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR;
WebPDemuxer *demux = WebPDemuxPartial(&wd, &state); WebPDemuxer *demux = WebPDemuxPartial(&wd, &state);
if (!demux) { if (!demux) {
g_warning("%s: %s", uri, "demux failure"); add_warning(ctx, "demux failure while reading metadata");
goto fail; goto fail;
} }
@ -1477,8 +1496,8 @@ open_libwebp(const gchar *data, gsize len, const gchar *uri,
} }
WebPDemuxDelete(demux); WebPDemuxDelete(demux);
if (target) { if (ctx->screen_profile) {
fiv_io_profile_xrgb32_page(result, target); fiv_io_profile_xrgb32_page(result, ctx->screen_profile);
fiv_io_premultiply_argb32_page(result); fiv_io_premultiply_argb32_page(result);
} }
@ -1677,16 +1696,19 @@ load_resvg_error(int err)
} }
static cairo_surface_t * static cairo_surface_t *
open_resvg(const gchar *data, gsize len, const gchar *uri, GError **error) open_resvg(
const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
GFile *file = g_file_new_for_uri(uri); GFile *file = g_file_new_for_uri(ctx->uri);
GFile *base_file = g_file_get_parent(file); GFile *base_file = g_file_get_parent(file);
g_object_unref(file); g_object_unref(file);
resvg_options *opt = resvg_options_create(); resvg_options *opt = resvg_options_create();
resvg_options_load_system_fonts(opt); resvg_options_load_system_fonts(opt);
resvg_options_set_resources_dir(opt, g_file_peek_path(base_file)); resvg_options_set_resources_dir(opt, g_file_peek_path(base_file));
// TODO(p): Acquire and set the right DPI for use. if (ctx->screen_dpi)
resvg_options_set_dpi(opt, ctx->screen_dpi);
resvg_render_tree *tree = NULL; resvg_render_tree *tree = NULL;
int err = resvg_parse_tree_from_data(data, len, opt, &tree); int err = resvg_parse_tree_from_data(data, len, opt, &tree);
resvg_options_destroy(opt); resvg_options_destroy(opt);
@ -1764,9 +1786,10 @@ load_librsvg_render(FivIoRenderClosure *closure, double scale)
} }
static cairo_surface_t * static cairo_surface_t *
open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error) open_librsvg(
const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
GFile *base_file = g_file_new_for_uri(uri); GFile *base_file = g_file_new_for_uri(ctx->uri);
GInputStream *is = g_memory_input_stream_new_from_data(data, len, NULL); GInputStream *is = g_memory_input_stream_new_from_data(data, len, NULL);
RsvgHandle *handle = rsvg_handle_new_from_stream_sync( RsvgHandle *handle = rsvg_handle_new_from_stream_sync(
is, base_file, RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA, NULL, error); is, base_file, RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA, NULL, error);
@ -1775,8 +1798,7 @@ open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error)
if (!handle) if (!handle)
return NULL; return NULL;
// TODO(p): Acquire this from somewhere else. rsvg_handle_set_dpi(handle, ctx->screen_dpi);
rsvg_handle_set_dpi(handle, 96);
double w = 0, h = 0; double w = 0, h = 0;
#if LIBRSVG_CHECK_VERSION(2, 51, 0) #if LIBRSVG_CHECK_VERSION(2, 51, 0)
@ -2075,8 +2097,9 @@ fail:
} }
static void static void
load_libheif_aux_images(const gchar *uri, struct heif_image_handle *top, load_libheif_aux_images(const FivIoOpenContext *ioctx,
cairo_surface_t **result, cairo_surface_t **result_tail) struct heif_image_handle *top, cairo_surface_t **result,
cairo_surface_t **result_tail)
{ {
// Include the depth image, we have no special processing for it now. // Include the depth image, we have no special processing for it now.
int filter = LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA; int filter = LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA;
@ -2089,14 +2112,14 @@ load_libheif_aux_images(const gchar *uri, struct heif_image_handle *top,
struct heif_error err = struct heif_error err =
heif_image_handle_get_auxiliary_image_handle(top, ids[i], &handle); heif_image_handle_get_auxiliary_image_handle(top, ids[i], &handle);
if (err.code != heif_error_Ok) { if (err.code != heif_error_Ok) {
g_warning("%s: %s", uri, err.message); add_warning(ioctx, "%s", err.message);
continue; continue;
} }
GError *e = NULL; GError *e = NULL;
if (!try_append_page( if (!try_append_page(
load_libheif_image(handle, &e), result, result_tail)) { load_libheif_image(handle, &e), result, result_tail)) {
g_warning("%s: %s", uri, e->message); add_warning(ioctx, "%s", e->message);
g_error_free(e); g_error_free(e);
} }
@ -2107,8 +2130,8 @@ load_libheif_aux_images(const gchar *uri, struct heif_image_handle *top,
} }
static cairo_surface_t * static cairo_surface_t *
open_libheif(const gchar *data, gsize len, const gchar *uri, open_libheif(
FivIoProfile profile, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ioctx, GError **error)
{ {
// libheif will throw C++ exceptions on allocation failures. // libheif will throw C++ exceptions on allocation failures.
// The library is generally awful through and through. // The library is generally awful through and through.
@ -2129,19 +2152,19 @@ open_libheif(const gchar *data, gsize len, const gchar *uri,
struct heif_image_handle *handle = NULL; struct heif_image_handle *handle = NULL;
err = heif_context_get_image_handle(ctx, ids[i], &handle); err = heif_context_get_image_handle(ctx, ids[i], &handle);
if (err.code != heif_error_Ok) { if (err.code != heif_error_Ok) {
g_warning("%s: %s", uri, err.message); add_warning(ioctx, "%s", err.message);
continue; continue;
} }
GError *e = NULL; GError *e = NULL;
if (!try_append_page( if (!try_append_page(
load_libheif_image(handle, &e), &result, &result_tail)) { load_libheif_image(handle, &e), &result, &result_tail)) {
g_warning("%s: %s", uri, e->message); add_warning(ioctx, "%s", e->message);
g_error_free(e); g_error_free(e);
} }
// TODO(p): Possibly add thumbnail images as well. // TODO(p): Possibly add thumbnail images as well.
load_libheif_aux_images(uri, handle, &result, &result_tail); load_libheif_aux_images(ioctx, handle, &result, &result_tail);
heif_image_handle_release(handle); heif_image_handle_release(handle);
} }
if (!result) { if (!result) {
@ -2152,13 +2175,14 @@ open_libheif(const gchar *data, gsize len, const gchar *uri,
g_free(ids); g_free(ids);
fail_read: fail_read:
heif_context_free(ctx); heif_context_free(ctx);
return fiv_io_profile_finalize(result, profile); return fiv_io_profile_finalize(result, ioctx->screen_profile);
} }
#endif // HAVE_LIBHEIF -------------------------------------------------------- #endif // HAVE_LIBHEIF --------------------------------------------------------
#ifdef HAVE_LIBTIFF //--------------------------------------------------------- #ifdef HAVE_LIBTIFF //---------------------------------------------------------
struct fiv_io_tiff { struct fiv_io_tiff {
const FivIoOpenContext *ctx;
unsigned char *data; unsigned char *data;
gchar *error; gchar *error;
@ -2241,7 +2265,7 @@ fiv_io_tiff_error(
if (io->error) if (io->error)
// I'm not sure if two errors can ever come in a succession, // I'm not sure if two errors can ever come in a succession,
// but make sure to log them in any case. // but make sure to log them in any case.
g_warning("tiff: %s: %s", module, message); add_warning(io->ctx, "%s: %s", module, message);
else else
io->error = g_strconcat(module, ": ", message, NULL); io->error = g_strconcat(module, ": ", message, NULL);
g_free(message); g_free(message);
@ -2335,8 +2359,8 @@ fail:
} }
static cairo_surface_t * static cairo_surface_t *
open_libtiff(const gchar *data, gsize len, const gchar *uri, open_libtiff(
FivIoProfile target, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
// Both kinds of handlers are called, redirect everything. // Both kinds of handlers are called, redirect everything.
TIFFErrorHandler eh = TIFFSetErrorHandler(NULL); TIFFErrorHandler eh = TIFFSetErrorHandler(NULL);
@ -2344,13 +2368,14 @@ open_libtiff(const gchar *data, gsize len, const gchar *uri,
TIFFErrorHandlerExt ehe = TIFFSetErrorHandlerExt(fiv_io_tiff_error); TIFFErrorHandlerExt ehe = TIFFSetErrorHandlerExt(fiv_io_tiff_error);
TIFFErrorHandlerExt whe = TIFFSetWarningHandlerExt(fiv_io_tiff_warning); TIFFErrorHandlerExt whe = TIFFSetWarningHandlerExt(fiv_io_tiff_warning);
struct fiv_io_tiff h = { struct fiv_io_tiff h = {
.ctx = ctx,
.data = (unsigned char *) data, .data = (unsigned char *) data,
.position = 0, .position = 0,
.len = len, .len = len,
}; };
cairo_surface_t *result = NULL, *result_tail = NULL; cairo_surface_t *result = NULL, *result_tail = NULL;
TIFF *tiff = TIFFClientOpen(uri, "rm" /* Avoid mmap. */, &h, TIFF *tiff = TIFFClientOpen(ctx->uri, "rm" /* Avoid mmap. */, &h,
fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek, fiv_io_tiff_read, fiv_io_tiff_write, fiv_io_tiff_seek,
fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL); fiv_io_tiff_close, fiv_io_tiff_size, NULL, NULL);
if (!tiff) if (!tiff)
@ -2385,7 +2410,7 @@ open_libtiff(const gchar *data, gsize len, const gchar *uri,
GError *err = NULL; GError *err = NULL;
if (!try_append_page( if (!try_append_page(
load_libtiff_directory(tiff, &err), &result, &result_tail)) { load_libtiff_directory(tiff, &err), &result, &result_tail)) {
g_warning("%s: %s", uri, err->message); add_warning(ctx, "%s", err->message);
g_error_free(err); g_error_free(err);
} }
} while (TIFFReadDirectory(tiff)); } while (TIFFReadDirectory(tiff));
@ -2408,7 +2433,7 @@ fail:
// TODO(p): Colour management even for un/associated alpha channels. // TODO(p): Colour management even for un/associated alpha channels.
// Note that TIFF has a number of fields that an ICC profile can be // Note that TIFF has a number of fields that an ICC profile can be
// constructed from--it's not a good idea to blindly assume sRGB. // constructed from--it's not a good idea to blindly assume sRGB.
return fiv_io_profile_finalize(result, target); return fiv_io_profile_finalize(result, ctx->screen_profile);
} }
#endif // HAVE_LIBTIFF -------------------------------------------------------- #endif // HAVE_LIBTIFF --------------------------------------------------------
@ -2439,7 +2464,7 @@ load_gdkpixbuf_argb32_unpremultiplied(GdkPixbuf *pixbuf)
static cairo_surface_t * static cairo_surface_t *
open_gdkpixbuf( open_gdkpixbuf(
const gchar *data, gsize len, FivIoProfile profile, GError **error) const gchar *data, gsize len, const FivIoOpenContext *ctx, GError **error)
{ {
// gdk-pixbuf controls the playback itself, there is no reliable method of // gdk-pixbuf controls the playback itself, there is no reliable method of
// extracting individual frames (due to loops). // extracting individual frames (due to loops).
@ -2449,7 +2474,8 @@ open_gdkpixbuf(
if (!pixbuf) if (!pixbuf)
return NULL; return NULL;
bool custom_argb32 = profile && gdk_pixbuf_get_has_alpha(pixbuf) && bool custom_argb32 = ctx->screen_profile &&
gdk_pixbuf_get_has_alpha(pixbuf) &&
gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB && gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB &&
gdk_pixbuf_get_n_channels(pixbuf) == 4 && gdk_pixbuf_get_n_channels(pixbuf) == 4 &&
gdk_pixbuf_get_bits_per_sample(pixbuf) == 8; gdk_pixbuf_get_bits_per_sample(pixbuf) == 8;
@ -2489,10 +2515,10 @@ open_gdkpixbuf(
g_object_unref(pixbuf); g_object_unref(pixbuf);
if (custom_argb32) { if (custom_argb32) {
fiv_io_profile_xrgb32_page(surface, profile); fiv_io_profile_xrgb32_page(surface, ctx->screen_profile);
fiv_io_premultiply_argb32_page(surface); fiv_io_premultiply_argb32_page(surface);
} else { } else {
surface = fiv_io_profile_finalize(surface, profile); surface = fiv_io_profile_finalize(surface, ctx->screen_profile);
} }
return surface; return surface;
} }
@ -2517,8 +2543,7 @@ cairo_user_data_key_t fiv_io_key_page_previous;
cairo_user_data_key_t fiv_io_key_render; cairo_user_data_key_t fiv_io_key_render;
cairo_surface_t * cairo_surface_t *
fiv_io_open( fiv_io_open(const FivIoOpenContext *ctx, GError **error)
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error)
{ {
// TODO(p): Don't always load everything into memory, test type first, // TODO(p): Don't always load everything into memory, test type first,
// so that we can reject non-pictures early. Wuffs only needs the first // so that we can reject non-pictures early. Wuffs only needs the first
@ -2534,22 +2559,21 @@ fiv_io_open(
// //
// gdk-pixbuf exposes its detection data through gdk_pixbuf_get_formats(). // gdk-pixbuf exposes its detection data through gdk_pixbuf_get_formats().
// This may also be unbounded, as per format_check(). // This may also be unbounded, as per format_check().
GFile *file = g_file_new_for_uri(uri); GFile *file = g_file_new_for_uri(ctx->uri);
gchar *data = NULL; gchar *data = NULL;
gsize len = 0; gsize len = 0;
if (!g_file_load_contents(file, NULL, &data, &len, NULL, error)) if (!g_file_load_contents(file, NULL, &data, &len, NULL, error))
return NULL; return NULL;
cairo_surface_t *surface = cairo_surface_t *surface = fiv_io_open_from_data(data, len, ctx, error);
fiv_io_open_from_data(data, len, uri, profile, enhance, error);
g_free(data); g_free(data);
return surface; return surface;
} }
cairo_surface_t * cairo_surface_t *
fiv_io_open_from_data(const char *data, size_t len, const gchar *uri, fiv_io_open_from_data(
FivIoProfile profile, gboolean enhance, GError **error) const char *data, size_t len, const FivIoOpenContext *ctx, GError **error)
{ {
wuffs_base__slice_u8 prefix = wuffs_base__slice_u8 prefix =
wuffs_base__make_slice_u8((uint8_t *) data, len); wuffs_base__make_slice_u8((uint8_t *) data, len);
@ -2561,30 +2585,30 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
// which is so far unsupported here. // which is so far unsupported here.
surface = open_wuffs_using( surface = open_wuffs_using(
wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder, data, len, wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder, data, len,
profile, error); ctx, error);
break; break;
case WUFFS_BASE__FOURCC__GIF: case WUFFS_BASE__FOURCC__GIF:
surface = open_wuffs_using( surface = open_wuffs_using(
wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder, data, len, wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder, data, len,
profile, error); ctx, error);
break; break;
case WUFFS_BASE__FOURCC__PNG: case WUFFS_BASE__FOURCC__PNG:
surface = open_wuffs_using( surface = open_wuffs_using(
wuffs_png__decoder__alloc_as__wuffs_base__image_decoder, data, len, wuffs_png__decoder__alloc_as__wuffs_base__image_decoder, data, len,
profile, error); ctx, error);
break; break;
case WUFFS_BASE__FOURCC__TGA: case WUFFS_BASE__FOURCC__TGA:
surface = open_wuffs_using( surface = open_wuffs_using(
wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder, data, len, wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder, data, len,
profile, error); ctx, error);
break; break;
case WUFFS_BASE__FOURCC__JPEG: case WUFFS_BASE__FOURCC__JPEG:
surface = enhance surface = ctx->enhance
? open_libjpeg_enhanced(data, len, profile, error) ? open_libjpeg_enhanced(data, len, ctx, error)
: open_libjpeg_turbo(data, len, profile, error); : open_libjpeg_turbo(data, len, ctx, error);
break; break;
case WUFFS_BASE__FOURCC__WEBP: case WUFFS_BASE__FOURCC__WEBP:
surface = open_libwebp(data, len, uri, profile, error); surface = open_libwebp(data, len, ctx, error);
break; break;
default: default:
#ifdef HAVE_LIBRAW // --------------------------------------------------------- #ifdef HAVE_LIBRAW // ---------------------------------------------------------
@ -2599,7 +2623,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
} }
#endif // HAVE_LIBRAW --------------------------------------------------------- #endif // HAVE_LIBRAW ---------------------------------------------------------
#ifdef HAVE_RESVG // ---------------------------------------------------------- #ifdef HAVE_RESVG // ----------------------------------------------------------
if ((surface = open_resvg(data, len, uri, error))) if ((surface = open_resvg(data, len, ctx, error)))
break; break;
if (error) { if (error) {
g_debug("%s", (*error)->message); g_debug("%s", (*error)->message);
@ -2607,7 +2631,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
} }
#endif // HAVE_RESVG ---------------------------------------------------------- #endif // HAVE_RESVG ----------------------------------------------------------
#ifdef HAVE_LIBRSVG // -------------------------------------------------------- #ifdef HAVE_LIBRSVG // --------------------------------------------------------
if ((surface = open_librsvg(data, len, uri, error))) if ((surface = open_librsvg(data, len, ctx, error)))
break; break;
// XXX: It doesn't look like librsvg can return sensible errors. // XXX: It doesn't look like librsvg can return sensible errors.
@ -2625,7 +2649,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
} }
#endif // HAVE_XCURSOR -------------------------------------------------------- #endif // HAVE_XCURSOR --------------------------------------------------------
#ifdef HAVE_LIBHEIF //--------------------------------------------------------- #ifdef HAVE_LIBHEIF //---------------------------------------------------------
if ((surface = open_libheif(data, len, uri, profile, error))) if ((surface = open_libheif(data, len, ctx, error)))
break; break;
if (error) { if (error) {
g_debug("%s", (*error)->message); g_debug("%s", (*error)->message);
@ -2634,7 +2658,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
#endif // HAVE_LIBHEIF -------------------------------------------------------- #endif // HAVE_LIBHEIF --------------------------------------------------------
#ifdef HAVE_LIBTIFF //--------------------------------------------------------- #ifdef HAVE_LIBTIFF //---------------------------------------------------------
// This needs to be positioned after LibRaw. // This needs to be positioned after LibRaw.
if ((surface = open_libtiff(data, len, uri, profile, error))) if ((surface = open_libtiff(data, len, ctx, error)))
break; break;
if (error) { if (error) {
g_debug("%s", (*error)->message); g_debug("%s", (*error)->message);
@ -2650,7 +2674,7 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri,
// Wuffs #71 and similar concerns make us default to it in all cases. // Wuffs #71 and similar concerns make us default to it in all cases.
if (!surface) { if (!surface) {
GError *err = NULL; GError *err = NULL;
if ((surface = open_gdkpixbuf(data, len, profile, &err))) { if ((surface = open_gdkpixbuf(data, len, ctx, &err))) {
g_clear_error(error); g_clear_error(error);
} else if (err->code == GDK_PIXBUF_ERROR_UNKNOWN_TYPE) { } else if (err->code == GDK_PIXBUF_ERROR_UNKNOWN_TYPE) {
g_error_free(err); g_error_free(err);

View File

@ -74,7 +74,7 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
extern cairo_user_data_key_t fiv_io_key_page_previous; extern cairo_user_data_key_t fiv_io_key_page_previous;
typedef struct _FivIoRenderClosure { typedef struct _FivIoRenderClosure {
/// The rendering is allowed to fail. /// The rendering is allowed to fail, returning NULL.
cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale); cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale);
} FivIoRenderClosure; } FivIoRenderClosure;
@ -83,10 +83,18 @@ typedef struct _FivIoRenderClosure {
/// The rendered image will not have this key. /// The rendered image will not have this key.
extern cairo_user_data_key_t fiv_io_key_render; extern cairo_user_data_key_t fiv_io_key_render;
cairo_surface_t *fiv_io_open( typedef struct {
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error); const char *uri; ///< Source URI
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len, FivIoProfile screen_profile; ///< Target colour space or NULL
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error); int screen_dpi; ///< Target DPI
gboolean enhance; ///< Enhance JPEG (currently)
gboolean first_frame_only; ///< Only interested in the 1st frame
GPtrArray *warnings; ///< String vector for non-fatal errors
} FivIoOpenContext;
cairo_surface_t *fiv_io_open(const FivIoOpenContext *ctx, GError **error);
cairo_surface_t *fiv_io_open_from_data(
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error);
// --- Filesystem -------------------------------------------------------------- // --- Filesystem --------------------------------------------------------------

View File

@ -252,15 +252,24 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
return FALSE; return FALSE;
} }
// TODO(p): Add a flag to avoid loading all pages and frames. FivIoOpenContext ctx = {
FivIoProfile sRGB = fiv_io_profile_new_sRGB(); .uri = g_file_get_uri(target),
.screen_profile = fiv_io_profile_new_sRGB(),
.screen_dpi = 96,
.first_frame_only = TRUE,
// Only using this array as a redirect.
.warnings = g_ptr_array_new_with_free_func(g_free),
};
gsize filesize = g_mapped_file_get_length(mf); gsize filesize = g_mapped_file_get_length(mf);
cairo_surface_t *surface = fiv_io_open_from_data( cairo_surface_t *surface = fiv_io_open_from_data(
g_mapped_file_get_contents(mf), filesize, path, sRGB, FALSE, error); g_mapped_file_get_contents(mf), filesize, &ctx, error);
g_mapped_file_unref(mf); g_mapped_file_unref(mf);
if (sRGB)
fiv_io_profile_free(sRGB); g_free((gchar *) ctx.uri);
g_ptr_array_free(ctx.warnings, TRUE);
if (ctx.screen_profile)
fiv_io_profile_free(ctx.screen_profile);
if (!surface) if (!surface)
return FALSE; return FALSE;
@ -285,7 +294,7 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
} }
// Without a CMM, no conversion is attempted. // Without a CMM, no conversion is attempted.
if (sRGB) { if (ctx.screen_profile) {
g_string_append_printf( g_string_append_printf(
thum, "%s%c%s%c", THUMB_COLORSPACE, 0, THUMB_COLORSPACE_SRGB, 0); thum, "%s%c%s%c", THUMB_COLORSPACE, 0, THUMB_COLORSPACE_SRGB, 0);
} }
@ -347,7 +356,8 @@ read_wide_thumbnail(
if (!thumbnail_uri) if (!thumbnail_uri)
return NULL; return NULL;
cairo_surface_t *surface = fiv_io_open(thumbnail_uri, NULL, FALSE, error); cairo_surface_t *surface =
fiv_io_open(&(FivIoOpenContext){.uri = thumbnail_uri}, error);
g_free(thumbnail_uri); g_free(thumbnail_uri);
if (!surface) if (!surface)
return NULL; return NULL;

View File

@ -1160,6 +1160,34 @@ fiv_view_init(FivView *self)
// --- Public interface -------------------------------------------------------- // --- Public interface --------------------------------------------------------
static cairo_surface_t *
open_without_swapping_in(FivView *self, const gchar *uri)
{
FivIoOpenContext ctx = {
.uri = uri,
.screen_profile = self->enable_cms ? self->screen_cms_profile : NULL,
.screen_dpi = 96, // TODO(p): Try to retrieve it from the screen.
.enhance = self->enhance,
.warnings = g_ptr_array_new_with_free_func(g_free),
};
GError *error = NULL;
cairo_surface_t *surface = fiv_io_open(&ctx, &error);
if (error) {
g_ptr_array_add(ctx.warnings, g_strdup(error->message));
g_error_free(error);
}
g_clear_pointer(&self->messages, g_free);
if (ctx.warnings->len) {
g_ptr_array_add(ctx.warnings, NULL);
self->messages = g_strjoinv("\n", (gchar **) ctx.warnings->pdata);
}
g_ptr_array_free(ctx.warnings, TRUE);
return surface;
}
// TODO(p): Progressive picture loading, or at least async/cancellable. // TODO(p): Progressive picture loading, or at least async/cancellable.
gboolean gboolean
fiv_view_set_uri(FivView *self, const gchar *uri) fiv_view_set_uri(FivView *self, const gchar *uri)
@ -1172,16 +1200,8 @@ fiv_view_set_uri(FivView *self, const gchar *uri)
G_OBJECT(self), view_properties[PROP_ENHANCE]); G_OBJECT(self), view_properties[PROP_ENHANCE]);
} }
GError *error = NULL; cairo_surface_t *surface = open_without_swapping_in(self, uri);
cairo_surface_t *surface = fiv_io_open(
uri, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, &error);
g_clear_pointer(&self->messages, g_free);
g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->image, cairo_surface_destroy);
if (error) {
self->messages = g_strdup(error->message);
g_error_free(error);
}
self->frame = self->page = NULL; self->frame = self->page = NULL;
self->image = surface; self->image = surface;
@ -1220,19 +1240,9 @@ frame_step(FivView *self, int step)
static gboolean static gboolean
reload(FivView *self) reload(FivView *self)
{ {
GError *error = NULL; cairo_surface_t *surface = open_without_swapping_in(self, self->uri);
cairo_surface_t *surface = fiv_io_open(self->uri,
self->enable_cms ? self->screen_cms_profile : NULL, self->enhance,
&error);
g_clear_pointer(&self->messages, g_free);
if (error) {
self->messages = g_strdup(error->message);
g_error_free(error);
}
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 (error) if (!surface)
return FALSE; return FALSE;
g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->image, cairo_surface_destroy);