From 40c1f8327e1fdbd48492edd2631ec05c4ade3a8b Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Wed, 22 Dec 2021 22:07:49 +0100 Subject: Use Little CMS for JPEG colour management --- fiv-view.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 18 deletions(-) (limited to 'fiv-view.c') diff --git a/fiv-view.c b/fiv-view.c index 2cc77b2..2e50d57 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -17,6 +17,9 @@ #include "config.h" +#include "fiv-io.h" +#include "fiv-view.h" + #include #include @@ -28,9 +31,6 @@ #include #endif // GDK_WINDOWING_QUARTZ -#include "fiv-io.h" -#include "fiv-view.h" - struct _FivView { GtkWidget parent_instance; gchar *path; ///< Path to the current image (if any) @@ -38,13 +38,15 @@ struct _FivView { cairo_surface_t *page; ///< Current page within image, weak cairo_surface_t *frame; ///< Current frame within page, weak FivIoOrientation orientation; ///< Current page orientation - bool filter; ///< Smooth scaling toggle - bool checkerboard; ///< Show checkerboard background - bool enhance; ///< Try to enhance picture data - bool scale_to_fit; ///< Image no larger than the allocation + bool enable_cms : 1; ///< Smooth scaling toggle + bool filter : 1; ///< Smooth scaling toggle + bool checkerboard : 1; ///< Show checkerboard background + bool enhance : 1; ///< Try to enhance picture data + bool scale_to_fit : 1; ///< Image no larger than the allocation double scale; ///< Scaling factor cairo_surface_t *enhance_swap; ///< Quick swap in/out + FivIoProfile screen_cms_profile; ///< Target colour profile for widget int remaining_loops; ///< Greater than zero if limited gint64 frame_time; ///< Current frame's start, µs precision @@ -96,6 +98,7 @@ static FivIoOrientation view_right[9] = { enum { PROP_SCALE = 1, PROP_SCALE_TO_FIT, + PROP_ENABLE_CMS, PROP_FILTER, PROP_CHECKERBOARD, PROP_ENHANCE, @@ -113,8 +116,9 @@ static void fiv_view_finalize(GObject *gobject) { FivView *self = FIV_VIEW(gobject); - cairo_surface_destroy(self->image); + g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free); g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); + g_clear_pointer(&self->image, cairo_surface_destroy); g_free(self->path); G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject); @@ -132,6 +136,9 @@ fiv_view_get_property( case PROP_SCALE_TO_FIT: g_value_set_boolean(value, self->scale_to_fit); break; + case PROP_ENABLE_CMS: + g_value_set_boolean(value, self->enable_cms); + break; case PROP_FILTER: g_value_set_boolean(value, self->filter); break; @@ -173,6 +180,10 @@ fiv_view_set_property( if (self->scale_to_fit != g_value_get_boolean(value)) fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT); break; + case PROP_ENABLE_CMS: + if (self->enable_cms != g_value_get_boolean(value)) + fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_CMS); + break; case PROP_FILTER: if (self->filter != g_value_get_boolean(value)) fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_FILTER); @@ -317,6 +328,46 @@ fiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation) g_object_notify_by_pspec(G_OBJECT(widget), view_properties[PROP_SCALE]); } +// https://www.freedesktop.org/wiki/OpenIcc/ICC_Profiles_in_X_Specification_0.4 +// has disappeared, but you can use the wayback machine. +// +// Note that Wayland does not have any appropriate protocol, as of writing: +// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/14 +static void +reload_screen_cms_profile(FivView *self, GdkWindow *window) +{ + g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free); + + GdkDisplay *display = gdk_window_get_display(window); + GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, window); + + int num = -1; + for (int i = gdk_display_get_n_monitors(display); num < 0 && i--; ) + if (gdk_display_get_monitor(display, i) == monitor) + num = i; + if (num < 0) + goto out; + + char atom[32] = ""; + g_snprintf(atom, sizeof atom, "_ICC_PROFILE%c%d", num ? '_' : '\0', num); + + // Sadly, there is no nice GTK+/GDK mechanism to watch this for changes. + int format = 0, length = 0; + GdkAtom type = GDK_NONE; + guchar *data = NULL; + GdkWindow *root = gdk_screen_get_root_window(gdk_window_get_screen(window)); + 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) + self->screen_cms_profile = fiv_io_profile_new(data, length); + g_free(data); + } + +out: + if (!self->screen_cms_profile) + self->screen_cms_profile = fiv_io_profile_new_sRGB(); +} + static void fiv_view_realize(GtkWidget *widget) { @@ -363,6 +414,8 @@ fiv_view_realize(GtkWidget *widget) 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); } static gboolean @@ -1050,15 +1103,18 @@ fiv_view_class_init(FivViewClass *klass) view_properties[PROP_SCALE_TO_FIT] = g_param_spec_boolean( "scale-to-fit", "Scale to fit", "Scale images down to fit the window", TRUE, G_PARAM_READWRITE); + view_properties[PROP_ENABLE_CMS] = g_param_spec_boolean( + "enable-cms", "Enable CMS", "Enable color management", + TRUE, G_PARAM_READWRITE); view_properties[PROP_FILTER] = g_param_spec_boolean( "filter", "Use filtering", "Scale images smoothly", TRUE, G_PARAM_READWRITE); view_properties[PROP_CHECKERBOARD] = g_param_spec_boolean( "checkerboard", "Show checkerboard", "Highlight transparent background", - TRUE, G_PARAM_READWRITE); + FALSE, G_PARAM_READWRITE); view_properties[PROP_ENHANCE] = g_param_spec_boolean( "enhance", "Enhance JPEG", "Enhance low-quality JPEG", - TRUE, G_PARAM_READWRITE); + FALSE, G_PARAM_READWRITE); view_properties[PROP_PLAYING] = g_param_spec_boolean( "playing", "Playing animation", "An animation is running", FALSE, G_PARAM_READABLE); @@ -1099,6 +1155,7 @@ fiv_view_init(FivView *self) { gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE); + self->enable_cms = true; self->filter = true; self->checkerboard = false; self->scale = 1.0; @@ -1110,7 +1167,8 @@ fiv_view_init(FivView *self) gboolean fiv_view_open(FivView *self, const gchar *path, GError **error) { - cairo_surface_t *surface = fiv_io_open(path, FALSE, error); + cairo_surface_t *surface = fiv_io_open( + path, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error); if (!surface) return FALSE; if (self->image) @@ -1157,18 +1215,37 @@ frame_step(FivView *self, int step) gtk_widget_queue_draw(GTK_WIDGET(self)); } -static void -swap_enhanced_image(FivView *self) +static gboolean +reload(FivView *self) { GError *error = NULL; - cairo_surface_t *surface = self->enhance_swap; - if (!surface) - surface = fiv_io_open(self->path, self->enhance, &error); + cairo_surface_t *surface = fiv_io_open(self->path, + self->enable_cms ? self->screen_cms_profile : NULL, self->enhance, + &error); if (!surface) { show_error_dialog(get_toplevel(GTK_WIDGET(self)), error); + return FALSE; + } + + g_clear_pointer(&self->image, cairo_surface_destroy); + g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); + switch_page(self, (self->image = surface)); + return TRUE; +} + +static void +swap_enhanced_image(FivView *self) +{ + cairo_surface_t *saved = self->image; + self->image = self->page = self->frame = NULL; + + if (self->enhance_swap) { + switch_page(self, (self->image = self->enhance_swap)); + self->enhance_swap = saved; + } else if (reload(self)) { + self->enhance_swap = saved; } else { - self->enhance_swap = self->image; - switch_page(self, (self->image = surface)); + switch_page(self, (self->image = saved)); } } @@ -1215,6 +1292,11 @@ fiv_view_command(FivView *self, FivViewCommand command) ? stop_animating(self) : start_animating(self); + break; case FIV_VIEW_COMMAND_TOGGLE_CMS: + self->enable_cms = !self->enable_cms; + g_object_notify_by_pspec( + G_OBJECT(self), view_properties[PROP_ENABLE_CMS]); + reload(self); break; case FIV_VIEW_COMMAND_TOGGLE_FILTER: self->filter = !self->filter; g_object_notify_by_pspec( -- cgit v1.2.3-70-g09d2