Compare commits

...

20 Commits

Author SHA1 Message Date
3bea18708f Bump version, update README.adoc
All checks were successful
Alpine 3.20 Success
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.5 Success
openSUSE 15.5 Success
2024-12-23 16:53:54 +01:00
ed8ba147ba Improve packaging directory structure
All checks were successful
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.5 Success
openSUSE 15.5 Success
Alpine 3.20 Success
2024-12-23 16:53:54 +01:00
c221a00c33 Improve MSI package names
All checks were successful
Alpine 3.20 Success
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.5 Success
openSUSE 15.5 Success
2024-12-23 16:12:35 +01:00
192ffa0de9 Update a comment 2024-07-27 08:43:56 +02:00
bac9fce4e0 Fix argument order in g_malloc0_n() usages
Some checks failed
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.5 Success
openSUSE 15.5 Success
Alpine 3.20 Scripts failed
2024-07-10 00:30:27 +02:00
2e9ea9b4e2 Do not rely on a particular CWD on Windows
on_app_activate() currently makes use of the CWD we are launched with,
so I'm choosing to not enforce it globally.
2024-07-10 00:29:49 +02:00
b34fe63198 Fix reverse image search
All checks were successful
Alpine 3.19 Success
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.3 Success
openSUSE 15.5 Success
It was only a matter of time before this would fail,
although I did not expect this to happen so soon.
2024-04-22 07:38:49 +02:00
3c8ddcaf26 Fix high-DPI scaling with OpenGL
All checks were successful
Alpine 3.19 Success
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.3 Success
openSUSE 15.5 Success
We used to render multiple copies (four for a scaling factor of 2).
2024-04-13 05:16:48 +02:00
e3ec07a19f Improve cross-compilation script compatibility
All checks were successful
Arch Linux AUR Success
Arch Linux Success
Alpine 3.19 Success
Debian Bookworm Success
OpenBSD 7.3 Success
Fedora 39 Success
openSUSE 15.5 Success
2024-04-07 01:06:46 +02:00
e57364cd97 Fix openSUSE 15.5 and Win32 builds 2024-04-06 23:56:47 +02:00
7330f07dd7 Fix LibRaw 0.20 compatibility 2024-03-28 16:03:40 +01:00
d68e09525c Update the screenshot
Taken on Ubuntu 23.10.  Unfortunately, on this distribution,
the dark mode of the theme doesn't apply to window titles.

The GNOME Shell's screenshot tool captures window shadows without
the background, and it can be used on unfocused windows as well.
2024-03-21 03:57:17 +01:00
115a7bab0f Fix a build issue, and a big endian conversion 2024-03-13 18:47:05 +01:00
91538aaba5 Add an experimental OpenGL renderer 2024-03-13 15:27:31 +01:00
c214e668d9 Resolve more GLib #2907 warnings 2024-02-24 00:54:29 +01:00
a5ebc697ad Do not restart all thumbnailers on new entries
This had the potential to create tons of unnecessary processes
doing the same job.

The change only covers moving or linking, not copying.
2024-01-30 02:34:05 +01:00
9ca18f52d5 Clean up thumbnailing 2024-01-30 02:16:17 +01:00
604594a8f1 Prepare for parallelized colour management
This rewrite is more or less necessary for:
 - colour-managed browser thumbnails,
 - asynchronous image loading,
 - turning fiv-io into a reusable library.

Little CMS has a fairly terrible API in this regard.
2024-01-28 01:48:28 +01:00
9acab00bcc Improve browser view styling 2024-01-26 21:00:30 +01:00
ae8dc3070a Partially circumvent a Little CMS bug 2024-01-26 19:55:31 +01:00
19 changed files with 969 additions and 367 deletions

View File

@@ -13,7 +13,7 @@ Features
photos, HEIC, AVIF, SVG, X11 cursors and TIFF, or whatever your gdk-pixbuf
modules manage to load.
- Employs high-performance file format libraries: Wuffs and libjpeg-turbo.
- Makes use of 30-bit X.org visuals, whenever it's possible and appropriate.
- Can make use of 30-bit X.org visuals, under certain conditions.
- Has a notion of pages, and tries to load all included content within files.
- Can keep the zoom and position when browsing, to help with comparing
zoomed-in images.
@@ -38,12 +38,15 @@ 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:
Meson, pkg-config, asciidoctor or asciidoc (recommended but optional) +
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
libturbojpeg, libwebp, librsvg-2.0 (for icons) +
libturbojpeg, libwebp, libepoxy, librsvg-2.0 (for icons) +
Optional dependencies: lcms2, Little CMS fast float plugin,
LibRaw, librsvg-2.0, xcursor, libheif, libtiff, ExifTool,
resvg (unstable API, needs to be requested explicitly) +

View File

