diff options
| -rw-r--r-- | fastiv.c | 33 | ||||
| -rw-r--r-- | fiv-io.c | 214 | ||||
| -rw-r--r-- | fiv-io.h | 15 | ||||
| -rw-r--r-- | fiv-view.c | 118 | ||||
| -rw-r--r-- | fiv-view.h | 1 | ||||
| -rw-r--r-- | meson.build | 7 | ||||
| -rw-r--r-- | meson_options.txt | 2 | 
7 files changed, 312 insertions, 78 deletions
@@ -229,18 +229,19 @@ make_key_window(void)  	XX(S4,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \  	/* XX(PIN,        B("view-pin-symbolic", "Keep view configuration")) */ \  	/* Or perhaps "blur-symbolic", also in the extended set. */ \ +	XX(COLOR,         T("preferences-color-symbolic", "Color management")) \  	XX(SMOOTH,        T("blend-tool-symbolic", "Smooth scaling")) \  	XX(CHECKERBOARD,  T("checkerboard-symbolic", "Highlight transparency")) \  	XX(ENHANCE,       T("heal-symbolic", "Enhance low-quality JPEG")) \ -	/* XX(COLOR,      B("preferences-color-symbolic", "Color management")) */ \ +	XX(S5,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \  	XX(SAVE,          B("document-save-as-symbolic", "Save as...")) \  	XX(PRINT,         B("document-print-symbolic", "Print...")) \  	XX(INFO,          B("info-symbolic", "Information")) \ -	XX(S5,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ +	XX(S6,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \  	XX(LEFT,          B("object-rotate-left-symbolic", "Rotate left")) \  	XX(MIRROR,        B("object-flip-horizontal-symbolic", "Mirror")) \  	XX(RIGHT,         B("object-rotate-right-symbolic", "Rotate right")) \ -	XX(S6,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ +	XX(S7,            gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \  	/* We are YouTube. */ \  	XX(FULLSCREEN,    B("view-fullscreen-symbolic", "Fullscreen")) @@ -1067,11 +1068,11 @@ make_view_toolbar(void)  	// Exploring different versions of awkward layouts.  	for (int i = 0; i <= TOOLBAR_S1; i++)  		gtk_box_pack_start(box, g.toolbar[i], FALSE, FALSE, 0); -	for (int i = TOOLBAR_COUNT; --i >= TOOLBAR_S6; ) +	for (int i = TOOLBAR_COUNT; --i >= TOOLBAR_S7; )  		gtk_box_pack_end(box, g.toolbar[i], FALSE, FALSE, 0);  	GtkWidget *center = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); -	for (int i = TOOLBAR_S1; ++i < TOOLBAR_S6; ) +	for (int i = TOOLBAR_S1; ++i < TOOLBAR_S7; )  		gtk_box_pack_start(GTK_BOX(center), g.toolbar[i], FALSE, FALSE, 0);  	gtk_box_set_center_widget(box, center); @@ -1090,6 +1091,7 @@ make_view_toolbar(void)  	toolbar_command(TOOLBAR_MINUS,         FIV_VIEW_COMMAND_ZOOM_OUT);  	toolbar_command(TOOLBAR_ONE,           FIV_VIEW_COMMAND_ZOOM_1);  	toolbar_toggler(TOOLBAR_FIT,           "scale-to-fit"); +	toolbar_toggler(TOOLBAR_COLOR,         "enable-cms");  	toolbar_toggler(TOOLBAR_SMOOTH,        "filter");  	toolbar_toggler(TOOLBAR_CHECKERBOARD,  "checkerboard");  	toolbar_toggler(TOOLBAR_ENHANCE,       "enhance"); @@ -1107,6 +1109,8 @@ make_view_toolbar(void)  		G_CALLBACK(on_notify_view_playing), NULL);  	g_signal_connect(g.view, "notify::scale-to-fit",  		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_FIT]); +	g_signal_connect(g.view, "notify::enable-cms", +		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_COLOR]);  	g_signal_connect(g.view, "notify::filter",  		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_SMOOTH]);  	g_signal_connect(g.view, "notify::checkerboard", @@ -1117,10 +1121,14 @@ make_view_toolbar(void)  	g_object_notify(G_OBJECT(g.view), "scale");  	g_object_notify(G_OBJECT(g.view), "playing");  	g_object_notify(G_OBJECT(g.view), "scale-to-fit"); +	g_object_notify(G_OBJECT(g.view), "enable-cms");  	g_object_notify(G_OBJECT(g.view), "filter");  	g_object_notify(G_OBJECT(g.view), "checkerboard");  	g_object_notify(G_OBJECT(g.view), "enhance"); +#ifndef HAVE_LCMS2 +	gtk_widget_set_no_show_all(g.toolbar[TOOLBAR_COLOR], TRUE); +#endif  #ifndef HAVE_JPEG_QS  	gtk_widget_set_no_show_all(g.toolbar[TOOLBAR_ENHANCE], TRUE);  #endif @@ -1146,7 +1154,7 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \  	#toolbar > button:last-child { padding-right: 4px; } \  	#toolbar separator { \  		background: mix(@insensitive_fg_color, \ -			@insensitive_bg_color, 0.4); margin: 6px 10px; \ +			@insensitive_bg_color, 0.4); margin: 6px 8px; \  	} \  	fiv-browser { padding: 5px; } \  	fiv-browser.item { \ @@ -1333,11 +1341,6 @@ main(int argc, char *argv[])  	on_toolbar_zoom(NULL, (gpointer) 0);  	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(funnel), TRUE); -	g.files = g_ptr_array_new_full(16, g_free); -	g.directory = g_get_current_dir(); -	if (!path_arg || !open_any_path(path_arg, browse)) -		open_any_path(g.directory, FALSE); -  	// Try to get half of the screen vertically, in 4:3 aspect ratio.  	//  	// We need the GdkMonitor before the GtkWindow has a GdkWindow (i.e., @@ -1355,6 +1358,14 @@ main(int argc, char *argv[])  	unit = MAX(200, unit);  	gtk_window_set_default_size(GTK_WINDOW(g.window), 4 * unit, 3 * unit); +	g.files = g_ptr_array_new_full(16, g_free); +	g.directory = g_get_current_dir(); + +	// XXX: The widget wants to read the display's profile. The realize is ugly. +	gtk_widget_realize(g.view); +	if (!path_arg || !open_any_path(path_arg, browse)) +		open_any_path(g.directory, FALSE); +  	gtk_widget_show_all(g.window);  	gtk_main();  	return 0; @@ -25,6 +25,11 @@  #include <spng.h>  #include <turbojpeg.h> +// Colour management must be handled before RGB conversions. +#ifdef HAVE_LCMS2 +#include <lcms2.h> +#endif  // HAVE_LCMS2 +  #ifdef HAVE_JPEG_QS  #include <jpeglib.h>  #include <setjmp.h> @@ -169,6 +174,129 @@ try_append_page(cairo_surface_t *surface, cairo_surface_t **result,  	return true;  } +// --- Colour management ------------------------------------------------------- + +FivIoProfile +fiv_io_profile_new(const void *data, size_t len) +{ +#ifdef HAVE_LCMS2 +	return cmsOpenProfileFromMem(data, len); +#else +	(void) data; +	(void) len; +	return NULL; +#endif +} + +FivIoProfile +fiv_io_profile_new_sRGB(void) +{ +#ifdef HAVE_LCMS2 +	return cmsCreate_sRGBProfile(); +#else +	return NULL; +#endif +} + +void +fiv_io_profile_free(FivIoProfile self) +{ +#ifdef HAVE_LCMS2 +	cmsCloseProfile(self); +#else +	(void) self; +#endif +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F. +#define FIV_IO_LCMS2_ARGB \ +	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8) + +// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with +// ARGB/BGRA/XRGB/BGRX. +static void +trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len) +{ +	// This CMYK handling has been seen in gdk-pixbuf/JPEG, GIMP/JPEG, skcms. +	// Assume that all YCCK/CMYK JPEG files use inverted CMYK, as Photoshop +	// does, see https://bugzilla.gnome.org/show_bug.cgi?id=618096 +	while (len--) { +		int c = p[0], m = p[1], y = p[2], k = p[3]; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +		p[0] = k * y / 255; +		p[1] = k * m / 255; +		p[2] = k * c / 255; +		p[3] = 255; +#else +		p[3] = k * y / 255; +		p[2] = k * m / 255; +		p[1] = k * c / 255; +		p[0] = 255; +#endif +		p += 4; +	} +} + +static void +fiv_io_profile_cmyk( +	cairo_surface_t *surface, FivIoProfile src, FivIoProfile dst) +{ +	unsigned char *data = cairo_image_surface_get_data(surface); +	int w = cairo_image_surface_get_width(surface); +	int h = cairo_image_surface_get_height(surface); + +#ifndef HAVE_LCMS2 +	(void) src; +	(void) dst; +#else +	cmsHTRANSFORM transform = NULL; +	if (src && dst) { +		transform = cmsCreateTransform(src, TYPE_CMYK_8_REV, dst, +			FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0); +	} +	if (transform) { +		cmsDoTransform(transform, data, data, w * h); +		cmsDeleteTransform(transform); +		return; +	} +#endif +	trivial_cmyk_to_host_byte_order_argb(data, w * h); +} + +static void +fiv_io_profile_xrgb( +	cairo_surface_t *surface, FivIoProfile src, FivIoProfile dst) +{ +#ifndef HAVE_LCMS2 +	(void) surface; +	(void) src; +	(void) dst; +#else +	unsigned char *data = cairo_image_surface_get_data(surface); +	int w = cairo_image_surface_get_width(surface); +	int h = cairo_image_surface_get_height(surface); + +	// TODO(p): We should make this optional. +	cmsHPROFILE src_fallback = NULL; +	if (dst && !src) +		src = src_fallback = cmsCreate_sRGBProfile(); + +	cmsHTRANSFORM transform = NULL; +	if (src && dst) { +		transform = cmsCreateTransform(src, FIV_IO_LCMS2_ARGB, dst, +			FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0); +	} +	if (transform) { +		cmsDoTransform(transform, data, data, w * h); +		cmsDeleteTransform(transform); +	} +	if (src_fallback) +		cmsCloseProfile(src_fallback); +#endif +} +  // --- Wuffs -------------------------------------------------------------------  // From libwebp, verified to exactly match [x * a / 255]. @@ -626,31 +754,7 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),  // --- JPEG -------------------------------------------------------------------- -static void -trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len) -{ -	// Inspired by gdk-pixbuf's io-jpeg.c: -	// -	// Assume that all YCCK/CMYK JPEG files use inverted CMYK, as Photoshop -	// does, see https://bugzilla.gnome.org/show_bug.cgi?id=618096 -	while (len--) { -		int c = p[0], m = p[1], y = p[2], k = p[3]; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN -		p[0] = k * y / 255; -		p[1] = k * m / 255; -		p[2] = k * c / 255; -		p[3] = 255; -#else -		p[3] = k * y / 255; -		p[2] = k * m / 255; -		p[1] = k * c / 255; -		p[0] = 255; -#endif -		p += 4; -	} -} - -static void +static GBytes *  parse_jpeg_metadata(cairo_surface_t *surface, const gchar *data, gsize len)  {  	// Because the JPEG file format is simple, just do it manually. @@ -732,16 +836,41 @@ parse_jpeg_metadata(cairo_surface_t *surface, const gchar *data, gsize len)  	else  		g_byte_array_free(exif, TRUE); +	GBytes *icc_profile = NULL;  	if (icc_done)  		cairo_surface_set_user_data(surface, &fiv_io_key_icc, -			g_byte_array_free_to_bytes(icc), +			(icc_profile = g_byte_array_free_to_bytes(icc)),  			(cairo_destroy_func_t) g_bytes_unref);  	else  		g_byte_array_free(icc, TRUE); +	return icc_profile; +} + +static void +load_jpeg_finalize(cairo_surface_t *surface, bool cmyk, +	FivIoProfile destination, const gchar *data, size_t len) +{ +	GBytes *icc_profile = parse_jpeg_metadata(surface, data, len); +	FivIoProfile source = NULL; +	if (icc_profile) +		source = fiv_io_profile_new( +			g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile)); + +	if (cmyk) +		fiv_io_profile_cmyk(surface, source, destination); +	else +		fiv_io_profile_xrgb(surface, source, destination); + +	if (source) +		fiv_io_profile_free(source); + +	// Pixel data has been written, need to let Cairo know. +	cairo_surface_mark_dirty(surface);  }  static cairo_surface_t * -open_libjpeg_turbo(const gchar *data, gsize len, GError **error) +open_libjpeg_turbo( +	const gchar *data, gsize len, FivIoProfile profile, GError **error)  {  	tjhandle dec = tjInitDecompress();  	if (!dec) { @@ -788,18 +917,9 @@ open_libjpeg_turbo(const gchar *data, gsize len, GError **error)  		}  	} -	if (pixel_format == TJPF_CMYK) { -		// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with -		// ARGB/BGR/XRGB/BGRX. -		trivial_cmyk_to_host_byte_order_argb( -			cairo_image_surface_get_data(surface), width * height); -	} - -	// Pixel data has been written, need to let Cairo know. -	cairo_surface_mark_dirty(surface); - +	load_jpeg_finalize( +		surface, (pixel_format == TJPF_CMYK), profile, data, len);  	tjDestroy(dec); -	parse_jpeg_metadata(surface, data, len);  	return surface;  } @@ -824,7 +944,8 @@ libjpeg_error_exit(j_common_ptr cinfo)  }  static cairo_surface_t * -open_libjpeg_enhanced(const gchar *data, gsize len, GError **error) +open_libjpeg_enhanced( +	const gchar *data, gsize len, FivIoProfile profile, GError **error)  {  	cairo_surface_t *volatile surface = NULL; @@ -880,11 +1001,11 @@ open_libjpeg_enhanced(const gchar *data, gsize len, GError **error)  	if (cinfo.out_color_space == JCS_CMYK)  		trivial_cmyk_to_host_byte_order_argb(  			surface_data, cinfo.output_width * cinfo.output_height); -	cairo_surface_mark_dirty(surface);  	(void) jpegqs_finish_decompress(&cinfo); +	load_jpeg_finalize( +		surface, (cinfo.out_color_space == JCS_CMYK), profile, data, len);  	jpeg_destroy_decompress(&cinfo); -	parse_jpeg_metadata(surface, data, len);  	return surface;  } @@ -1934,7 +2055,8 @@ cairo_user_data_key_t fiv_io_key_page_next;  cairo_user_data_key_t fiv_io_key_page_previous;  cairo_surface_t * -fiv_io_open(const gchar *path, gboolean enhance, GError **error) +fiv_io_open( +	const gchar *path, FivIoProfile profile, gboolean enhance, GError **error)  {  	// TODO(p): Don't always load everything into memory, test type first,  	// so that we can reject non-pictures early.  Wuffs only needs the first @@ -1956,14 +2078,14 @@ fiv_io_open(const gchar *path, gboolean enhance, GError **error)  		return NULL;  	cairo_surface_t *surface = -		fiv_io_open_from_data(data, len, path, enhance, error); +		fiv_io_open_from_data(data, len, path, profile, enhance, error);  	free(data);  	return surface;  }  cairo_surface_t *  fiv_io_open_from_data(const char *data, size_t len, const gchar *path, -	gboolean enhance, GError **error) +	FivIoProfile profile, gboolean enhance, GError **error)  {  	wuffs_base__slice_u8 prefix =  		wuffs_base__make_slice_u8((uint8_t *) data, len); @@ -1989,8 +2111,8 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path,  		break;  	case WUFFS_BASE__FOURCC__JPEG:  		surface = enhance -			? open_libjpeg_enhanced(data, len, error) -			: open_libjpeg_turbo(data, len, error); +			? open_libjpeg_enhanced(data, len, profile, error) +			: open_libjpeg_turbo(data, len, profile, error);  		break;  	default:  #ifdef HAVE_LIBRAW  // --------------------------------------------------------- @@ -21,6 +21,17 @@  #include <gio/gio.h>  #include <glib.h> +// --- Colour management ------------------------------------------------------- + +// TODO(p): Make it possible to use Skia's skcms, +// which also supports premultiplied alpha. +typedef void *FivIoProfile; +FivIoProfile fiv_io_profile_new(const void *data, size_t len); +FivIoProfile fiv_io_profile_new_sRGB(void); +void fiv_io_profile_free(FivIoProfile self); + +// --- Loading ----------------------------------------------------------------- +  extern const char *fiv_io_supported_media_types[];  char **fiv_io_all_supported_media_types(void); @@ -56,9 +67,9 @@ extern cairo_user_data_key_t fiv_io_key_page_next;  extern cairo_user_data_key_t fiv_io_key_page_previous;  cairo_surface_t *fiv_io_open( -	const gchar *path, gboolean enhance, GError **error); +	const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);  cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len, -	const gchar *path, gboolean enhance, GError **error); +	const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);  int fiv_io_filecmp(GFile *f1, GFile *f2); @@ -17,6 +17,9 @@  #include "config.h" +#include "fiv-io.h" +#include "fiv-view.h" +  #include <math.h>  #include <stdbool.h> @@ -28,9 +31,6 @@  #include <gdk/gdkquartz.h>  #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( @@ -41,6 +41,7 @@ typedef enum _FivViewCommand {  	// Going to the end frame makes no sense, wrap around if needed.  	FIV_VIEW_COMMAND_TOGGLE_PLAYBACK, +	FIV_VIEW_COMMAND_TOGGLE_CMS,  	FIV_VIEW_COMMAND_TOGGLE_FILTER,  	FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD,  	FIV_VIEW_COMMAND_TOGGLE_ENHANCE, diff --git a/meson.build b/meson.build index de58bd5..f3fae8c 100644 --- a/meson.build +++ b/meson.build @@ -15,6 +15,7 @@ if get_option('buildtype').startswith('debug')  endif  # TODO(p): Use libraw_r later, when we start parallelizing/preloading. +lcms2 = dependency('lcms2', required : get_option('lcms2'))  libraw = dependency('libraw', required : get_option('libraw'))  librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))  xcursor = dependency('xcursor', required : get_option('xcursor')) @@ -27,11 +28,14 @@ libtiff = dependency('libtiff-4', required : get_option('libtiff'))  gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))  dependencies = [  	dependency('gtk+-3.0'), +	dependency('pixman-1'), +  	dependency('libturbojpeg'),  	dependency('libjpeg', required : get_option('jpeg-qs')),  	dependency('spng', version : '>=0.7.0',  		default_options: 'default_library=static'), -	dependency('pixman-1'), + +	lcms2,  	libraw,  	librsvg,  	xcursor, @@ -50,6 +54,7 @@ conf.set_quoted('PROJECT_NAME', meson.project_name())  conf.set_quoted('PROJECT_VERSION', meson.project_version())  # TODO(p): Wrap it in a Meson subproject, try to enable SIMD.  conf.set('HAVE_JPEG_QS', get_option('jpeg-qs').enabled()) +conf.set('HAVE_LCMS2', lcms2.found())  conf.set('HAVE_LIBRAW', libraw.found())  conf.set('HAVE_LIBRSVG', librsvg.found())  conf.set('HAVE_XCURSOR', xcursor.found()) diff --git a/meson_options.txt b/meson_options.txt index 5318848..e8c6f7d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,5 @@ +option('lcms2', type : 'feature', value : 'auto', +	description : 'Build with Little CMS colour management')  option('jpeg-qs', type : 'feature', value : 'enabled',  	description : 'Build with JPEG Quant Smooth integration')  option('libraw', type : 'feature', value : 'auto',  | 
