diff options
-rw-r--r-- | README.adoc | 3 | ||||
-rw-r--r-- | docs/fiv.html | 2 | ||||
-rw-r--r-- | fiv-browser.c | 13 | ||||
-rw-r--r-- | fiv-context-menu.c | 11 | ||||
-rw-r--r-- | fiv-io-cmm.c | 3 | ||||
-rw-r--r-- | fiv-io.c | 7 | ||||
-rwxr-xr-x | fiv-reverse-search | 4 | ||||
-rw-r--r-- | fiv-thumbnail.c | 2 | ||||
-rw-r--r-- | fiv-view.c | 11 | ||||
-rw-r--r-- | meson.build | 11 | ||||
-rwxr-xr-x | msys2-configure.sh | 13 | ||||
-rwxr-xr-x | msys2-install.sh | 6 | ||||
-rwxr-xr-x | msys2-package.sh | 5 | ||||
m--------- | submodules/wuffs-mirror-release-c | 0 | ||||
-rw-r--r-- | tools/benchmark-raw.c | 428 |
15 files changed, 496 insertions, 23 deletions
diff --git a/README.adoc b/README.adoc index 9ca9e65..46740eb 100644 --- a/README.adoc +++ b/README.adoc @@ -38,6 +38,9 @@ You can get a package with the latest development version using Arch Linux's https://aur.archlinux.org/packages/fiv-git[AUR], or as a https://git.janouch.name/p/nixexprs[Nix derivation]. +https://janouch.name/cd[Windows installers can be found here], +you want the _x86_64_ version. + Building and Running -------------------- Build-only dependencies: diff --git a/docs/fiv.html b/docs/fiv.html index a458882..b4a5f33 100644 --- a/docs/fiv.html +++ b/docs/fiv.html @@ -16,7 +16,7 @@ q:lang(en):after { content: "’"; } <p class="details"> <span id="author">Přemysl Eric Janouch</span><br> <span id="email"><a href="mailto:p@janouch.name">p@janouch.name</a></span><br> -<span id="revnumber">version 0.0.0,</span> +<span id="revnumber">version 1.0.0,</span> <span id="revdate">2023-04-17</span> <p class="figure"><img src="fiv.webp" alt="fiv in browser and viewer modes"> diff --git a/fiv-browser.c b/fiv-browser.c index c9963f4..4a904f0 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -828,9 +828,18 @@ thumbnailer_next(Thumbnailer *t) "--thumbnail", fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", uri, NULL}; + GSubprocessLauncher *launcher = + g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE); +#ifdef G_OS_WIN32 + gchar *prefix = g_win32_get_package_installation_directory_of_module(NULL); + g_subprocess_launcher_set_cwd(launcher, prefix); + g_free(prefix); +#endif + GError *error = NULL; - t->minion = g_subprocess_newv(t->target->icon ? argv_faster : argv_slower, - G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error); + t->minion = g_subprocess_launcher_spawnv( + launcher, t->target->icon ? argv_faster : argv_slower, &error); + g_object_unref(launcher); if (error) { g_warning("%s", error->message); g_error_free(error); diff --git a/fiv-context-menu.c b/fiv-context-menu.c index 16460b6..678c616 100644 --- a/fiv-context-menu.c +++ b/fiv-context-menu.c @@ -185,15 +185,24 @@ info_spawn(GtkWidget *dialog, const char *path, GBytes *bytes_in) if (bytes_in) flags |= G_SUBPROCESS_FLAGS_STDIN_PIPE; + GSubprocessLauncher *launcher = g_subprocess_launcher_new(flags); +#ifdef G_OS_WIN32 + // Both to find wperl, and then to let wperl find the nearby exiftool. + gchar *prefix = g_win32_get_package_installation_directory_of_module(NULL); + g_subprocess_launcher_set_cwd(launcher, prefix); + g_free(prefix); +#endif + // TODO(p): Add a fallback to internal capabilities. // The simplest is to specify the filename and the resolution. GError *error = NULL; - GSubprocess *subprocess = g_subprocess_new(flags, &error, + GSubprocess *subprocess = g_subprocess_launcher_spawn(launcher, &error, #ifdef G_OS_WIN32 "wperl", #endif "exiftool", "-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary", "-quiet", "--", path, NULL); + g_object_unref(launcher); if (error) { info_redirect_error(dialog, error); return; diff --git a/fiv-io-cmm.c b/fiv-io-cmm.c index b131acf..8a8b8dc 100644 --- a/fiv-io-cmm.c +++ b/fiv-io-cmm.c @@ -400,7 +400,8 @@ fiv_io_cmm_argb32_premultiply(FivIoCmm *self, #else // ! HAVE_LCMS2 || LCMS_VERSION < 2130 static void -fiv_io_cmm_argb32(FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *) +fiv_io_cmm_argb32(G_GNUC_UNUSED FivIoCmm *self, G_GNUC_UNUSED FivIoImage *image, + G_GNUC_UNUSED FivIoProfile *source, G_GNUC_UNUSED FivIoProfile *target) { // TODO(p): Unpremultiply, transform, repremultiply. Or require lcms2>=2.13. } @@ -42,6 +42,8 @@ #include <libraw.h> #if LIBRAW_VERSION >= LIBRAW_MAKE_VERSION(0, 21, 0) #define LIBRAW_OPIONS_NO_MEMERR_CALLBACK 0 +#else +#define rawparams params #endif #endif // HAVE_LIBRAW #ifdef HAVE_RESVG @@ -59,6 +61,9 @@ #ifdef HAVE_LIBTIFF #include <tiff.h> #include <tiffio.h> +#ifndef TIFF_TMSIZE_T_MAX +#define TIFF_TMSIZE_T_MAX ((tmsize_t) (SIZE_MAX >> 1)) +#endif #endif // HAVE_LIBTIFF #ifdef HAVE_GDKPIXBUF #include <gdk-pixbuf/gdk-pixbuf.h> @@ -971,7 +976,7 @@ static uint32_t * parse_mpf_index_entries(const struct tiffer *T, struct tiffer_entry *entry) { uint32_t count = entry->remaining_count / 16; - uint32_t *offsets = g_malloc0_n(sizeof *offsets, count + 1), *out = offsets; + uint32_t *offsets = g_malloc0_n(count + 1, sizeof *offsets), *out = offsets; for (uint32_t i = 0; i < count; i++) { // 5.2.3.3.3. Individual Image Data Offset uint32_t offset = parse_mpf_mpentry(entry->p + i * 16, T); diff --git a/fiv-reverse-search b/fiv-reverse-search index 5210703..dc12790 100755 --- a/fiv-reverse-search +++ b/fiv-reverse-search @@ -5,5 +5,5 @@ if [ "$#" -ne 2 ]; then fi xdg-open "$1$(fiv --thumbnail-for-search large "$2" \ - | curl --silent --show-error --upload-file - https://transfer.sh/image \ - | jq --slurp --raw-input --raw-output @uri)" + | curl --silent --show-error --form 'files[]=@-' https://uguu.se/upload \ + | jq --raw-output '.files[] | .url | @uri')" diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 80f378e..fffbac7 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -392,7 +392,7 @@ extract_libraw_unpack(libraw_data_t *iprc, int *flip, GError **error) // The main image's "flip" often matches up, but sometimes doesn't, e.g.: // - Phase One/H 25/H25_Outdoor_.IIQ // - Phase One/H 25/H25_IT8.7-2_Card.TIF - *flip = iprc->sizes.flip + *flip = iprc->sizes.flip; return TRUE; } @@ -646,7 +646,8 @@ reload_screen_cms_profile(FivView *self, GdkWindow *window) gchar *data = NULL; gsize length = 0; if (g_file_get_contents(path, &data, &length, NULL)) - self->screen_cms_profile = fiv_io_profile_new(data, length); + self->screen_cms_profile = fiv_io_cmm_get_profile( + fiv_io_cmm_get_default(), data, length); g_free(data); } g_free(path); @@ -850,6 +851,10 @@ gl_draw(FivView *self, cairo_t *cr) cliph = allocation.height; } + int scale = gtk_widget_get_scale_factor(GTK_WIDGET(self)); + clipw *= scale; + cliph *= scale; + enum { SRC, DEST }; GLuint textures[2] = {}; glGenTextures(2, textures); @@ -957,12 +962,14 @@ gl_draw(FivView *self, cairo_t *cr) // XXX: Native GdkWindows send this to the software fallback path. // XXX: This only reliably alpha blends when using the software fallback, // such as with a native window, because 7237f5d in GTK+ 3 is a regression. + // (Introduced in 3.24.39, reverted in 3.24.42.) + // // We had to resort to rendering the checkerboard pattern in the shader. // Unfortunately, it is hard to retrieve the theme colours from CSS. GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(self)); cairo_translate(cr, dx, dy); gdk_cairo_draw_from_gl( - cr, window, textures[DEST], GL_TEXTURE, 1, 0, 0, clipw, cliph); + cr, window, textures[DEST], GL_TEXTURE, scale, 0, 0, clipw, cliph); gdk_gl_context_make_current(self->gl_context); glDeleteBuffers(1, &vertex_buffer); diff --git a/meson.build b/meson.build index 82e7978..be2f963 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ # vim: noet ts=4 sts=4 sw=4: project('fiv', 'c', default_options : ['c_std=gnu99', 'warning_level=2'], - version : '0.1.0', + version : '1.0.0', meson_version : '>=0.57') cc = meson.get_compiler('c') @@ -200,6 +200,9 @@ if get_option('tools').enabled() c_args: tools_c_args) endforeach + executable('benchmark-raw', 'tools/benchmark-raw.c', + objects : iolib, + dependencies : dependencies + tools_dependencies) if gdkpixbuf.found() executable('benchmark-io', 'tools/benchmark-io.c', objects : iolib, @@ -363,10 +366,12 @@ elif meson.is_cross_build() 'ProjectURL' : application_url, }), ) + msi = meson.project_name() + '-' + meson.project_version() + \ + '-' + host_machine.cpu() + '.msi' custom_target('package', - output : 'fiv.msi', + output : msi, command : [meson.current_source_dir() / 'msys2-package.sh', - host_machine.cpu(), 'fiv.msi', wxs], + host_machine.cpu(), msi, wxs], env : ['MESON_BUILD_ROOT=' + meson.current_build_dir(), 'MESON_SOURCE_ROOT=' + meson.current_source_dir()], console : true, diff --git a/msys2-configure.sh b/msys2-configure.sh index 2f43d0d..7b7724e 100755 --- a/msys2-configure.sh +++ b/msys2-configure.sh @@ -75,10 +75,15 @@ extract() { --exclude '*/share/man' --exclude '*/share/doc' done < db.want - bsdtar -xf exiftool.tar.gz - mv Image-ExifTool-*/exiftool bin - mv Image-ExifTool-*/lib/* lib/perl5/site_perl - rm -rf Image-ExifTool-* + # Don't require Perl, which may not exist anymore on i686: + # https://github.com/msys2/MINGW-packages/pull/20085 + if [ -d lib/perl5 ] + then + bsdtar -xf exiftool.tar.gz + mv Image-ExifTool-*/exiftool bin + mv Image-ExifTool-*/lib/* lib/perl5/site_perl + rm -rf Image-ExifTool-* + fi } configure() { diff --git a/msys2-install.sh b/msys2-install.sh index da2d2f1..4cebb73 100755 --- a/msys2-install.sh +++ b/msys2-install.sh @@ -12,15 +12,15 @@ fi # Copy binaries we directly or indirectly depend on. cp -p "$msys2_root"/bin/*.dll . -cp -p "$msys2_root"/bin/wperl.exe . -cp -p "$msys2_root"/bin/exiftool . +cp -p "$msys2_root"/bin/wperl.exe . || : +cp -p "$msys2_root"/bin/exiftool . || : # The console helper is only useful for debug builds. cp -p "$msys2_root"/bin/gspawn-*-helper*.exe . cp -pR "$msys2_root"/etc/ . mkdir -p lib cp -pR "$msys2_root"/lib/gdk-pixbuf-2.0/ lib -cp -pR "$msys2_root"/lib/perl5/ lib +cp -pR "$msys2_root"/lib/perl5/ lib || : mkdir -p share/glib-2.0/schemas cp -pR "$msys2_root"/share/glib-2.0/schemas/*.Settings.* share/glib-2.0/schemas mkdir -p share/icons diff --git a/msys2-package.sh b/msys2-package.sh index 363c36a..b929cbc 100755 --- a/msys2-package.sh +++ b/msys2-package.sh @@ -1,7 +1,8 @@ #!/bin/sh -e export LC_ALL=C cd "$MESON_BUILD_ROOT" -arch=$1 msi=$2 files=package-files.wxs destdir=$(pwd)/package +arch=$1 msi=$2 files=package-files.wxs +destdir=$(pwd)/package/${msi%.*} shift 2 # We're being passed host_machine.cpu(), which will be either x86 or x86_64. @@ -15,7 +16,7 @@ txt2rtf() { print "{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0 Tahoma;}}" print "\\f0\\fs24{\\pard\\sa240" } { - gsub(/\\/, "\\\\"); gsub(/{/, "\\{"); gsub(/}/, "\\}") + gsub(/\\/, "\\\\"); gsub(/[{]/, "\\{"); gsub(/[}]/, "\\}") if (!$0) { print "\\par}{\\pard\\sa240"; prefix = "" } else { print prefix $0; prefix = " " } } END { diff --git a/submodules/wuffs-mirror-release-c b/submodules/wuffs-mirror-release-c -Subproject c63c4a9348fb1b52a9b60a6eb62328a97d979d9 +Subproject 50869df0ea703b4f41b238bfe26aec6ec9c8688 diff --git a/tools/benchmark-raw.c b/tools/benchmark-raw.c new file mode 100644 index 0000000..a818efa --- /dev/null +++ b/tools/benchmark-raw.c @@ -0,0 +1,428 @@ +// +// benchmark-raw.c: measure loading times of raw images and their thumbnails +// +// This is a tool to help decide on criteria for fast thumbnail extraction. +// +// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name> +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include <gio/gio.h> +#include <jv.h> +#include <libraw.h> + +#include <stdbool.h> +#include <time.h> + +#if LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0) +#error LibRaw 0.21.0 or newer is required. +#endif + +#include "fiv-io.h" +#include "fiv-thumbnail.h" + +// --- Analysis ---------------------------------------------------------------- +// Functions duplicated from info.h and benchmark-io.c. + +static jv +add_to_subarray(jv o, const char *key, jv value) +{ + // Invalid values are not allocated, and we use up any valid one. + // Beware that jv_get() returns jv_null() rather than jv_invalid(). + // Also, the header comment is lying, jv_is_valid() doesn't unreference. + jv a = jv_object_get(jv_copy(o), jv_string(key)); + return jv_set(o, jv_string(key), + jv_is_valid(a) ? jv_array_append(a, value) : JV_ARRAY(value)); +} + +static jv +add_warning(jv o, const char *message) +{ + return add_to_subarray(o, "warnings", jv_string(message)); +} + +static jv +add_error(jv o, const char *message) +{ + return jv_object_set(o, jv_string("error"), jv_string(message)); +} + +static double +timestamp(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1.e9; +} + +// --- Raw image files --------------------------------------------------------- + +static bool extract_mode = false; + +// Copied function from fiv-thumbnail.c. +static FivIoImage * +orient_thumbnail(FivIoImage *image) +{ + if (image->orientation <= FivIoOrientation0) + return image; + + double w = 0, h = 0; + cairo_matrix_t matrix = + fiv_io_orientation_apply(image, image->orientation, &w, &h); + FivIoImage *oriented = fiv_io_image_new(image->format, w, h); + if (!oriented) { + g_warning("image allocation failure"); + return image; + } + + cairo_surface_t *surface = fiv_io_image_to_surface_noref(oriented); + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + + surface = fiv_io_image_to_surface(image); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); + cairo_pattern_set_matrix(cairo_get_source(cr), &matrix); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_destroy(cr); + return oriented; +} + +// Modified function from fiv-thumbnail.c. +static FivIoImage * +adjust_thumbnail(FivIoImage *thumbnail, double row_height) +{ + // Hardcode orientation. + FivIoOrientation orientation = thumbnail->orientation; + + double w = 0, h = 0; + cairo_matrix_t matrix = + fiv_io_orientation_apply(thumbnail, orientation, &w, &h); + + double scale_x = 1; + double scale_y = 1; + if (w > FIV_THUMBNAIL_WIDE_COEFFICIENT * h) { + scale_x = FIV_THUMBNAIL_WIDE_COEFFICIENT * row_height / w; + scale_y = round(scale_x * h) / h; + } else { + scale_y = row_height / h; + scale_x = round(scale_y * w) / w; + } + + // NOTE: Ignoring renderable images. + + if (orientation <= FivIoOrientation0 && scale_x == 1 && scale_y == 1) + return fiv_io_image_ref(thumbnail); + + cairo_format_t format = thumbnail->format; + int projected_width = round(scale_x * w); + int projected_height = round(scale_y * h); + FivIoImage *scaled = fiv_io_image_new( + (format == CAIRO_FORMAT_RGB24 || format == CAIRO_FORMAT_RGB30) + ? CAIRO_FORMAT_RGB24 + : CAIRO_FORMAT_ARGB32, + projected_width, projected_height); + if (!scaled) + 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_scale(cr, scale_x, scale_y); + + surface = fiv_io_image_to_surface_noref(thumbnail); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_surface_destroy(surface); + + cairo_pattern_t *pattern = cairo_get_source(cr); + // CAIRO_FILTER_BEST, for some reason, works bad with CAIRO_FORMAT_RGB30. + cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); + cairo_pattern_set_matrix(pattern, &matrix); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + + // NOTE: Ignoring silent cairo errors. + + cairo_destroy(cr); + return scaled; +} + +// Copied function from fiv-thumbnail.c. +// LibRaw does a weird permutation here, so follow the documentation, +// which assumes that mirrored orientations never happen. +static FivIoOrientation +extract_libraw_unflip(int flip) +{ + switch (flip) { + break; case 0: + return FivIoOrientation0; + break; case 3: + return FivIoOrientation180; + break; case 5: + return FivIoOrientation270; + break; case 6: + return FivIoOrientation90; + break; default: + return FivIoOrientationUnknown; + } +} + +// Modified function from fiv-thumbnail.c. +static FivIoImage * +extract_libraw_bitmap(libraw_processed_image_t *image, int flip) +{ + // Anything else is extremely rare. + if (image->colors != 3 || image->bits != 8) + return NULL; + + FivIoImage *I = fiv_io_image_new( + CAIRO_FORMAT_RGB24, image->width, image->height); + if (!I) + return NULL; + + guint32 *out = (guint32 *) I->data; + const unsigned char *in = image->data; + for (guint64 i = 0; i < image->width * image->height; in += 3) + out[i++] = in[0] << 16 | in[1] << 8 | in[2]; + + I->orientation = extract_libraw_unflip(flip); + return I; +} + +static jv +process_thumbnail( + jv o, FivIoOpenContext *ctx, libraw_data_t *iprc, int i) +{ + double since = timestamp(); + + int err = 0; + if ((err = libraw_unpack_thumb_ex(iprc, i))) { + if (err != LIBRAW_NO_THUMBNAIL) + o = add_warning(o, libraw_strerror(err)); + return o; + } + + libraw_thumbnail_item_t *item = iprc->thumbs_list.thumblist + i; + jv to = JV_OBJECT( + jv_string("width"), jv_number(item->twidth), + jv_string("height"), jv_number(item->theight)); + + libraw_processed_image_t *image = libraw_dcraw_make_mem_thumb(iprc, &err); + if (!image) { + o = add_warning(o, libraw_strerror(err)); + goto fail; + } + + FivIoImage *I = NULL; + FivIoOrientation orientation = 0; + switch (image->type) { + break; case LIBRAW_IMAGE_JPEG: + I = fiv_io_open_from_data( + (const char *) image->data, image->data_size, ctx, NULL); + orientation = I->orientation; + break; case LIBRAW_IMAGE_BITMAP: + I = extract_libraw_bitmap(image, item->tflip); + orientation = I->orientation; + break; default: + o = add_warning(o, "unsupported embedded thumbnail"); + } + if (!I) + goto fail_render; + + if (item->tflip != 0xffff && + extract_libraw_unflip(item->tflip) != orientation) { + gchar *m = g_strdup_printf("Orientation mismatch: tflip %d, Exif %d", + extract_libraw_unflip(item->tflip), orientation); + o = add_warning(o, m); + g_free(m); + } + + double width = 0, height = 0; + fiv_io_orientation_dimensions(I, orientation, &width, &height); + to = jv_set(to, jv_string("width"), jv_number(width)); + to = jv_set(to, jv_string("height"), jv_number(height)); + + to = jv_set(to, jv_string("pixels_percent"), + jv_number(100 * (width * height) / + ((float) iprc->sizes.iwidth * iprc->sizes.iheight))); + + float main_ratio = (float) iprc->sizes.iwidth / iprc->sizes.iheight; + to = jv_set(to, jv_string("ratio_difference_percent"), + jv_number(fabs((main_ratio - width / height) * 100))); + + // Resize, hardcode orientation. This may take a long time. + to = jv_set(to, jv_string("duration_decode_ms"), + jv_number((timestamp() - since) * 1000)); + fiv_io_image_unref(adjust_thumbnail(I, 256.)); + to = jv_set(to, jv_string("duration_ms"), + jv_number((timestamp() - since) * 1000)); + + // Luckily, large thumbnails are typically JPEGs, which don't need encoding. + gchar *path = NULL; + GError *error = NULL; + if (extract_mode && (path = g_filename_from_uri(ctx->uri, NULL, &error))) { + gchar *thumbnail_path = NULL; + if (image->type == LIBRAW_IMAGE_JPEG) { + thumbnail_path = g_strdup_printf("%s.thumb.%d.jpg", path, i); + g_file_set_contents(thumbnail_path, + (const char *) image->data, image->data_size, &error); + } else { + thumbnail_path = g_strdup_printf("%s.thumb.%d.webp", path, i); + I = orient_thumbnail(I); + fiv_io_save(I, I, NULL, thumbnail_path, &error); + } + + g_clear_pointer(&thumbnail_path, g_free); + g_clear_pointer(&path, g_free); + } + if (error) { + o = add_warning(o, error->message); + g_clear_error(&error); + } + + g_clear_pointer(&I, fiv_io_image_unref); +fail_render: + libraw_dcraw_clear_mem(image); +fail: + return add_to_subarray(o, "thumbnails", to); +} + +static jv +process_raw(jv o, const char *filename, const uint8_t *data, size_t len) +{ + libraw_data_t *iprc = libraw_init(LIBRAW_OPIONS_NO_DATAERR_CALLBACK); + if (!iprc) + return add_error(o, "failed to obtain a LibRaw handle"); + + // First, bail out if this isn't a raw image file. + int err = 0; + if ((err = libraw_open_buffer(iprc, data, len)) || + (err = libraw_adjust_sizes_info_only(iprc))) { + o = add_error(o, libraw_strerror(err)); + goto fail; + } + + // Run our entire stack, like the render() function in fiv-thumbnail.c does. + // Note that this may use the TIFF/EP shortcut code. + double since = timestamp(); + GFile *file = g_file_new_for_commandline_arg(filename); + FivIoCmm *cmm = fiv_io_cmm_get_default(); + FivIoOpenContext ctx = { + .uri = g_file_get_uri(file), + .cmm = cmm, + .screen_profile = fiv_io_cmm_get_profile_sRGB(cmm), + .screen_dpi = 96, + .warnings = g_ptr_array_new_with_free_func(g_free), + }; + g_clear_object(&file); + + // This is really slow, let's decouple the mode from measurement a bit. + if (!extract_mode) { + GError *error = NULL; + FivIoImage *image = + fiv_io_open_from_data((const char *) data, len, &ctx, &error); + if (!image) { + o = add_error(o, error->message); + g_error_free(error); + goto fail_context; + } + + // Resize, hardcode orientation. This may take a long time. + o = jv_set(o, jv_string("duration_decode_ms"), + jv_number((timestamp() - since) * 1000)); + fiv_io_image_unref(adjust_thumbnail(image, 256.)); + g_clear_pointer(&image, fiv_io_image_unref); + + o = jv_set(o, jv_string("duration_ms"), + jv_number((timestamp() - since) * 1000)); + } + + o = jv_set(o, jv_string("thumbnails"), jv_array()); + for (int i = 0; i < iprc->thumbs_list.thumbcount; i++) + o = process_thumbnail(o, &ctx, iprc, i); + +fail_context: + g_free((char *) ctx.uri); + if (ctx.screen_profile) + fiv_io_profile_free(ctx.screen_profile); + + for (guint i = 0; i < ctx.warnings->len; i++) + o = add_warning(o, ctx.warnings->pdata[i]); + g_ptr_array_free(ctx.warnings, TRUE); + +fail: + libraw_close(iprc); + return o; +} + +// --- I/O --------------------------------------------------------------------- + +static jv +do_file(const char *filename, jv o) +{ + const char *err = NULL; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + err = strerror(errno); + goto error; + } + + uint8_t *data = NULL, buf[256 << 10]; + size_t n, len = 0; + while ((n = fread(buf, sizeof *buf, sizeof buf / sizeof *buf, fp))) { + data = realloc(data, len + n); + memcpy(data + len, buf, n); + len += n; + } + if (ferror(fp)) { + err = strerror(errno); + goto error_read; + } + + o = process_raw(o, filename, data, len); + +error_read: + fclose(fp); + free(data); +error: + if (err) + o = add_error(o, err); + return o; +} + +int +main(int argc, char *argv[]) +{ + // We don't need to call gdk_cairo_surface_create_from_pixbuf() here, + // so don't bother initializing GDK. + + // A mode to just extract all thumbnails to files for closer inspection. + extract_mode = !!getenv("BENCHMARK_RAW_EXTRACT"); + + // XXX: Can't use `xargs -P0`, there's a risk of non-atomic writes. + // Usage: find . -type f -print0 | xargs -0 ./benchmark-raw + for (int i = 1; i < argc; i++) { + const char *filename = argv[i]; + + jv o = jv_object(); + o = jv_object_set(o, jv_string("filename"), jv_string(filename)); + o = do_file(filename, o); + jv_dumpf(o, stdout, 0 /* Might consider JV_PRINT_SORTED. */); + fputc('\n', stdout); + } + return 0; +} |