@@ -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">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -92,7 +92,8 @@ struct _FivBrowser {
Thumbnailer *thumbnailers; ///< Parallelized thumbnailers
size_t thumbnailers_len; ///< Thumbnailers array size
GQueue thumbnailers_queue; ///< Queued up Entry pointers
GQueue thumbnailers_queue_1; ///< Queued up Entry pointers, hi-prio
GQueue thumbnailers_queue_2; ///< Queued up Entry pointers, lo-prio
GdkCursor *pointer; ///< Cached pointer cursor
cairo_pattern_t *glow; ///< CAIRO_FORMAT_A8 mask for corners
@@ -742,7 +743,7 @@ thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry)
if ((flags & FIV_IO_SERIALIZE_LOW_QUALITY)) {
cairo_surface_set_user_data(entry->thumbnail, &fiv_thumbnail_key_lq,
(void *) (intptr_t) 1, NULL);
g_queue_push_tail(&self->thumbnailers_queue, entry);
g_queue_push_tail(&self->thumbnailers_queue_2, entry);
}
entry_set_surface_user_data(entry);
@@ -796,13 +797,21 @@ on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data)
thumbnailer_next(t);
}
// TODO(p): Try to keep the minions alive (stdout will be a problem).
static gboolean
thumbnailer_next(Thumbnailer *t)
{
// TODO(p): Try to keep the minions alive (stdout will be a problem).
// Already have something to do, not a failure.
if (t->target)
return TRUE;
// They could have been removed via post-reload changes in the model.
FivBrowser *self = t->self;
if (!(t->target = g_queue_pop_head(&self->thumbnailers_queue)))
return FALSE;
do {
if (!(t->target = g_queue_pop_head(&self->thumbnailers_queue_1)) &&
!(t->target = g_queue_pop_head(&self->thumbnailers_queue_2)))
return FALSE;
} while (t->target->removed);
// Case analysis:
// - We haven't found any thumbnail for the entry at all
@@ -819,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);
@@ -839,7 +857,8 @@ thumbnailer_next(Thumbnailer *t)
static void
thumbnailers_abort(FivBrowser *self)
{
g_queue_clear(&self->thumbnailers_queue);
g_queue_clear(&self->thumbnailers_queue_1);
g_queue_clear(&self->thumbnailers_queue_2);
for (size_t i = 0; i < self->thumbnailers_len; i++) {
Thumbnailer *t = self->thumbnailers + i;
@@ -855,35 +874,35 @@ thumbnailers_abort(FivBrowser *self)
}
static void
thumbnailers_start(FivBrowser *self)
thumbnailers_enqueue(FivBrowser *self, Entry *entry)
{
thumbnailers_abort(self);
if (!self->model)
return;
GQueue lq = G_QUEUE_INIT;
for (guint i = 0; i < self->entries->len; i++) {
Entry *entry = self->entries->pdata[i];
if (entry->removed)
continue;
if (!entry->removed) {
if (entry->icon)
g_queue_push_tail(&self->thumbnailers_queue, entry);
g_queue_push_tail(&self->thumbnailers_queue_1, entry);
else if (cairo_surface_get_user_data(
entry->thumbnail, &fiv_thumbnail_key_lq))
g_queue_push_tail(&lq, entry);
}
while (!g_queue_is_empty(&lq)) {
g_queue_push_tail_link(
&self->thumbnailers_queue, g_queue_pop_head_link(&lq));
g_queue_push_tail(&self->thumbnailers_queue_2, entry);
}
}
static void
thumbnailers_deploy(FivBrowser *self)
{
for (size_t i = 0; i < self->thumbnailers_len; i++) {
if (!thumbnailer_next(self->thumbnailers + i))
break;
}
}
static void
thumbnailers_restart(FivBrowser *self)
{
thumbnailers_abort(self);
for (guint i = 0; i < self->entries->len; i++)
thumbnailers_enqueue(self, self->entries->pdata[i]);
thumbnailers_deploy(self);
}
// --- Boilerplate -------------------------------------------------------------
G_DEFINE_TYPE_EXTENDED(FivBrowser, fiv_browser, GTK_TYPE_WIDGET, 0,
@@ -1004,7 +1023,7 @@ set_item_size(FivBrowser *self, FivThumbnailSize size)
g_hash_table_remove_all(self->thumbnail_cache);
reload_thumbnails(self);
thumbnailers_start(self);
thumbnailers_restart(self);
g_object_notify_by_pspec(
G_OBJECT(self), browser_properties[PROP_THUMBNAIL_SIZE]);
@@ -1872,7 +1891,8 @@ fiv_browser_init(FivBrowser *self)
g_malloc0_n(self->thumbnailers_len, sizeof *self->thumbnailers);
for (size_t i = 0; i < self->thumbnailers_len; i++)
self->thumbnailers[i].self = self;
g_queue_init(&self->thumbnailers_queue);
g_queue_init(&self->thumbnailers_queue_1);
g_queue_init(&self->thumbnailers_queue_2);
set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL);
self->show_labels = FALSE;
@@ -1917,8 +1937,9 @@ on_model_reloaded(FivIoModel *model, FivBrowser *self)
fiv_browser_select(self, selected_uri);
g_free(selected_uri);
// Restarting thumbnailers is critical, because they keep Entry pointers.
reload_thumbnails(self);
thumbnailers_start(self);
thumbnailers_restart(self);
}
static void
@@ -1933,8 +1954,8 @@ on_model_changed(FivIoModel *model, FivIoModelEntry *old, FivIoModelEntry *new,
g_ptr_array_add(self->entries, entry);
reload_one_thumbnail(self, entry);
// TODO(p): Try to add to thumbnailer queue if already started.
thumbnailers_start(self);
thumbnailers_enqueue(self, entry);
thumbnailers_deploy(self);
return;
}
@@ -1960,8 +1981,9 @@ on_model_changed(FivIoModel *model, FivIoModelEntry *old, FivIoModelEntry *new,
// so that there's no jumping around. Or, a bit more properly,
// move the thumbnail cache entry to the new URI.
reload_one_thumbnail(self, found);
// TODO(p): Try to add to thumbnailer queue if already started.
thumbnailers_start(self);
// TODO(p): Rather cancel the entry in any running thumbnailer,
// remove it from queues, and _enqueue() + _deploy().
thumbnailers_restart(self);
} else {
found->removed = TRUE;
gtk_widget_queue_draw(GTK_WIDGET(self));

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
//
// fiv-profile.c: colour management
// fiv-io-cmm.c: colour management
//
// Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
//
@@ -23,6 +23,7 @@
#include "fiv-io.h"
// Colour management must be handled before RGB conversions.
// TODO(p): Make it also possible to use Skia's skcms.
#ifdef HAVE_LCMS2
#include <lcms2.h>
#endif // HAVE_LCMS2
@@ -30,124 +31,7 @@
#include <lcms2_fast_float.h>
#endif // HAVE_LCMS2_FAST_FLOAT
void
fiv_io_profile_init(void)
{
// TODO(p): Use Little CMS with contexts instead.
#ifdef HAVE_LCMS2_FAST_FLOAT
cmsPluginTHR(NULL, cmsFastFloatExtensions());
#endif // HAVE_LCMS2_FAST_FLOAT
}
FivIoProfile *
fiv_io_profile_new(const void *data, size_t len)
{
#ifdef HAVE_LCMS2
return cmsOpenProfileFromMemTHR(NULL, data, len);
#else
(void) data;
(void) len;
return NULL;
#endif
}
FivIoProfile *
fiv_io_profile_new_sRGB(void)
{
#ifdef HAVE_LCMS2
return cmsCreate_sRGBProfileTHR(NULL);
#else
return NULL;
#endif
}
FivIoProfile *
fiv_io_profile_new_parametric(
double gamma, double whitepoint[2], double primaries[6])
{
#ifdef HAVE_LCMS2
// TODO(p): Make sure to use the library in a thread-safe manner.
cmsContext context = NULL;
const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0};
const cmsCIExyYTRIPLE cmsP = {
{primaries[0], primaries[1], 1.0},
{primaries[2], primaries[3], 1.0},
{primaries[4], primaries[5], 1.0},
};
cmsToneCurve *curve = cmsBuildGamma(context, gamma);
if (!curve)
return NULL;
cmsHPROFILE profile = cmsCreateRGBProfileTHR(
context, &cmsWP, &cmsP, (cmsToneCurve *[3]){curve, curve, curve});
cmsFreeToneCurve(curve);
return profile;
#else
(void) gamma;
(void) whitepoint;
(void) primaries;
return NULL;
#endif
}
FivIoProfile *
fiv_io_profile_new_sRGB_gamma(double gamma)
{
return fiv_io_profile_new_parametric(gamma,
(double[2]){0.3127, 0.3290},
(double[6]){0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600});
}
FivIoProfile *
fiv_io_profile_new_from_bytes(GBytes *bytes)
{
gsize len = 0;
gconstpointer p = g_bytes_get_data(bytes, &len);
return fiv_io_profile_new(p, len);
}
GBytes *
fiv_io_profile_to_bytes(FivIoProfile *profile)
{
#ifdef HAVE_LCMS2
cmsUInt32Number len = 0;
(void) cmsSaveProfileToMem(profile, NULL, &len);
gchar *data = g_malloc0(len);
if (!cmsSaveProfileToMem(profile, data, &len)) {
g_free(data);
return NULL;
}
return g_bytes_new_take(data, len);
#else
(void) profile;
return NULL;
#endif
}
void
fiv_io_profile_free(FivIoProfile *self)
{
#ifdef HAVE_LCMS2
cmsCloseProfile(self);
#else
(void) self;
#endif
}
// --- Image loading -----------------------------------------------------------
// TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F.
#ifndef HAVE_LCMS2
#define FIV_IO_PROFILE_ARGB32 0
#define FIV_IO_PROFILE_4X16LE 0
#else
#define FIV_IO_PROFILE_ARGB32 \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8)
#define FIV_IO_PROFILE_4X16LE \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_16 : TYPE_BGRA_16_SE)
#endif
// --- CMM-independent transforms ----------------------------------------------
// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with
// ARGB/BGRA/XRGB/BGRX.
@@ -175,99 +59,6 @@ trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len)
}
}
void
fiv_io_profile_cmyk(
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
#ifndef HAVE_LCMS2
(void) source;
(void) target;
#else
cmsHTRANSFORM transform = NULL;
if (source && target) {
transform = cmsCreateTransformTHR(NULL, source, TYPE_CMYK_8_REV, target,
FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(
transform, image->data, image->data, image->width * image->height);
cmsDeleteTransform(transform);
return;
}
#endif
trivial_cmyk_to_host_byte_order_argb(
image->data, image->width * image->height);
}
static bool
fiv_io_profile_rgb_direct(unsigned char *data, int w, int h,
FivIoProfile *source, FivIoProfile *target,
uint32_t source_format, uint32_t target_format)
{
#ifndef HAVE_LCMS2
(void) data;
(void) w;
(void) h;
(void) source;
(void) source_format;
(void) target;
(void) target_format;
return false;
#else
// TODO(p): We should make this optional.
cmsHPROFILE src_fallback = NULL;
if (target && !source)
source = src_fallback = cmsCreate_sRGBProfileTHR(NULL);
cmsHTRANSFORM transform = NULL;
if (source && target) {
transform = cmsCreateTransformTHR(NULL,
source, source_format, target, target_format, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(transform, data, data, w * h);
cmsDeleteTransform(transform);
}
if (src_fallback)
cmsCloseProfile(src_fallback);
return transform != NULL;
#endif
}
static void
fiv_io_profile_xrgb32(
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
fiv_io_profile_rgb_direct(image->data, image->width, image->height,
source, target, FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32);
}
void
fiv_io_profile_4x16le_direct(unsigned char *data,
int w, int h, FivIoProfile *source, FivIoProfile *target)
{
fiv_io_profile_rgb_direct(data, w, h, source, target,
FIV_IO_PROFILE_4X16LE, FIV_IO_PROFILE_4X16LE);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void
fiv_io_profile_page(FivIoImage *page, FivIoProfile *target,
void (*frame_cb) (FivIoImage *, FivIoProfile *, FivIoProfile *))
{
FivIoProfile *source = NULL;
if (page->icc)
source = fiv_io_profile_new_from_bytes(page->icc);
// TODO(p): All animations need to be composited in a linear colour space.
for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next)
frame_cb(frame, source, target);
if (source)
fiv_io_profile_free(source);
}
// From libwebp, verified to exactly match [x * a / 255].
#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23)
@@ -289,29 +80,316 @@ fiv_io_premultiply_argb32(FivIoImage *image)
}
}
// --- Profiles ----------------------------------------------------------------
#ifdef HAVE_LCMS2
struct _FivIoProfile {
FivIoCmm *cmm;
cmsHPROFILE profile;
};
GBytes *
fiv_io_profile_to_bytes(FivIoProfile *profile)
{
cmsUInt32Number len = 0;
(void) cmsSaveProfileToMem(profile, NULL, &len);
gchar *data = g_malloc0(len);
if (!cmsSaveProfileToMem(profile, data, &len)) {
g_free(data);
return NULL;
}
return g_bytes_new_take(data, len);
}
static FivIoProfile *
fiv_io_profile_new(FivIoCmm *cmm, cmsHPROFILE profile)
{
FivIoProfile *self = g_new0(FivIoProfile, 1);
self->cmm = g_object_ref(cmm);
self->profile = profile;
return self;
}
void
fiv_io_profile_free(FivIoProfile *self)
{
cmsCloseProfile(self->profile);
g_clear_object(&self->cmm);
g_free(self);
}
#else // ! HAVE_LCMS2
GBytes *fiv_io_profile_to_bytes(FivIoProfile *) { return NULL; }
void fiv_io_profile_free(FivIoProfile *) {}
#endif // ! HAVE_LCMS2
// --- Contexts ----------------------------------------------------------------
#ifdef HAVE_LCMS2
struct _FivIoCmm {
GObject parent_instance;
cmsContext context;
// https://github.com/mm2/Little-CMS/issues/430
gboolean broken_premul;
};
G_DEFINE_TYPE(FivIoCmm, fiv_io_cmm, G_TYPE_OBJECT)
static void
fiv_io_cmm_finalize(GObject *gobject)
{
FivIoCmm *self = FIV_IO_CMM(gobject);
cmsDeleteContext(self->context);
G_OBJECT_CLASS(fiv_io_cmm_parent_class)->finalize(gobject);
}
static void
fiv_io_cmm_class_init(FivIoCmmClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fiv_io_cmm_finalize;
}
static void
fiv_io_cmm_init(FivIoCmm *self)
{
self->context = cmsCreateContext(NULL, self);
#ifdef HAVE_LCMS2_FAST_FLOAT
if (cmsPluginTHR(self->context, cmsFastFloatExtensions()))
self->broken_premul = LCMS_VERSION <= 2160;
#endif // HAVE_LCMS2_FAST_FLOAT
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FivIoCmm *
fiv_io_cmm_get_default(void)
{
static gsize initialization_value = 0;
static FivIoCmm *default_ = NULL;
if (g_once_init_enter(&initialization_value)) {
gsize setup_value = 1;
default_ = g_object_new(FIV_TYPE_IO_CMM, NULL);
g_once_init_leave(&initialization_value, setup_value);
}
return default_;
}
FivIoProfile *
fiv_io_cmm_get_profile(FivIoCmm *self, const void *data, size_t len)
{
g_return_val_if_fail(self != NULL, NULL);
return fiv_io_profile_new(self,
cmsOpenProfileFromMemTHR(self->context, data, len));
}
FivIoProfile *
fiv_io_cmm_get_profile_sRGB(FivIoCmm *self)
{
g_return_val_if_fail(self != NULL, NULL);
return fiv_io_profile_new(self,
cmsCreate_sRGBProfileTHR(self->context));
}
FivIoProfile *
fiv_io_cmm_get_profile_parametric(FivIoCmm *self,
double gamma, double whitepoint[2], double primaries[6])
{
g_return_val_if_fail(self != NULL, NULL);
const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0};
const cmsCIExyYTRIPLE cmsP = {
{primaries[0], primaries[1], 1.0},
{primaries[2], primaries[3], 1.0},
{primaries[4], primaries[5], 1.0},
};
cmsToneCurve *curve = cmsBuildGamma(self->context, gamma);
if (!curve)
return NULL;
cmsHPROFILE profile = cmsCreateRGBProfileTHR(self->context,
&cmsWP, &cmsP, (cmsToneCurve *[3]){curve, curve, curve});
cmsFreeToneCurve(curve);
return fiv_io_profile_new(self, profile);
}
#else // ! HAVE_LCMS2
FivIoCmm *
fiv_io_cmm_get_default()
{
return NULL;
}
FivIoProfile *
fiv_io_cmm_get_profile(FivIoCmm *, const void *, size_t)
{
return NULL;
}
FivIoProfile *
fiv_io_cmm_get_profile_sRGB(FivIoCmm *)
{
return NULL;
}
FivIoProfile *
fiv_io_cmm_get_profile_parametric(FivIoCmm *, double, double[2], double[6])
{
return NULL;
}
#endif // ! HAVE_LCMS2
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FivIoProfile *
fiv_io_cmm_get_profile_sRGB_gamma(FivIoCmm *self, double gamma)
{
return fiv_io_cmm_get_profile_parametric(self, gamma,
(double[2]){0.3127, 0.3290},
(double[6]){0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600});
}
FivIoProfile *
fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes)
{
gsize len = 0;
gconstpointer p = g_bytes_get_data(bytes, &len);
return fiv_io_cmm_get_profile(self, p, len);
}
// --- Image loading -----------------------------------------------------------
#ifdef HAVE_LCMS2
// TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F.
#define FIV_IO_PROFILE_ARGB32 \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8)
#define FIV_IO_PROFILE_4X16LE \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_16 : TYPE_BGRA_16_SE)
void
fiv_io_cmm_cmyk(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
g_return_if_fail(target == NULL || self != NULL);
cmsHTRANSFORM transform = NULL;
if (source && target) {
transform = cmsCreateTransformTHR(self->context,
source->profile, TYPE_CMYK_8_REV,
target->profile, FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(
transform, image->data, image->data, image->width * image->height);
cmsDeleteTransform(transform);
return;
}
trivial_cmyk_to_host_byte_order_argb(
image->data, image->width * image->height);
}
static bool
fiv_io_cmm_rgb_direct(FivIoCmm *self, unsigned char *data, int w, int h,
FivIoProfile *source, FivIoProfile *target,
uint32_t source_format, uint32_t target_format)
{
g_return_val_if_fail(target == NULL || self != NULL, false);
// TODO(p): We should make this optional.
FivIoProfile *src_fallback = NULL;
if (target && !source)
source = src_fallback = fiv_io_cmm_get_profile_sRGB(self);
cmsHTRANSFORM transform = NULL;
if (source && target) {
transform = cmsCreateTransformTHR(self->context,
source->profile, source_format,
target->profile, target_format, INTENT_PERCEPTUAL, 0);
}
if (transform) {
cmsDoTransform(transform, data, data, w * h);
cmsDeleteTransform(transform);
}
if (src_fallback)
fiv_io_profile_free(src_fallback);
return transform != NULL;
}
static void
fiv_io_cmm_xrgb32(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
fiv_io_cmm_rgb_direct(self, image->data, image->width, image->height,
source, target, FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32);
}
void
fiv_io_cmm_4x16le_direct(FivIoCmm *self, unsigned char *data,
int w, int h, FivIoProfile *source, FivIoProfile *target)
{
fiv_io_cmm_rgb_direct(self, data, w, h, source, target,
FIV_IO_PROFILE_4X16LE, FIV_IO_PROFILE_4X16LE);
}
#else // ! HAVE_LCMS2
void
fiv_io_cmm_cmyk(FivIoCmm *, FivIoImage *image, FivIoProfile *, FivIoProfile *)
{
trivial_cmyk_to_host_byte_order_argb(
image->data, image->width * image->height);
}
static void
fiv_io_cmm_xrgb32(FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *)
{
}
void
fiv_io_cmm_4x16le_direct(
FivIoCmm *, unsigned char *, int, int, FivIoProfile *, FivIoProfile *)
{
}
#endif // ! HAVE_LCMS2
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if defined HAVE_LCMS2 && LCMS_VERSION >= 2130
#define FIV_IO_PROFILE_ARGB32_PREMUL \
(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8_PREMUL : TYPE_ARGB_8_PREMUL)
static void
fiv_io_profile_argb32(FivIoImage *image,
fiv_io_cmm_argb32(FivIoCmm *self, FivIoImage *image,
FivIoProfile *source, FivIoProfile *target)
{
g_return_if_fail(image->format == CAIRO_FORMAT_ARGB32);
fiv_io_profile_rgb_direct(image->data, image->width, image->height,
// TODO: With self->broken_premul,
// this probably also needs to be wrapped in un-premultiplication.
fiv_io_cmm_rgb_direct(self, image->data, image->width, image->height,
source, target,
FIV_IO_PROFILE_ARGB32_PREMUL, FIV_IO_PROFILE_ARGB32_PREMUL);
}
void
fiv_io_profile_argb32_premultiply(
fiv_io_cmm_argb32_premultiply(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
g_return_if_fail(target == NULL || self != NULL);
if (image->format != CAIRO_FORMAT_ARGB32) {
fiv_io_profile_xrgb32(image, source, target);
} else if (!fiv_io_profile_rgb_direct(image->data,
fiv_io_cmm_xrgb32(self, image, source, target);
} else if (!target || self->broken_premul) {
fiv_io_cmm_xrgb32(self, image, source, target);
fiv_io_premultiply_argb32(image);
} else if (!fiv_io_cmm_rgb_direct(self, image->data,
image->width, image->height, source, target,
FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32_PREMUL)) {
g_debug("failed to create a premultiplying transform");
@@ -321,23 +399,42 @@ fiv_io_profile_argb32_premultiply(
#else // ! HAVE_LCMS2 || LCMS_VERSION < 2130
// TODO(p): Unpremultiply, transform, repremultiply. Or require lcms2>=2.13.
#define fiv_io_profile_argb32(surface, source, target)
static void
fiv_io_profile_argb32_premultiply(
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.
}
void
fiv_io_cmm_argb32_premultiply(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
fiv_io_profile_xrgb32(image, source, target);
fiv_io_cmm_xrgb32(self, image, source, target);
fiv_io_premultiply_argb32(image);
}
#endif // ! HAVE_LCMS2 || LCMS_VERSION < 2130
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void
fiv_io_profile_any(
fiv_io_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target,
void (*frame_cb) (FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *))
{
FivIoProfile *source = NULL;
if (page->icc)
source = fiv_io_cmm_get_profile_from_bytes(self, page->icc);
// TODO(p): All animations need to be composited in a linear colour space.
for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next)
frame_cb(self, frame, source, target);
if (source)
fiv_io_profile_free(source);
}
void
fiv_io_cmm_any(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target)
{
// TODO(p): Ensure we do colour management early enough, so that
@@ -345,9 +442,9 @@ fiv_io_profile_any(
// and also for correct alpha compositing.
switch (image->format) {
break; case CAIRO_FORMAT_RGB24:
fiv_io_profile_xrgb32(image, source, target);
fiv_io_cmm_xrgb32(self, image, source, target);
break; case CAIRO_FORMAT_ARGB32:
fiv_io_profile_argb32(image, source, target);
fiv_io_cmm_argb32(self, image, source, target);
break; default:
g_debug("CM attempted on an unsupported surface format");
}
@@ -355,12 +452,12 @@ fiv_io_profile_any(
// TODO(p): Offer better integration, upgrade the bit depth if appropriate.
FivIoImage *
fiv_io_profile_finalize(FivIoImage *image, FivIoProfile *target)
fiv_io_cmm_finish(FivIoCmm *self, FivIoImage *image, FivIoProfile *target)
{
if (!target)
return image;
for (FivIoImage *page = image; page != NULL; page = page->page_next)
fiv_io_profile_page(page, target, fiv_io_profile_any);
fiv_io_cmm_page(self, page, target, fiv_io_cmm_any);
return image;
}

View File

@@ -247,7 +247,9 @@ static GPtrArray *
model_decide_placement(
FivIoModel *self, GFileInfo *info, GPtrArray *subdirs, GPtrArray *files)
{
if (self->filtering && g_file_info_get_is_hidden(info))
if (self->filtering &&
g_file_info_has_attribute(info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) &&
g_file_info_get_is_hidden(info))
return NULL;
if (g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY)
return subdirs;

110
fiv-io.c
View File

@@ -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>
@@ -379,6 +384,7 @@ struct load_wuffs_frame_context {
GBytes *meta_iccp; ///< Reference-counted ICC profile
GBytes *meta_xmp; ///< Reference-counted XMP
FivIoCmm *cmm; ///< CMM context, if any
FivIoProfile *target; ///< Target device profile, if any
FivIoProfile *source; ///< Source colour profile, if any
@@ -447,11 +453,12 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
if (ctx->target) {
if (ctx->expand_16_float || ctx->pack_16_10) {
fiv_io_profile_4x16le_direct(
fiv_io_cmm_4x16le_direct(ctx->cmm,
targetbuf, ctx->width, ctx->height, ctx->source, ctx->target);
// The first one premultiplies below, the second doesn't need to.
} else {
fiv_io_profile_argb32_premultiply(image, ctx->source, ctx->target);
fiv_io_cmm_argb32_premultiply(
ctx->cmm, image, ctx->source, ctx->target);
}
}
@@ -589,7 +596,8 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
const FivIoOpenContext *ioctx, GError **error)
{
struct load_wuffs_frame_context ctx = {
.dec = dec, .src = &src, .target = ioctx->screen_profile};
.dec = dec, .src = &src,
.cmm = ioctx->cmm, .target = ioctx->screen_profile};
// TODO(p): PNG text chunks, like we do with PNG thumbnails.
// TODO(p): See if something could and should be done about
@@ -673,9 +681,11 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
// TODO(p): Improve our simplistic PNG handling of: gAMA, cHRM, sRGB.
if (ctx.target) {
if (ctx.meta_iccp)
ctx.source = fiv_io_profile_new_from_bytes(ctx.meta_iccp);
ctx.source = fiv_io_cmm_get_profile_from_bytes(
ctx.cmm, ctx.meta_iccp);
else if (isfinite(gamma) && gamma > 0)
ctx.source = fiv_io_profile_new_sRGB_gamma(gamma);
ctx.source = fiv_io_cmm_get_profile_sRGB_gamma(
ctx.cmm, gamma);
}
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
@@ -966,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);
@@ -1059,7 +1069,7 @@ parse_exif_profile_subifd(
}
static FivIoProfile *
parse_exif_profile(const void *data, size_t len)
parse_exif_profile(FivIoCmm *cmm, const void *data, size_t len)
{
struct tiffer T = {};
if (!tiffer_init(&T, (const uint8_t *) data, len) || !tiffer_next_ifd(&T))
@@ -1091,7 +1101,7 @@ parse_exif_profile(const void *data, size_t len)
// If sRGB is claimed, assume all parameters are standard.
if (params.colorspace == Exif_ColorSpace_sRGB)
return fiv_io_profile_new_sRGB();
return fiv_io_cmm_get_profile_sRGB(cmm);
// AdobeRGB Nikon JPEGs provide all of these.
if (params.colorspace != Exif_ColorSpace_Uncalibrated ||
@@ -1100,7 +1110,7 @@ parse_exif_profile(const void *data, size_t len)
!params.have_primaries)
return NULL;
return fiv_io_profile_new_parametric(
return fiv_io_cmm_get_profile_parametric(cmm,
params.gamma, params.whitepoint, params.primaries);
}
@@ -1255,17 +1265,17 @@ load_jpeg_finalize(FivIoImage *image, bool cmyk,
g_byte_array_free(meta.icc, TRUE);
FivIoProfile *source = NULL;
if (icc_profile)
source = fiv_io_profile_new(
if (icc_profile && ctx->cmm)
source = fiv_io_cmm_get_profile(ctx->cmm,
g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile));
else if (image->exif)
source = parse_exif_profile(
else if (image->exif && ctx->cmm)
source = parse_exif_profile(ctx->cmm,
g_bytes_get_data(image->exif, NULL), g_bytes_get_size(image->exif));
if (cmyk)
fiv_io_profile_cmyk(image, source, ctx->screen_profile);
fiv_io_cmm_cmyk(ctx->cmm, image, source, ctx->screen_profile);
else
fiv_io_profile_any(image, source, ctx->screen_profile);
fiv_io_cmm_any(ctx->cmm, image, source, ctx->screen_profile);
if (source)
fiv_io_profile_free(source);
@@ -1533,9 +1543,11 @@ load_libwebp_frame(WebPAnimDecoder *dec, const WebPAnimInfo *info,
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
memcpy(dst, buf, area * sizeof *dst);
} else {
uint32_t *src = (uint32_t *) buf;
for (uint64_t i = 0; i < area; i++)
*dst++ = GUINT32_FROM_LE(*src++);
const uint32_t *src = (const uint32_t *) buf;
for (uint64_t i = 0; i < area; i++) {
uint32_t value = *src++;
*dst++ = GUINT32_FROM_LE(value);
}
}
// info->bgcolor is not reliable.
@@ -1666,7 +1678,8 @@ open_libwebp(
WebPDemuxDelete(demux);
if (ctx->screen_profile)
fiv_io_profile_argb32_premultiply_page(result, ctx->screen_profile);
fiv_io_cmm_argb32_premultiply_page(
ctx->cmm, result, ctx->screen_profile);
fail:
WebPFreeDecBuffer(&config.output);
@@ -2048,7 +2061,7 @@ open_libraw(
out:
libraw_close(iprc);
return fiv_io_profile_finalize(result, ctx->screen_profile);
return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile);
}
#endif // HAVE_LIBRAW ---------------------------------------------------------
@@ -2070,8 +2083,8 @@ load_resvg_destroy(FivIoRenderClosure *closure)
}
static FivIoImage *
load_resvg_render_internal(FivIoRenderClosureResvg *self,
double scale, FivIoProfile *target, GError **error)
load_resvg_render_internal(FivIoRenderClosureResvg *self, double scale,
FivIoCmm *cmm, FivIoProfile *target, GError **error)
{
double w = ceil(self->width * scale), h = ceil(self->height * scale);
if (w > SHRT_MAX || h > SHRT_MAX) {
@@ -2101,15 +2114,15 @@ load_resvg_render_internal(FivIoRenderClosureResvg *self,
uint32_t rgba = g_ntohl(pixels[i]);
pixels[i] = rgba << 24 | rgba >> 8;
}
return fiv_io_profile_finalize(image, target);
return fiv_io_cmm_finish(cmm, image, target);
}
static FivIoImage *
load_resvg_render(
FivIoRenderClosure *closure, FivIoProfile *target, double scale)
load_resvg_render(FivIoRenderClosure *closure,
FivIoCmm *cmm, FivIoProfile *target, double scale)
{
FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure;
return load_resvg_render_internal(self, scale, target, NULL);
return load_resvg_render_internal(self, scale, cmm, target, NULL);
}
static const char *
@@ -2167,8 +2180,8 @@ open_resvg(
closure->width = size.width;
closure->height = size.height;
FivIoImage *image =
load_resvg_render_internal(closure, 1., ctx->screen_profile, error);
FivIoImage *image = load_resvg_render_internal(
closure, 1., ctx->cmm, ctx->screen_profile, error);
if (!image) {
load_resvg_destroy(&closure->parent);
return NULL;
@@ -2198,7 +2211,7 @@ load_librsvg_destroy(FivIoRenderClosure *closure)
static FivIoImage *
load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale,
FivIoProfile *target, GError **error)
FivIoCmm *cmm, FivIoProfile *target, GError **error)
{
RsvgRectangle viewport = {.x = 0, .y = 0,
.width = self->width * scale, .height = self->height * scale};
@@ -2225,15 +2238,15 @@ load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale,
fiv_io_image_unref(image);
return NULL;
}
return fiv_io_profile_finalize(image, target);
return fiv_io_cmm_finish(cmm, image, target);
}
static FivIoImage *
load_librsvg_render(
FivIoRenderClosure *closure, FivIoProfile *target, double scale)
load_librsvg_render(FivIoRenderClosure *closure,
FivIoCmm *cmm, FivIoProfile *target, double scale)
{
FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure;
return load_librsvg_render_internal(self, scale, target, NULL);
return load_librsvg_render_internal(self, scale, cmm, target, NULL);
}
static FivIoImage *
@@ -2282,8 +2295,8 @@ open_librsvg(
// librsvg rasterizes filters, so rendering to a recording Cairo surface
// has been abandoned.
FivIoImage *image =
load_librsvg_render_internal(closure, 1., ctx->screen_profile, error);
FivIoImage *image = load_librsvg_render_internal(
closure, 1., ctx->cmm, ctx->screen_profile, error);
if (!image) {
load_librsvg_destroy(&closure->parent);
return NULL;
@@ -2599,7 +2612,7 @@ open_libheif(
g_free(ids);
fail_read:
heif_context_free(ctx);
return fiv_io_profile_finalize(result, ioctx->screen_profile);
return fiv_io_cmm_finish(ioctx->cmm, result, ioctx->screen_profile);
}
#endif // HAVE_LIBHEIF --------------------------------------------------------
@@ -2830,7 +2843,7 @@ fail:
TIFFSetWarningHandlerExt(whe);
TIFFSetErrorHandler(eh);
TIFFSetWarningHandler(wh);
return fiv_io_profile_finalize(result, ctx->screen_profile);
return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile);
}
#endif // HAVE_LIBTIFF --------------------------------------------------------
@@ -2925,9 +2938,10 @@ open_gdkpixbuf(
g_object_unref(pixbuf);
if (custom_argb32)
fiv_io_profile_argb32_premultiply_page(image, ctx->screen_profile);
fiv_io_cmm_argb32_premultiply_page(
ctx->cmm, image, ctx->screen_profile);
else
image = fiv_io_profile_finalize(image, ctx->screen_profile);
image = fiv_io_cmm_finish(ctx->cmm, image, ctx->screen_profile);
return image;
}
@@ -3444,34 +3458,40 @@ fiv_io_orientation_apply(const FivIoImage *image,
FivIoOrientation orientation, double *width, double *height)
{
fiv_io_orientation_dimensions(image, orientation, width, height);
return fiv_io_orientation_matrix(orientation, *width, *height);
}
cairo_matrix_t
fiv_io_orientation_matrix(
FivIoOrientation orientation, double width, double height)
{
cairo_matrix_t matrix = {};
cairo_matrix_init_identity(&matrix);
switch (orientation) {
case FivIoOrientation90:
cairo_matrix_rotate(&matrix, -M_PI_2);
cairo_matrix_translate(&matrix, -*width, 0);
cairo_matrix_translate(&matrix, -width, 0);
break;
case FivIoOrientation180:
cairo_matrix_scale(&matrix, -1, -1);
cairo_matrix_translate(&matrix, -*width, -*height);
cairo_matrix_translate(&matrix, -width, -height);
break;
case FivIoOrientation270:
cairo_matrix_rotate(&matrix, +M_PI_2);
cairo_matrix_translate(&matrix, 0, -*height);
cairo_matrix_translate(&matrix, 0, -height);
break;
case FivIoOrientationMirror0:
cairo_matrix_scale(&matrix, -1, +1);
cairo_matrix_translate(&matrix, -*width, 0);
cairo_matrix_translate(&matrix, -width, 0);
break;
case FivIoOrientationMirror90:
cairo_matrix_rotate(&matrix, +M_PI_2);
cairo_matrix_scale(&matrix, -1, +1);
cairo_matrix_translate(&matrix, -*width, -*height);
cairo_matrix_translate(&matrix, -width, -height);
break;
case FivIoOrientationMirror180:
cairo_matrix_scale(&matrix, +1, -1);
cairo_matrix_translate(&matrix, 0, -*height);
cairo_matrix_translate(&matrix, 0, -height);
break;
case FivIoOrientationMirror270:
cairo_matrix_rotate(&matrix, -M_PI_2);

View File

@@ -25,41 +25,50 @@
typedef enum _FivIoOrientation FivIoOrientation;
typedef struct _FivIoRenderClosure FivIoRenderClosure;
typedef struct _FivIoImage FivIoImage;
typedef struct _FivIoProfile FivIoProfile;
// --- Colour management -------------------------------------------------------
// Note that without a CMM, all FivIoCmm and FivIoProfile will be returned NULL.
void fiv_io_profile_init(void);
// TODO(p): Make it also possible to use Skia's skcms.
typedef void *FivIoProfile;
FivIoProfile *fiv_io_profile_new(const void *data, size_t len);
FivIoProfile *fiv_io_profile_new_from_bytes(GBytes *bytes);
FivIoProfile *fiv_io_profile_new_sRGB(void);
FivIoProfile *fiv_io_profile_new_sRGB_gamma(double gamma);
FivIoProfile *fiv_io_profile_new_parametric(
double gamma, double whitepoint[2], double primaries[6]);
GBytes *fiv_io_profile_to_bytes(FivIoProfile *profile);
void fiv_io_profile_free(FivIoProfile *self);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#define FIV_TYPE_IO_CMM (fiv_io_cmm_get_type())
G_DECLARE_FINAL_TYPE(FivIoCmm, fiv_io_cmm, FIV, IO_CMM, GObject)
FivIoCmm *fiv_io_cmm_get_default(void);
FivIoProfile *fiv_io_cmm_get_profile(
FivIoCmm *self, const void *data, size_t len);
FivIoProfile *fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes);
FivIoProfile *fiv_io_cmm_get_profile_sRGB(FivIoCmm *self);
FivIoProfile *fiv_io_cmm_get_profile_sRGB_gamma(FivIoCmm *self, double gamma);
FivIoProfile *fiv_io_cmm_get_profile_parametric(
FivIoCmm *self, double gamma, double whitepoint[2], double primaries[6]);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void fiv_io_premultiply_argb32(FivIoImage *image);
void fiv_io_profile_cmyk(
void fiv_io_cmm_cmyk(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target);
void fiv_io_profile_4x16le_direct(unsigned char *data,
void fiv_io_cmm_4x16le_direct(FivIoCmm *self, unsigned char *data,
int w, int h, FivIoProfile *source, FivIoProfile *target);
void fiv_io_profile_argb32_premultiply(
FivIoImage *image, FivIoProfile *source, FivIoProfile *target);
void fiv_io_profile_page(FivIoImage *page, FivIoProfile *target,
void (*frame_cb) (FivIoImage *, FivIoProfile *, FivIoProfile *));
#define fiv_io_profile_argb32_premultiply_page(page, target) \
fiv_io_profile_page((page), (target), fiv_io_profile_argb32_premultiply)
void fiv_io_profile_any(
void fiv_io_cmm_argb32_premultiply(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target);
FivIoImage *fiv_io_profile_finalize(FivIoImage *image, FivIoProfile *target);
#define fiv_io_cmm_argb32_premultiply_page(cmm, page, target) \
fiv_io_cmm_page((cmm), (page), (target), fiv_io_cmm_argb32_premultiply)
void fiv_io_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target,
void (*frame_cb) (FivIoCmm *,
FivIoImage *, FivIoProfile *, FivIoProfile *));
void fiv_io_cmm_any(FivIoCmm *self,
FivIoImage *image, FivIoProfile *source, FivIoProfile *target);
FivIoImage *fiv_io_cmm_finish(FivIoCmm *self,
FivIoImage *image, FivIoProfile *target);
// --- Loading -----------------------------------------------------------------
@@ -84,7 +93,8 @@ enum _FivIoOrientation {
// then loaders could store it in their closures.
struct _FivIoRenderClosure {
/// The rendering is allowed to fail, returning NULL.
FivIoImage *(*render)(FivIoRenderClosure *, FivIoProfile *, double scale);
FivIoImage *(*render)(
FivIoRenderClosure *, FivIoCmm *, FivIoProfile *, double scale);
void (*destroy)(FivIoRenderClosure *);
};
@@ -155,7 +165,8 @@ cairo_surface_t *fiv_io_image_to_surface_noref(const FivIoImage *image);
typedef struct {
const char *uri; ///< Source URI
FivIoProfile screen_profile; ///< Target colour space or NULL
FivIoCmm *cmm; ///< Colour management module or NULL
FivIoProfile *screen_profile; ///< Target colour space or NULL
int screen_dpi; ///< Target DPI
gboolean enhance; ///< Enhance JPEG (currently)
gboolean first_frame_only; ///< Only interested in the 1st frame
@@ -174,6 +185,8 @@ FivIoImage *fiv_io_open_png_thumbnail(const char *path, GError **error);
/// and its target dimensions.
cairo_matrix_t fiv_io_orientation_apply(const FivIoImage *image,
FivIoOrientation orientation, double *width, double *height);
cairo_matrix_t fiv_io_orientation_matrix(
FivIoOrientation orientation, double width, double height);
void fiv_io_orientation_dimensions(const FivIoImage *image,
FivIoOrientation orientation, double *width, double *height);

View File

@@ -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')"

View File

@@ -433,7 +433,10 @@ complete_path(GFile *location, GtkListStore *model)
!info)
break;
if (g_file_info_get_file_type(info) != G_FILE_TYPE_DIRECTORY ||
if (g_file_info_get_file_type(info) != G_FILE_TYPE_DIRECTORY)
continue;
if (g_file_info_has_attribute(info,
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) &&
g_file_info_get_is_hidden(info))
continue;

View File

@@ -137,10 +137,12 @@ might_be_a_thumbnail(const char *path_or_uri)
static FivIoImage *
render(GFile *target, GBytes *data, gboolean *color_managed, GError **error)
{
FivIoCmm *cmm = fiv_io_cmm_get_default();
FivIoOpenContext ctx = {
.uri = g_file_get_uri(target),
// Remember to synchronize changes with adjust_thumbnail().
.screen_profile = fiv_io_profile_new_sRGB(),
.cmm = cmm,
.screen_profile = fiv_io_cmm_get_profile_sRGB(cmm),
.screen_dpi = 96,
.first_frame_only = TRUE,
// Only using this array as a redirect.
@@ -182,9 +184,11 @@ adjust_thumbnail(FivIoImage *thumbnail, double row_height)
FivIoRenderClosure *closure = thumbnail->render;
if (closure && orientation <= FivIoOrientation0) {
// Remember to synchronize changes with render().
FivIoProfile *screen_profile = fiv_io_profile_new_sRGB();
FivIoCmm *cmm = fiv_io_cmm_get_default();
FivIoProfile *screen_profile = fiv_io_cmm_get_profile_sRGB(cmm);
// This API doesn't accept non-uniform scaling; prefer a vertical fit.
FivIoImage *scaled = closure->render(closure, screen_profile, scale_y);
FivIoImage *scaled =
closure->render(closure, cmm, screen_profile, scale_y);
if (screen_profile)
fiv_io_profile_free(screen_profile);
if (scaled)
@@ -388,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;
}

View File

@@ -1,7 +1,7 @@
//
// fiv-view.c: image viewing widget
//
// Copyright (c) 2021 - 2022, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2024, 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.
@@ -24,6 +24,7 @@
#include <math.h>
#include <stdbool.h>
#include <epoxy/gl.h>
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
@@ -83,6 +84,10 @@ struct _FivView {
int remaining_loops; ///< Greater than zero if limited
gint64 frame_time; ///< Current frame's start, µs precision
gulong frame_update_connection; ///< GdkFrameClock::update
GdkGLContext *gl_context; ///< OpenGL context
bool gl_initialized; ///< Objects have been created
GLuint gl_program; ///< Linked render program
};
G_DEFINE_TYPE_EXTENDED(FivView, fiv_view, GTK_TYPE_WIDGET, 0,
@@ -161,6 +166,147 @@ enum {
// Globals are, sadly, the canonical way of storing signal numbers.
static guint view_signals[LAST_SIGNAL];
// --- OpenGL ------------------------------------------------------------------
// While GTK+ 3 technically still supports legacy desktop OpenGL 2.0[1],
// we will pick the 3.3 core profile, which is fairly old by now.
// It doesn't seem to make any sense to go below 3.2.
//
// [1] https://stackoverflow.com/a/37923507/76313
//
// OpenGL ES
//
// Currently, we do not support OpenGL ES at all--it needs its own shaders
// (if only because of different #version statements), and also further analysis
// as to what is our minimum version requirement. While GTK+ 3 can again go
// down as low as OpenGL ES 2.0, this might be too much of a hassle to support.
//
// ES can be forced via GDK_GL=gles, if gdk_gl_context_set_required_version()
// doesn't stand in the way.
//
// Let's not forget that this is a desktop image viewer first and foremost.
static const char *
gl_error_string(GLenum err)
{
switch (err) {
case GL_NO_ERROR:
return "no error";
case GL_CONTEXT_LOST:
return "context lost";
case GL_INVALID_ENUM:
return "invalid enum";
case GL_INVALID_VALUE:
return "invalid value";
case GL_INVALID_OPERATION:
return "invalid operation";
case GL_INVALID_FRAMEBUFFER_OPERATION:
return "invalid framebuffer operation";
case GL_OUT_OF_MEMORY:
return "out of memory";
case GL_STACK_UNDERFLOW:
return "stack underflow";
case GL_STACK_OVERFLOW:
return "stack overflow";
default:
return NULL;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static const char *gl_vertex =
"#version 330\n"
"layout(location = 0) in vec4 position;\n"
"out vec2 coordinates;\n"
"void main() {\n"
"\tcoordinates = position.zw;\n"
"\tgl_Position = vec4(position.xy, 0., 1.);\n"
"}\n";
static const char *gl_fragment =
"#version 330\n"
"in vec2 coordinates;\n"
"layout(location = 0) out vec4 color;\n"
"uniform sampler2D picture;\n"
"uniform bool checkerboard;\n"
"\n"
"vec3 checker() {\n"
"\tvec2 xy = gl_FragCoord.xy / 20.;\n"
"\tif (checkerboard && (int(floor(xy.x) + floor(xy.y)) & 1) == 0)\n"
"\t\treturn vec3(0.98);\n"
"\telse\n"
"\t\treturn vec3(1.00);\n"
"}\n"
"\n"
"void main() {\n"
"\tvec3 c = checker();\n"
"\tvec4 t = texture(picture, coordinates);\n"
"\t// Premultiplied blending with a solid background.\n"
"\t// XXX: This is only correct for linear components.\n"
"\tcolor = vec4(c * (1. - t.a) + t.rgb, 1.);\n"
"}\n";
static GLuint
gl_make_shader(int type, const char *glsl)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &glsl, NULL);
glCompileShader(shader);
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (!status) {
GLint len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
GLchar *buffer = g_malloc0(len + 1);
glGetShaderInfoLog(shader, len, NULL, buffer);
g_warning("GL shader compilation failed: %s", buffer);
g_free(buffer);
glDeleteShader(shader);
return 0;
}
return shader;
}
static GLuint
gl_make_program(void)
{
GLuint vertex = gl_make_shader(GL_VERTEX_SHADER, gl_vertex);
GLuint fragment = gl_make_shader(GL_FRAGMENT_SHADER, gl_fragment);
if (!vertex || !fragment) {
glDeleteShader(vertex);
glDeleteShader(fragment);
return 0;
}
GLuint program = glCreateProgram();
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glLinkProgram(program);
glDeleteShader(vertex);
glDeleteShader(fragment);
GLint status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (!status) {
GLint len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
GLchar *buffer = g_malloc0(len + 1);
glGetProgramInfoLog(program, len, NULL, buffer);
g_warning("GL program linking failed: %s", buffer);
g_free(buffer);
glDeleteProgram(program);
return 0;
}
return program;
}
// -----------------------------------------------------------------------------
static void
on_adjustment_value_changed(
G_GNUC_UNUSED GtkAdjustment *adjustment, gpointer user_data)
@@ -417,6 +563,7 @@ prescale_page(FivView *self)
// If it fails, the previous frame pointer may become invalid.
g_clear_pointer(&self->page_scaled, fiv_io_image_unref);
self->frame = self->page_scaled = closure->render(closure,
self->enable_cms ? fiv_io_cmm_get_default() : NULL,
self->enable_cms ? self->screen_cms_profile : NULL, self->scale);
if (!self->page_scaled)
self->frame = self->page;
@@ -475,7 +622,8 @@ monitor_cms_profile(GdkWindow *root, int num)
if (gdk_property_get(root, gdk_atom_intern(atom, FALSE), GDK_NONE, 0,
8 << 20 /* MiB */, FALSE, &type, &format, &length, &data)) {
if (format == 8 && length > 0)
result = fiv_io_profile_new(data, length);
result = fiv_io_cmm_get_profile(
fiv_io_cmm_get_default(), data, length);
g_free(data);
}
return result;
@@ -498,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);
@@ -525,7 +674,8 @@ reload_screen_cms_profile(FivView *self, GdkWindow *window)
out:
if (!self->screen_cms_profile)
self->screen_cms_profile = fiv_io_profile_new_sRGB();
self->screen_cms_profile =
fiv_io_cmm_get_profile_sRGB(fiv_io_cmm_get_default());
}
static void
@@ -559,6 +709,9 @@ fiv_view_realize(GtkWidget *widget)
GdkWindow *window = gdk_window_new(gtk_widget_get_parent_window(widget),
&attributes, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
GSettings *settings = g_settings_new(PROJECT_NS PROJECT_NAME);
gboolean opengl = g_settings_get_boolean(settings, "opengl");
// Without the following call, or the rendering mode set to "recording",
// RGB30 degrades to RGB24, because gdk_window_begin_paint_internal()
// creates backing stores using cairo_content_t constants.
@@ -568,20 +721,274 @@ fiv_view_realize(GtkWidget *widget)
// Note that this disables double buffering, and sometimes causes artefacts,
// see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2560
//
// If GTK+'s OpenGL integration fails to deliver, we need to use the window
// directly, sidestepping the toolkit entirely.
GSettings *settings = g_settings_new(PROJECT_NS PROJECT_NAME);
// GTK+'s OpenGL integration is terrible, so we may need to use
// the X11 subwindow directly, sidestepping the toolkit entirely.
if (GDK_IS_X11_WINDOW(window) &&
g_settings_get_boolean(settings, "native-view-window"))
gdk_window_ensure_native(window);
g_object_unref(settings);
#endif // GDK_WINDOWING_X11
g_object_unref(settings);
gtk_widget_register_window(widget, window);
gtk_widget_set_window(widget, window);
gtk_widget_set_realized(widget, TRUE);
reload_screen_cms_profile(FIV_VIEW(widget), window);
FivView *self = FIV_VIEW(widget);
g_clear_object(&self->gl_context);
if (!opengl)
return;
GError *error = NULL;
GdkGLContext *gl_context = gdk_window_create_gl_context(window, &error);
if (!gl_context) {
g_warning("GL: %s", error->message);
g_error_free(error);
return;
}
gdk_gl_context_set_use_es(gl_context, FALSE);
gdk_gl_context_set_required_version(gl_context, 3, 3);
gdk_gl_context_set_debug_enabled(gl_context, TRUE);
if (!gdk_gl_context_realize(gl_context, &error)) {
g_warning("GL: %s", error->message);
g_error_free(error);
g_object_unref(gl_context);
return;
}
self->gl_context = gl_context;
}
static void GLAPIENTRY
gl_on_message(G_GNUC_UNUSED GLenum source, GLenum type, G_GNUC_UNUSED GLuint id,
G_GNUC_UNUSED GLenum severity, G_GNUC_UNUSED GLsizei length,
const GLchar *message, G_GNUC_UNUSED const void *user_data)
{
if (type == GL_DEBUG_TYPE_ERROR)
g_warning("GL: error: %s", message);
else
g_debug("GL: %s", message);
}
static void
fiv_view_unrealize(GtkWidget *widget)
{
FivView *self = FIV_VIEW(widget);
if (self->gl_context) {
if (self->gl_initialized) {
gdk_gl_context_make_current(self->gl_context);
glDeleteProgram(self->gl_program);
}
if (self->gl_context == gdk_gl_context_get_current())
gdk_gl_context_clear_current();
g_clear_object(&self->gl_context);
}
GTK_WIDGET_CLASS(fiv_view_parent_class)->unrealize(widget);
}
static bool
gl_draw(FivView *self, cairo_t *cr)
{
gdk_gl_context_make_current(self->gl_context);
if (!self->gl_initialized) {
GLuint program = gl_make_program();
if (!program)
return false;
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
if (epoxy_has_gl_extension("GL_ARB_debug_output")) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(gl_on_message, NULL);
}
self->gl_program = program;
self->gl_initialized = true;
}
// This limit is always less than that of Cairo/pixman,
// and we'd have to figure out tiling.
GLint max = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
if (max < (GLint) self->frame->width ||
max < (GLint) self->frame->height) {
g_warning("OpenGL max. texture size is too small");
return false;
}
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
int dw = 0, dh = 0, dx = 0, dy = 0;
get_display_dimensions(self, &dw, &dh);
int clipw = dw, cliph = dh;
double x1 = 0., y1 = 0., x2 = 1., y2 = 1.;
if (self->hadjustment)
x1 = floor(gtk_adjustment_get_value(self->hadjustment)) / dw;
if (self->vadjustment)
y1 = floor(gtk_adjustment_get_value(self->vadjustment)) / dh;
if (dw <= allocation.width) {
dx = round((allocation.width - dw) / 2.);
} else {
x2 = x1 + (double) allocation.width / dw;
clipw = allocation.width;
}
if (dh <= allocation.height) {
dy = round((allocation.height - dh) / 2.);
} else {
y2 = y1 + (double) allocation.height / dh;
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);
// https://stackoverflow.com/questions/25157306 0..1
// GL_TEXTURE_RECTANGLE seems kind-of useful
glBindTexture(GL_TEXTURE_2D, textures[SRC]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (self->filter) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
// GL_UNPACK_ALIGNMENT is initially 4, which is fine for these.
// Texture swizzling is OpenGL 3.3.
if (self->frame->format == CAIRO_FORMAT_ARGB32) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
self->frame->width, self->frame->height,
0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, self->frame->data);
} else if (self->frame->format == CAIRO_FORMAT_RGB24) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
self->frame->width, self->frame->height,
0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, self->frame->data);
} else if (self->frame->format == CAIRO_FORMAT_RGB30) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
self->frame->width, self->frame->height,
0, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV, self->frame->data);
} else {
g_warning("GL: unsupported bitmap format");
}
// GtkGLArea creates textures like this.
glBindTexture(GL_TEXTURE_2D, textures[DEST]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, clipw, cliph, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
glViewport(0, 0, clipw, cliph);
GLuint vao = 0;
glGenVertexArrays(1, &vao);
GLuint frame_buffer = 0;
glGenFramebuffers(1, &frame_buffer);
glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[DEST], 0);
glClearColor(0., 0., 0., 1.);
glClear(GL_COLOR_BUFFER_BIT);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
g_warning("GL framebuffer status: %u", status);
glUseProgram(self->gl_program);
GLint position_location = glGetAttribLocation(
self->gl_program, "position");
GLint picture_location = glGetUniformLocation(
self->gl_program, "picture");
GLint checkerboard_location = glGetUniformLocation(
self->gl_program, "checkerboard");
glUniform1i(picture_location, 0);
glUniform1i(checkerboard_location, self->checkerboard);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[SRC]);
// Note that the Y axis is flipped in the table.
double vertices[][4] = {
{-1., -1., x1, y2},
{+1., -1., x2, y2},
{+1., +1., x2, y1},
{-1., +1., x1, y1},
};
cairo_matrix_t matrix = fiv_io_orientation_matrix(self->orientation, 1, 1);
cairo_matrix_transform_point(&matrix, &vertices[0][2], &vertices[0][3]);
cairo_matrix_transform_point(&matrix, &vertices[1][2], &vertices[1][3]);
cairo_matrix_transform_point(&matrix, &vertices[2][2], &vertices[2][3]);
cairo_matrix_transform_point(&matrix, &vertices[3][2], &vertices[3][3]);
GLuint vertex_buffer = 0;
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
glBindVertexArray(vao);
glVertexAttribPointer(position_location,
G_N_ELEMENTS(vertices[0]), GL_DOUBLE, GL_FALSE, sizeof vertices[0], 0);
glEnableVertexAttribArray(position_location);
glDrawArrays(GL_TRIANGLE_FAN, 0, G_N_ELEMENTS(vertices));
glDisableVertexAttribArray(position_location);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 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, scale, 0, 0, clipw, cliph);
gdk_gl_context_make_current(self->gl_context);
glDeleteBuffers(1, &vertex_buffer);
glDeleteTextures(2, textures);
glDeleteVertexArrays(1, &vao);
glDeleteFramebuffers(1, &frame_buffer);
// TODO(p): Possibly use this clue as a hint to use Cairo rendering.
GLenum err = 0;
while ((err = glGetError()) != GL_NO_ERROR) {
const char *string = gl_error_string(err);
if (string)
g_warning("GL: error: %s", string);
else
g_warning("GL: error: %u", err);
}
gdk_gl_context_clear_current();
return true;
}
static gboolean
@@ -598,8 +1005,10 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
if (!self->image ||
!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
return TRUE;
if (self->gl_context && gl_draw(self, cr))
return TRUE;
int dw, dh;
int dw = 0, dh = 0;
get_display_dimensions(self, &dw, &dh);
double x = 0;
@@ -1293,6 +1702,7 @@ fiv_view_class_init(FivViewClass *klass)
widget_class->map = fiv_view_map;
widget_class->unmap = fiv_view_unmap;
widget_class->realize = fiv_view_realize;
widget_class->unrealize = fiv_view_unrealize;
widget_class->draw = fiv_view_draw;
widget_class->button_press_event = fiv_view_button_press_event;
widget_class->scroll_event = fiv_view_scroll_event;
@@ -1381,6 +1791,7 @@ open_without_swapping_in(FivView *self, const char *uri)
{
FivIoOpenContext ctx = {
.uri = uri,
.cmm = self->enable_cms ? fiv_io_cmm_get_default() : NULL,
.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,

10
fiv.c
View File

@@ -1689,6 +1689,9 @@ make_toolbar_radio(const char *label, const char *tooltip)
GtkWidget *button = gtk_radio_button_new_with_label(NULL, label);
gtk_widget_set_tooltip_text(button, tooltip);
gtk_widget_set_focus_on_click(button, FALSE);
gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(button), FALSE);
gtk_style_context_add_class(
gtk_widget_get_style_context(button), GTK_STYLE_CLASS_FLAT);
return button;
}
@@ -1750,7 +1753,6 @@ make_browser_toolbar(void)
gtk_radio_button_join_group(radio, last);
last = radio;
}
return browser_toolbar;
}
@@ -2206,8 +2208,10 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
mix(@theme_selected_bg_color, @content_view_bg, 0.5); \
fiv-view, fiv-browser { background: @content_view_bg; } \
placessidebar.fiv box > separator { margin: 4px 0; } \
placessidebar.fiv row { min-height: 1.75em; } \
placessidebar.fiv row { min-height: 2em; } \
.fiv-toolbar button { padding-left: 0; padding-right: 0; } \
.fiv-toolbar button.text-button { \
padding-left: 4px; padding-right: 4px; } \
.fiv-toolbar > button:first-child { padding-left: 4px; } \
.fiv-toolbar > button:last-child { padding-right: 4px; } \
.fiv-toolbar separator { \
@@ -2579,8 +2583,6 @@ on_app_handle_local_options(G_GNUC_UNUSED GApplication *app,
return 0;
}
fiv_io_profile_init();
// Normalize all arguments to URIs, and run thumbnailing modes first.
for (gsize i = 0; o.args && o.args[i]; i++) {
GFile *resolved = g_file_new_for_commandline_arg(o.args[i]);

View File

@@ -17,6 +17,13 @@
double buffering.
</description>
</key>
<key name='opengl' type='b'>
<default>false</default>
<summary>Use experimental OpenGL rendering</summary>
<description>
OpenGL within GTK+ is highly problematic--you don't want this.
</description>
</key>
<key name='dark-theme' type='b'>
<default>false</default>
<summary>Use a dark theme variant on start-up</summary>

View File

@@ -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')
@@ -36,6 +36,7 @@ gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))
dependencies = [
dependency('gtk+-3.0'),
dependency('pixman-1'),
dependency('epoxy'),
dependency('libjpeg'),
dependency('libturbojpeg'),
@@ -164,8 +165,8 @@ tiff_tables = custom_target('tiff-tables.h',
)
desktops = ['fiv.desktop', 'fiv-browse.desktop']
iolib = static_library('fiv-io', 'fiv-io.c', 'fiv-io-profile.c', 'xdg.c',
tiff_tables,
iolib = static_library('fiv-io', 'fiv-io.c', 'fiv-io-cmm.c', 'xdg.c',
tiff_tables, config,
dependencies : dependencies).extract_all_objects(recursive : true)
exe = executable('fiv', 'fiv.c', 'fiv-view.c', 'fiv-context-menu.c',
'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'fiv-collection.c',
@@ -362,10 +363,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,

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 {