aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.adoc3
-rw-r--r--docs/fiv.html2
-rw-r--r--fiv-browser.c13
-rw-r--r--fiv-context-menu.c11
-rw-r--r--fiv-io-cmm.c3
-rw-r--r--fiv-io.c7
-rwxr-xr-xfiv-reverse-search4
-rw-r--r--fiv-thumbnail.c2
-rw-r--r--fiv-view.c11
-rw-r--r--meson.build11
-rwxr-xr-xmsys2-configure.sh13
-rwxr-xr-xmsys2-install.sh6
-rwxr-xr-xmsys2-package.sh5
m---------submodules/wuffs-mirror-release-c0
-rw-r--r--tools/benchmark-raw.c428
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.
}
diff --git a/fiv-io.c b/fiv-io.c
index 498b155..5c7e75d 100644
--- a/fiv-io.c
+++ b/fiv-io.c
@@ -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;
}
diff --git a/fiv-view.c b/fiv-view.c
index fb01b3a..cd3cc51 100644
--- a/fiv-view.c
+++ b/fiv-view.c
@@ -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;
+}