[gthumb] image loader: added icc profile convertion
- From: Paolo Bacchilega <paobac src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gthumb] image loader: added icc profile convertion
- Date: Fri, 6 Feb 2015 09:58:27 +0000 (UTC)
commit 2949dbaa8625152b4db353d31c7e19486596ed69
Author: Paolo Bacchilega <paobac src gnome org>
Date: Tue Feb 3 09:43:14 2015 +0100
image loader: added icc profile convertion
configure.ac | 22 ++
extensions/cairo_io/cairo-image-surface-jpeg.c | 18 ++-
extensions/image_viewer/gth-image-viewer-page.c | 10 +
extensions/jpeg_utils/jpeg-info.c | 284 ++++++++++++++++-------
extensions/jpeg_utils/jpeg-info.h | 3 +-
gthumb/Makefile.am | 2 +
gthumb/gth-browser.c | 43 ++++
gthumb/gth-browser.h | 2 +
gthumb/gth-image-loader.c | 18 ++
gthumb/gth-image-loader.h | 2 +
gthumb/gth-image-preloader.c | 13 +
gthumb/gth-image-preloader.h | 2 +
gthumb/gth-image.c | 182 +++++++++++++++
gthumb/gth-image.h | 18 ++
14 files changed, 536 insertions(+), 83 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index da246f9..95ef5cc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,6 +72,7 @@ LIBRSVG_REQUIRED=2.34.0
LIBWEBP_REQUIRED=0.2.0
JSON_GLIB_REQUIRED=0.15.0
WEBKIT2_REQUIRED=1.10.0
+LCMS2_REQUIRED=2.6
dnl ===========================================================================
@@ -237,6 +238,26 @@ AM_CONDITIONAL(ENABLE_LIBCHAMPLAIN, test "x$enable_libchamplain" = xyes)
dnl ===========================================================================
+AC_ARG_ENABLE([lcms2],
+ [AS_HELP_STRING([--disable-lcms2],[do not compile code that uses the lcms2 library])],,
+ [enable_lcms2=yes])
+
+if test x$enable_lcms2 = xyes ; then
+ PKG_CHECK_MODULES(LCMS2,
+ lcms2 >= $LCMS2_REQUIRED,
+ [enable_lcms2=yes],
+ [enable_lcms2=no])
+fi
+if test x$enable_lcms2 = xyes ; then
+ AC_DEFINE(HAVE_LCMS2, 1, [Define to 1 if lcms2 support is included])
+fi
+
+AC_SUBST(LCMS2_LIBS)
+AC_SUBST(LCMS2_CFLAGS)
+AM_CONDITIONAL(ENABLE_LCMS2, test "x$enable_lcms2" = xyes)
+
+dnl ===========================================================================
+
IT_PROG_INTLTOOL([0.35.0])
GETTEXT_PACKAGE=gthumb
AC_SUBST([GETTEXT_PACKAGE])
@@ -751,4 +772,5 @@ Configuration:
Map support : ${enable_libchamplain}
SVG support : ${enable_librsvg}
WebP support : ${enable_libwebp}
+ LCMS2 support : ${enable_lcms2}
"
diff --git a/extensions/cairo_io/cairo-image-surface-jpeg.c b/extensions/cairo_io/cairo-image-surface-jpeg.c
index 0dd4201..a6d6630 100644
--- a/extensions/cairo_io/cairo-image-surface-jpeg.c
+++ b/extensions/cairo_io/cairo-image-surface-jpeg.c
@@ -25,6 +25,9 @@
#include <stdlib.h>
#include <setjmp.h>
#include <jpeglib.h>
+#if HAVE_LCMS2
+#include <lcms2.h>
+#endif
#include <gthumb.h>
#include <extensions/jpeg_utils/jmemorysrc.h>
#include <extensions/jpeg_utils/jpeg-info.h>
@@ -155,6 +158,7 @@ _cairo_image_surface_create_from_jpeg (GInputStream *istream,
GError **error)
{
GthImage *image;
+ JpegInfoFlags info_flags;
gboolean load_scaled;
GthTransform orientation;
int destination_width;
@@ -169,6 +173,7 @@ _cairo_image_surface_create_from_jpeg (GInputStream *istream,
struct jpeg_decompress_struct srcinfo;
cairo_surface_t *surface;
cairo_surface_metadata_t *metadata;
+ unsigned char *surface_data;
unsigned char *surface_row;
JSAMPARRAY buffer;
int buffer_stride;
@@ -193,11 +198,19 @@ _cairo_image_surface_create_from_jpeg (GInputStream *istream,
}
_jpeg_info_data_init (&jpeg_info);
- _jpeg_info_get_from_buffer (in_buffer, in_buffer_size, _JPEG_INFO_EXIF_ORIENTATION, &jpeg_info);
+ info_flags = _JPEG_INFO_EXIF_ORIENTATION;
+#if HAVE_LCMS2
+ info_flags |= _JPEG_INFO_ICC_PROFILE;
+#endif
+ _jpeg_info_get_from_buffer (in_buffer, in_buffer_size, info_flags, &jpeg_info);
if (jpeg_info.valid & _JPEG_INFO_EXIF_ORIENTATION)
orientation = jpeg_info.orientation;
else
orientation = GTH_TRANSFORM_NONE;
+#if HAVE_LCMS2
+ if (jpeg_info.valid & _JPEG_INFO_ICC_PROFILE)
+ gth_image_set_icc_profile (image, cmsOpenProfileFromMem (jpeg_info.icc_data,
jpeg_info.icc_data_size));
+#endif
_jpeg_info_data_dispose (&jpeg_info);
srcinfo.err = jpeg_std_error (&(jsrcerr.pub));
@@ -271,7 +284,8 @@ _cairo_image_surface_create_from_jpeg (GInputStream *istream,
metadata = _cairo_image_surface_get_metadata (surface);
metadata->has_alpha = FALSE;
- surface_row = _cairo_image_surface_flush_and_get_data (surface) + line_start;
+ surface_data = _cairo_image_surface_flush_and_get_data (surface);
+ surface_row = surface_data + line_start;
switch (srcinfo.out_color_space) {
case JCS_CMYK:
diff --git a/extensions/image_viewer/gth-image-viewer-page.c b/extensions/image_viewer/gth-image-viewer-page.c
index 1e26d0f..1106711 100644
--- a/extensions/image_viewer/gth-image-viewer-page.c
+++ b/extensions/image_viewer/gth-image-viewer-page.c
@@ -292,6 +292,13 @@ _g_mime_type_can_load_different_quality (const char *mime_type)
}
+static void
+_gth_image_preloader_init_preloader (GthImageViewerPage *self)
+{
+ gth_image_preloader_set_out_profile (self->priv->preloader, gth_browser_get_screen_profile
(self->priv->browser));
+}
+
+
static gboolean
update_quality_cb (gpointer user_data)
{
@@ -308,6 +315,7 @@ update_quality_cb (gpointer user_data)
if (! self->priv->image_changed && ! _g_mime_type_can_load_different_quality
(gth_file_data_get_mime_type (self->priv->file_data)))
return FALSE;
+ _gth_image_preloader_init_preloader (self);
gth_image_preloader_load (self->priv->preloader,
self->priv->image_changed ? GTH_MODIFIED_IMAGE : self->priv->file_data,
_gth_image_preloader_get_requested_size_for_current_image (self),
@@ -1134,6 +1142,7 @@ gth_image_viewer_page_real_view (GthViewerPage *base,
gth_image_viewer_set_void (GTH_IMAGE_VIEWER (self->priv->viewer));
}
+ _gth_image_preloader_init_preloader (self);
gth_image_preloader_load (self->priv->preloader,
self->priv->file_data,
#ifdef ALWAYS_LOAD_ORIGINAL_SIZE
@@ -1914,6 +1923,7 @@ gth_image_viewer_page_get_original (GthImageViewerPage *self,
gth_image_viewer_page_get_original);
data->cancellable = (cancellable != NULL) ? g_object_ref (cancellable) : g_cancellable_new ();
+ _gth_image_preloader_init_preloader (self);
gth_image_preloader_load (self->priv->preloader,
self->priv->image_changed ? GTH_MODIFIED_IMAGE : self->priv->file_data,
GTH_ORIGINAL_SIZE,
diff --git a/extensions/jpeg_utils/jpeg-info.c b/extensions/jpeg_utils/jpeg-info.c
index 8626cb1..5ca2ec3 100644
--- a/extensions/jpeg_utils/jpeg-info.c
+++ b/extensions/jpeg_utils/jpeg-info.c
@@ -39,9 +39,8 @@ _jpeg_info_data_init (JpegInfoData *data)
void
_jpeg_info_data_dispose (JpegInfoData *data)
{
- if (data->valid & _JPEG_INFO_ICC_PROFILE) {
+ if (data->valid & _JPEG_INFO_ICC_PROFILE)
g_free (data->icc_data);
- }
}
@@ -111,13 +110,14 @@ _jpeg_skip_segment_data (GInputStream *stream,
}
-static GthTransform
-_jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
- gsize app1_segment_size)
+static gboolean
+_jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
+ gsize app1_segment_size,
+ JpegInfoData *data)
{
int pos;
guint length;
- gboolean is_motorola;
+ gboolean big_endian;
guchar *exif_data;
guint offset, number_of_tags, tagnum;
int orientation;
@@ -127,7 +127,7 @@ _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
length = app1_segment_size;
if (length < 6)
- return 0;
+ return FALSE;
pos = 0;
@@ -140,78 +140,68 @@ _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
|| (in_buffer[pos++] != 0)
|| (in_buffer[pos++] != 0))
{
- return 0;
+ return FALSE;
}
/* Length of an IFD entry */
if (length < 12)
- return 0;
+ return FALSE;
exif_data = in_buffer + pos;
/* Discover byte order */
if ((exif_data[0] == 0x49) && (exif_data[1] == 0x49))
- is_motorola = FALSE;
+ big_endian = FALSE;
else if ((exif_data[0] == 0x4D) && (exif_data[1] == 0x4D))
- is_motorola = TRUE;
+ big_endian = TRUE;
else
- return 0;
+ return FALSE;
/* Check Tag Mark */
- if (is_motorola) {
+ if (big_endian) {
if (exif_data[2] != 0)
- return 0;
+ return FALSE;
if (exif_data[3] != 0x2A)
- return 0;
+ return FALSE;
}
else {
if (exif_data[3] != 0)
- return 0;
+ return FALSE;
if (exif_data[2] != 0x2A)
- return 0;
+ return FALSE;
}
/* Get first IFD offset (offset to IFD0) */
- if (is_motorola) {
+ if (big_endian) {
if (exif_data[4] != 0)
- return 0;
+ return FALSE;
if (exif_data[5] != 0)
- return 0;
- offset = exif_data[6];
- offset <<= 8;
- offset += exif_data[7];
+ return FALSE;
+ offset = (exif_data[6] << 8) + exif_data[7];
}
else {
if (exif_data[7] != 0)
- return 0;
+ return FALSE;
if (exif_data[6] != 0)
- return 0;
- offset = exif_data[5];
- offset <<= 8;
- offset += exif_data[4];
+ return FALSE;
+ offset = (exif_data[5] << 8) + exif_data[4];
}
if (offset > length - 2) /* check end of data segment */
- return 0;
+ return FALSE;
/* Get the number of directory entries contained in this IFD */
- if (is_motorola) {
- number_of_tags = exif_data[offset];
- number_of_tags <<= 8;
- number_of_tags += exif_data[offset+1];
- }
- else {
- number_of_tags = exif_data[offset+1];
- number_of_tags <<= 8;
- number_of_tags += exif_data[offset];
- }
+ if (big_endian)
+ number_of_tags = (exif_data[offset] << 8) + exif_data[offset+1];
+ else
+ number_of_tags = (exif_data[offset+1] << 8) + exif_data[offset];
if (number_of_tags == 0)
- return 0;
+ return FALSE;
offset += 2;
@@ -219,47 +209,174 @@ _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
for (;;) {
if (offset > length - 12) /* check end of data segment */
- return 0;
+ return FALSE;
/* Get Tag number */
- if (is_motorola) {
- tagnum = exif_data[offset];
- tagnum <<= 8;
- tagnum += exif_data[offset+1];
- }
- else {
- tagnum = exif_data[offset+1];
- tagnum <<= 8;
- tagnum += exif_data[offset];
- }
+ if (big_endian)
+ tagnum = (exif_data[offset] << 8) + exif_data[offset+1];
+ else
+ tagnum = (exif_data[offset+1] << 8) + exif_data[offset];
- if (tagnum == 0x0112) /* found Orientation Tag */
+ if (tagnum == 0x0112) { /* found Orientation Tag */
+ if (big_endian) {
+ if (exif_data[offset + 8] != 0)
+ return FALSE;
+ orientation = exif_data[offset + 9];
+ }
+ else {
+ if (exif_data[offset + 9] != 0)
+ return FALSE;
+ orientation = exif_data[offset + 8];
+ }
+ if (orientation > 8)
+ orientation = 0;
+ data->orientation = orientation;
break;
+ }
if (--number_of_tags == 0)
- return 0;
+ return FALSE;
offset += 12;
}
- /* Get the Orientation value */
+ return TRUE;
+}
+
+
+static gboolean
+_jpeg_exif_colorimetry_from_app1_segment (guchar *in_buffer,
+ gsize app1_segment_size,
+ JpegInfoData *data)
+{
+ int pos;
+ guint length;
+ gboolean big_endian;
+ guchar *exif_data;
+ guint offset, number_of_tags, tagnum;
+ int orientation;
+ int remaining_tags;
+
+ /* Length includes itself, so must be at least 2 */
+ /* Following Exif data length must be at least 6 */
- if (is_motorola) {
- if (exif_data[offset + 8] != 0)
- return 0;
- orientation = exif_data[offset + 9];
+ length = app1_segment_size;
+ if (length < 6)
+ return FALSE;
+
+ pos = 0;
+
+ /* Read Exif head, check for "Exif" */
+
+ if ((in_buffer[pos++] != 'E')
+ || (in_buffer[pos++] != 'x')
+ || (in_buffer[pos++] != 'i')
+ || (in_buffer[pos++] != 'f')
+ || (in_buffer[pos++] != 0)
+ || (in_buffer[pos++] != 0))
+ {
+ return FALSE;
+ }
+
+ /* Length of an IFD entry */
+
+ if (length < 12)
+ return FALSE;
+
+ exif_data = in_buffer + pos;
+
+ /* Discover byte order */
+
+ if ((exif_data[0] == 0x49) && (exif_data[1] == 0x49))
+ big_endian = FALSE;
+ else if ((exif_data[0] == 0x4D) && (exif_data[1] == 0x4D))
+ big_endian = TRUE;
+ else
+ return FALSE;
+
+ /* Check Tag Mark */
+
+ if (big_endian) {
+ if (exif_data[2] != 0)
+ return FALSE;
+ if (exif_data[3] != 0x2A)
+ return FALSE;
+ }
+ else {
+ if (exif_data[3] != 0)
+ return FALSE;
+ if (exif_data[2] != 0x2A)
+ return FALSE;
+ }
+
+ /* Get first IFD offset (offset to IFD0) */
+
+ if (big_endian) {
+ if (exif_data[4] != 0)
+ return FALSE;
+ if (exif_data[5] != 0)
+ return FALSE;
+ offset = (exif_data[6] << 8) + exif_data[7];
}
else {
- if (exif_data[offset + 9] != 0)
- return 0;
- orientation = exif_data[offset + 8];
+ if (exif_data[7] != 0)
+ return FALSE;
+ if (exif_data[6] != 0)
+ return FALSE;
+ offset = (exif_data[5] << 8) + exif_data[4];
}
- if (orientation > 8)
- orientation = 0;
+ if (offset > length - 2) /* check end of data segment */
+ return FALSE;
+
+ /* Get the number of directory entries contained in this IFD */
- return (GthTransform) orientation;
+ if (big_endian)
+ number_of_tags = (exif_data[offset] << 8) + exif_data[offset+1];
+ else
+ number_of_tags = (exif_data[offset+1] << 8) + exif_data[offset];
+ if (number_of_tags == 0)
+ return FALSE;
+
+ offset += 2;
+
+ /* Search the tags in IFD0 */
+
+ remaining_tags = 3;
+ for (;;) {
+ if (offset > length - 12) /* check end of data segment */
+ return FALSE;
+
+ /* Get Tag number */
+
+ if (big_endian)
+ tagnum = (exif_data[offset] << 8) + exif_data[offset+1];
+ else
+ tagnum = (exif_data[offset+1] << 8) + exif_data[offset];
+
+ if (tagnum == 0x012D) { /* TransferFunction */
+ remaining_tags--;
+ }
+
+ if (tagnum == 0x013E) { /* WhitePoint */
+ remaining_tags--;
+ }
+
+ if (tagnum == 0x013F) { /* PrimaryChromaticities */
+ remaining_tags--;
+ }
+
+ if (remaining_tags == 0)
+ break;
+
+ if (--number_of_tags == 0)
+ return FALSE;
+
+ offset += 12;
+ }
+
+ return TRUE;
}
@@ -341,6 +458,12 @@ _jpeg_get_icc_profile_chunk_from_app2_segment (guchar *in_buffer,
}
+#define _JPEG_MARKER_SOF0 0xc0
+#define _JPEG_MARKER_SOF1 0xc2
+#define _JPEG_MARKER_APP1 0xe1
+#define _JPEG_MARKER_APP2 0xe2
+
+
gboolean
_jpeg_info_get_from_stream (GInputStream *stream,
JpegInfoFlags flags,
@@ -357,9 +480,8 @@ _jpeg_info_get_from_stream (GInputStream *stream,
while ((marker_id = _jpeg_read_segment_marker (stream, cancellable, error)) != 0x00) {
gboolean segment_data_consumed = FALSE;
- if ((flags & _JPEG_INFO_IMAGE_SIZE)
- && ! (data->valid & _JPEG_INFO_IMAGE_SIZE)
- && ((marker_id == 0xc0) || (marker_id == 0xc2))) /* SOF0 or SOF1 */
+ if (((flags & _JPEG_INFO_IMAGE_SIZE) && ! (data->valid & _JPEG_INFO_IMAGE_SIZE))
+ && ((marker_id == _JPEG_MARKER_SOF0) || (marker_id == _JPEG_MARKER_SOF1)))
{
guint h, l;
guint size;
@@ -391,9 +513,9 @@ _jpeg_info_get_from_stream (GInputStream *stream,
segment_data_consumed = TRUE;
}
- if ((flags & _JPEG_INFO_EXIF_ORIENTATION)
- && ! (data->valid & _JPEG_INFO_EXIF_ORIENTATION)
- && (marker_id == 0xe1)) { /* APP1 */
+ if (((flags & _JPEG_INFO_EXIF_ORIENTATION) || (flags & _JPEG_INFO_EXIF_COLORIMETRY))
+ && (marker_id == _JPEG_MARKER_APP1))
+ {
guint h, l;
guint app1_segment_size;
guchar *app1_segment;
@@ -409,8 +531,15 @@ _jpeg_info_get_from_stream (GInputStream *stream,
cancellable,
error) > 0)
{
- data->valid |= _JPEG_INFO_EXIF_ORIENTATION;
- data->orientation = _jpeg_exif_orientation_from_app1_segment (app1_segment,
app1_segment_size);
+ if (flags & _JPEG_INFO_EXIF_ORIENTATION) {
+ if (_jpeg_exif_orientation_from_app1_segment (app1_segment,
app1_segment_size, data))
+ data->valid |= _JPEG_INFO_EXIF_ORIENTATION;
+ }
+
+ if (flags & _JPEG_INFO_EXIF_COLORIMETRY) {
+ if (_jpeg_exif_colorimetry_from_app1_segment (app1_segment,
app1_segment_size, data))
+ data->valid |= _JPEG_INFO_EXIF_ORIENTATION;
+ }
}
segment_data_consumed = TRUE;
@@ -418,10 +547,7 @@ _jpeg_info_get_from_stream (GInputStream *stream,
g_free (app1_segment);
}
- if ((flags & _JPEG_INFO_ICC_PROFILE)
- && ! (data->valid & _JPEG_INFO_ICC_PROFILE)
- && (marker_id == 0xe2)) /* APP2 */
- {
+ if ((flags & _JPEG_INFO_ICC_PROFILE) && (marker_id == _JPEG_MARKER_APP2)) {
guint h, l;
gsize app2_segment_size;
guchar *app2_segment;
@@ -442,8 +568,6 @@ _jpeg_info_get_from_stream (GInputStream *stream,
}
segment_data_consumed = TRUE;
-
- g_free (app2_segment);
}
if (! segment_data_consumed && ! _jpeg_skip_segment_data (stream, marker_id, cancellable,
error))
@@ -456,7 +580,7 @@ _jpeg_info_get_from_stream (GInputStream *stream,
GList *scan;
int seq_n;
- ostream = g_memory_output_stream_new_resizable ();
+ ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
icc_chunks = g_list_sort (icc_chunks, icc_chunk_compare);
seq_n = 1;
for (scan = icc_chunks; scan; scan = scan->next) {
@@ -472,7 +596,7 @@ _jpeg_info_get_from_stream (GInputStream *stream,
seq_n++;
}
- if (valid_icc) {
+ if (valid_icc && g_output_stream_close (ostream, NULL, NULL)) {
data->valid |= _JPEG_INFO_ICC_PROFILE;
data->icc_data = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream));
data->icc_data_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM
(ostream));
diff --git a/extensions/jpeg_utils/jpeg-info.h b/extensions/jpeg_utils/jpeg-info.h
index 09c718b..f475969 100644
--- a/extensions/jpeg_utils/jpeg-info.h
+++ b/extensions/jpeg_utils/jpeg-info.h
@@ -30,7 +30,8 @@ typedef enum /*< skip >*/ {
_JPEG_INFO_NONE = 0,
_JPEG_INFO_IMAGE_SIZE = 1 << 0,
_JPEG_INFO_EXIF_ORIENTATION = 1 << 1,
- _JPEG_INFO_ICC_PROFILE = 1 << 2
+ _JPEG_INFO_ICC_PROFILE = 1 << 2,
+ _JPEG_INFO_EXIF_COLORIMETRY = 1 << 3
} JpegInfoFlags;
typedef struct {
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
index 025f023..dba678f 100644
--- a/gthumb/Makefile.am
+++ b/gthumb/Makefile.am
@@ -296,6 +296,7 @@ gthumb_LDADD = \
$(LIBWEBP_LIBS) \
$(JSON_GLIB_LIBS) \
$(WEBKIT2_LIBS) \
+ $(LCMS2_LIBS) \
$(NULL)
if RUN_IN_PLACE
@@ -326,6 +327,7 @@ gthumb_CFLAGS = \
$(LIBSOUP_CFLAGS) \
$(LIBCHAMPLAIN_CFLAGS) \
$(SMCLIENT_CFLAGS) \
+ $(LCMS2_CFLAGS) \
-DGTHUMB_LOCALEDIR=\"$(datadir)/locale\" \
-DGTHUMB_PREFIX=\"$(prefix)\" \
-DGTHUMB_SYSCONFDIR=\"$(sysconfdir)\" \
diff --git a/gthumb/gth-browser.c b/gthumb/gth-browser.c
index 45e4296..5762667 100644
--- a/gthumb/gth-browser.c
+++ b/gthumb/gth-browser.c
@@ -23,6 +23,9 @@
#include <math.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
+#if HAVE_LCMS2
+#include <lcms2.h>
+#endif
#include "dlg-personalize-filters.h"
#include "glib-utils.h"
#include "gtk-utils.h"
@@ -178,6 +181,7 @@ struct _GthBrowserPrivate {
gboolean file_properties_on_the_right;
GthSidebarState viewer_sidebar;
BrowserState state;
+ GthICCProfile screen_profile;
/* settings */
@@ -2597,6 +2601,7 @@ gth_browser_finalize (GObject *object)
g_free (browser->priv->list_attributes);
_g_object_unref (browser->priv->folder_popup_file_data);
_g_object_unref (browser->priv->history_menu);
+ gth_icc_profile_free (browser->priv->screen_profile);
G_OBJECT_CLASS (gth_browser_parent_class)->finalize (object);
}
@@ -4125,6 +4130,7 @@ gth_browser_init (GthBrowser *browser)
browser->priv->desktop_interface_settings = g_settings_new (GNOME_DESKTOP_INTERFACE_SCHEMA);
browser->priv->file_properties_on_the_right = g_settings_get_boolean
(browser->priv->browser_settings, PREF_BROWSER_PROPERTIES_ON_THE_RIGHT);
browser->priv->menu_managers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
g_object_unref);
+ browser->priv->screen_profile = NULL;
browser_state_init (&browser->priv->state);
@@ -6638,6 +6644,43 @@ gth_browser_apply_editor_changes (GthBrowser *browser)
}
+GthICCProfile
+gth_browser_get_screen_profile (GthBrowser *browser)
+{
+#if HAVE_LCMS2
+ if (browser->priv->screen_profile == NULL) {
+ GdkScreen *screen;
+ char *atom_name;
+ GdkAtom type = GDK_NONE;
+ int format = 0;
+ int nitems = 0;
+ int monitor = 0;
+ guchar *data = NULL;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (browser));
+ if (gdk_screen_get_number (screen) > 0)
+ atom_name = g_strdup_printf ("_ICC_PROFILE_%d", gdk_screen_get_number (screen));
+ else
+ atom_name = g_strdup ("_ICC_PROFILE");
+
+ if (gdk_property_get (gdk_screen_get_root_window (screen),
+ gdk_atom_intern (atom_name, FALSE),
+ GDK_NONE,
+ 0, 64 * 1024 * 1024, FALSE,
+ &type, &format, &nitems, &data) && nitems > 0)
+ {
+ browser->priv->screen_profile = cmsOpenProfileFromMem (data, nitems);
+ g_free (data);
+ }
+
+ g_free (atom_name);
+
+ }
+#endif
+ return browser->priv->screen_profile;
+}
+
+
GMenuItem *
_g_menu_item_new_for_file (GFile *file,
const char *custom_label)
diff --git a/gthumb/gth-browser.h b/gthumb/gth-browser.h
index a0ddeef..d4b22a7 100644
--- a/gthumb/gth-browser.h
+++ b/gthumb/gth-browser.h
@@ -25,6 +25,7 @@
#include "gth-file-source.h"
#include "gth-file-store.h"
#include "gth-icon-cache.h"
+#include "gth-image.h"
#include "gth-menu-manager.h"
#include "gth-task.h"
#include "gth-window.h"
@@ -261,6 +262,7 @@ void gth_browser_ask_whether_to_save (GthBrowser *browser,
void gth_browser_save_state (GthBrowser *browser);
gboolean gth_browser_restore_state (GthBrowser *browser);
void gth_browser_apply_editor_changes (GthBrowser *browser);
+GthICCProfile gth_browser_get_screen_profile (GthBrowser *browser);
/* utilities */
diff --git a/gthumb/gth-image-loader.c b/gthumb/gth-image-loader.c
index a73f62d..8dfb901 100644
--- a/gthumb/gth-image-loader.c
+++ b/gthumb/gth-image-loader.c
@@ -33,6 +33,7 @@ struct _GthImageLoaderPrivate {
gboolean as_animation; /* Whether to load the image in a
* GdkPixbufAnimation structure. */
GthImageFormat preferred_format;
+ GthICCProfile out_profile;
GthImageLoaderFunc loader_func;
gpointer loader_data;
};
@@ -69,6 +70,7 @@ gth_image_loader_init (GthImageLoader *self)
self->priv->loader_func = NULL;
self->priv->loader_data = NULL;
self->priv->preferred_format = GTH_IMAGE_FORMAT_CAIRO_SURFACE;
+ self->priv->out_profile = NULL;
}
@@ -106,6 +108,15 @@ gth_image_loader_set_preferred_format (GthImageLoader *self,
}
+void
+gth_image_loader_set_out_profile (GthImageLoader *self,
+ GthICCProfile out_profile)
+{
+ g_return_if_fail (self != NULL);
+ self->priv->out_profile = out_profile;
+}
+
+
typedef struct {
GthFileData *file_data;
int requested_size;
@@ -206,6 +217,13 @@ load_image_thread (GSimpleAsyncResult *result,
_g_object_unref (istream);
+ if (! g_cancellable_is_cancelled (cancellable)
+ && (self->priv->out_profile != NULL)
+ && gth_image_get_icc_profile (image) != NULL)
+ {
+ gth_image_apply_icc_profile (image, self->priv->out_profile, cancellable);
+ }
+
if (g_cancellable_is_cancelled (cancellable)) {
g_clear_error (&error);
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "");
diff --git a/gthumb/gth-image-loader.h b/gthumb/gth-image-loader.h
index 2db3343..187db5b 100644
--- a/gthumb/gth-image-loader.h
+++ b/gthumb/gth-image-loader.h
@@ -57,6 +57,8 @@ void gth_image_loader_set_loader_func (GthImageLoader
gpointer loader_data);
void gth_image_loader_set_preferred_format (GthImageLoader *loader,
GthImageFormat preferred_format);
+void gth_image_loader_set_out_profile (GthImageLoader *loader,
+ GthICCProfile profile);
void gth_image_loader_load (GthImageLoader *loader,
GthFileData *file_data,
int requested_size,
diff --git a/gthumb/gth-image-preloader.c b/gthumb/gth-image-preloader.c
index 2159380..7c493d1 100644
--- a/gthumb/gth-image-preloader.c
+++ b/gthumb/gth-image-preloader.c
@@ -26,6 +26,7 @@
#include "cairo-scale.h"
#include "cairo-utils.h"
#include "glib-utils.h"
+#include "gth-image.h"
#include "gth-image-preloader.h"
#include "gth-image-utils.h"
#include "gth-marshal.h"
@@ -77,6 +78,7 @@ struct _GthImagePreloaderPrivate {
GthImageLoader *loader;
GQueue *cache;
guint load_next_id;
+ GthICCProfile out_profile;
};
@@ -277,6 +279,7 @@ gth_image_preloader_init (GthImagePreloader *self)
self->priv->loader = gth_image_loader_new (NULL, NULL);
self->priv->cache = g_queue_new ();
self->priv->load_next_id = 0;
+ self->priv->out_profile = NULL;
}
@@ -287,6 +290,15 @@ gth_image_preloader_new (void)
}
+void
+gth_image_preloader_set_out_profile (GthImagePreloader *self,
+ GthICCProfile out_profile)
+{
+ g_return_if_fail (self != NULL);
+ self->priv->out_profile = out_profile;
+}
+
+
/* -- gth_image_preloader_load -- */
@@ -707,6 +719,7 @@ _gth_image_preloader_load_current_file (GthImagePreloader *self,
g_print ("load %s @%d\n", g_file_get_uri (requested_file->file), ignore_requested_size ? -1 :
request->requested_size);
#endif
+ gth_image_loader_set_out_profile (self->priv->loader, self->priv->out_profile);
gth_image_loader_load (self->priv->loader,
requested_file,
ignore_requested_size ? -1 : request->requested_size,
diff --git a/gthumb/gth-image-preloader.h b/gthumb/gth-image-preloader.h
index 78134fd..98d29f9 100644
--- a/gthumb/gth-image-preloader.h
+++ b/gthumb/gth-image-preloader.h
@@ -51,6 +51,8 @@ struct _GthImagePreloaderClass {
GType gth_image_preloader_get_type (void) G_GNUC_CONST;
GthImagePreloader * gth_image_preloader_new (void);
+void gth_image_preloader_set_out_profile (GthImagePreloader
*loader,
+ GthICCProfile profile);
void gth_image_preloader_load (GthImagePreloader *self,
GthFileData *requested,
int
requested_size,
diff --git a/gthumb/gth-image.c b/gthumb/gth-image.c
index 020c468..c082644 100644
--- a/gthumb/gth-image.c
+++ b/gthumb/gth-image.c
@@ -21,8 +21,12 @@
#define GDK_PIXBUF_ENABLE_BACKEND 1
+#include <config.h>
#include <glib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
+#ifdef HAVE_LCMS2
+#include <lcms2.h>
+#endif /* HAVE_LCMS2 */
#include "cairo-utils.h"
#include "glib-utils.h"
#include "gth-image.h"
@@ -36,6 +40,7 @@ struct _GthImagePrivate {
GdkPixbuf *pixbuf;
GdkPixbufAnimation *pixbuf_animation;
} data;
+ GthICCProfile icc_profile;
};
@@ -68,12 +73,21 @@ _gth_image_free_data (GthImage *self)
static void
+_gth_image_free_icc_profile (GthImage *self)
+{
+ gth_icc_profile_free (self->priv->icc_profile);
+ self->priv->icc_profile = NULL;
+}
+
+
+static void
gth_image_finalize (GObject *object)
{
g_return_if_fail (object != NULL);
g_return_if_fail (GTH_IS_IMAGE (object));
_gth_image_free_data (GTH_IMAGE (object));
+ _gth_image_free_icc_profile (GTH_IMAGE (object));
/* Chain up */
G_OBJECT_CLASS (gth_image_parent_class)->finalize (object);
@@ -118,6 +132,7 @@ gth_image_init (GthImage *self)
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_IMAGE, GthImagePrivate);
self->priv->format = GTH_IMAGE_FORMAT_CAIRO_SURFACE;
self->priv->data.surface = NULL;
+ self->priv->icc_profile = NULL;
}
@@ -351,3 +366,170 @@ gth_image_get_is_animation (GthImage *image)
return ((image->priv->format == GTH_IMAGE_FORMAT_GDK_PIXBUF_ANIMATION)
&& (! gdk_pixbuf_animation_is_static_image (image->priv->data.pixbuf_animation)));
}
+
+
+void
+gth_image_set_icc_profile (GthImage *image,
+ GthICCProfile profile)
+{
+ _gth_image_free_icc_profile (image);
+ image->priv->icc_profile = profile;
+}
+
+
+GthICCProfile
+gth_image_get_icc_profile (GthImage *image)
+{
+ return image->priv->icc_profile;
+}
+
+
+/* -- gth_image_apply_icc_profile -- */
+
+
+#if HAVE_LCMS2
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN /* BGRA */
+#define _LCMS2_CAIRO_FORMAT TYPE_BGRA_8
+#elif G_BYTE_ORDER == G_BIG_ENDIAN /* ARGB */
+#define _LCMS2_CAIRO_FORMAT TYPE_ARGB_8
+#else
+#define _LCMS2_CAIRO_FORMAT TYPE_ABGR_8
+#endif
+#endif
+
+
+void
+gth_image_apply_icc_profile (GthImage *image,
+ GthICCProfile out_profile,
+ GCancellable *cancellable)
+{
+#if HAVE_LCMS2
+
+ cmsHTRANSFORM hTransform;
+ cairo_surface_t *surface;
+ unsigned char *surface_row;
+ int width;
+ int height;
+ int row_stride;
+ int row;
+
+ if (image->priv->format != GTH_IMAGE_FORMAT_CAIRO_SURFACE)
+ return;
+
+ hTransform = cmsCreateTransform ((cmsHPROFILE) image->priv->icc_profile,
+ _LCMS2_CAIRO_FORMAT,
+ (cmsHPROFILE) out_profile,
+ _LCMS2_CAIRO_FORMAT,
+ INTENT_PERCEPTUAL, 0);
+ if (hTransform == NULL)
+ return;
+
+ surface = gth_image_get_cairo_surface (image);
+ surface_row = _cairo_image_surface_flush_and_get_data (surface);
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+ row_stride = cairo_image_surface_get_stride (surface);
+
+ for (row = 0; row < height; row++) {
+ if (g_cancellable_is_cancelled (cancellable))
+ break;
+ cmsDoTransform (hTransform, surface_row, surface_row, width);
+ surface_row += row_stride;
+ }
+ cairo_surface_mark_dirty (surface);
+
+ cairo_surface_destroy (surface);
+ cmsDeleteTransform (hTransform);
+
+#endif
+}
+
+
+/* -- gth_image_apply_icc_profile_async -- */
+
+
+typedef struct {
+ GthImage *image;
+ GthICCProfile out_profile;
+} ApplyProfileData;
+
+
+static void
+apply_profile_data_free (gpointer user_data)
+{
+ ApplyProfileData *apd = user_data;
+
+ g_object_unref (apd->image);
+ g_free (apd);
+}
+
+
+static void
+_gth_image_apply_icc_profile_thread (GSimpleAsyncResult *result,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ ApplyProfileData *apd;
+ GError *error = NULL;
+
+ apd = g_simple_async_result_get_op_res_gpointer (result);
+ if ((apd->image->priv->icc_profile != NULL) && (apd->out_profile != NULL))
+ gth_image_apply_icc_profile (apd->image, apd->out_profile, cancellable);
+
+ if ((cancellable != NULL) && g_cancellable_is_cancelled (cancellable))
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "");
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+}
+
+
+void
+gth_image_apply_icc_profile_async (GthImage *image,
+ GthICCProfile out_profile,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ ApplyProfileData *apd;
+
+ g_return_if_fail (image != NULL);
+
+ result = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ gth_image_apply_icc_profile_async);
+
+ apd = g_new (ApplyProfileData, 1);
+ apd->image = g_object_ref (image);
+ apd->out_profile = out_profile;
+ g_simple_async_result_set_op_res_gpointer (result, apd, apply_profile_data_free);
+ g_simple_async_result_run_in_thread (result,
+ _gth_image_apply_icc_profile_thread,
+ G_PRIORITY_DEFAULT,
+ cancellable);
+
+ g_object_unref (result);
+}
+
+
+gboolean
+gth_image_apply_icc_profile_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
gth_image_apply_icc_profile_async), FALSE);
+ return g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+
+void
+gth_icc_profile_free (GthICCProfile icc_profile)
+{
+#ifdef HAVE_LCMS2
+ if (icc_profile != NULL)
+ cmsCloseProfile ((cmsHPROFILE) icc_profile);
+#endif
+}
diff --git a/gthumb/gth-image.h b/gthumb/gth-image.h
index 5800506..9528b13 100644
--- a/gthumb/gth-image.h
+++ b/gthumb/gth-image.h
@@ -25,6 +25,7 @@
#include <glib.h>
#include <glib-object.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gio/gio.h>
#include <cairo.h>
#include "gth-file-data.h"
@@ -37,6 +38,8 @@ typedef enum {
GTH_IMAGE_N_FORMATS
} GthImageFormat;
+typedef gpointer GthICCProfile;
+
#define GTH_TYPE_IMAGE (gth_image_get_type ())
#define GTH_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE, GthImage))
#define GTH_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE, GthImageClass))
@@ -97,6 +100,21 @@ void gth_image_set_pixbuf_animation (GthImage
GdkPixbufAnimation *value);
GdkPixbufAnimation * gth_image_get_pixbuf_animation (GthImage *image);
gboolean gth_image_get_is_animation (GthImage *image);
+void gth_image_set_icc_profile (GthImage *image,
+ GthICCProfile profile);
+GthICCProfile gth_image_get_icc_profile (GthImage *image);
+void gth_image_apply_icc_profile (GthImage *image,
+ GthICCProfile out_profile,
+ GCancellable *cancellable);
+void gth_image_apply_icc_profile_async (GthImage *image,
+ GthICCProfile out_profile,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gth_image_apply_icc_profile_finish (GAsyncResult *result,
+ GError **error);
+
+void gth_icc_profile_free (GthICCProfile icc_profile);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]