[gthumb] image loader: added icc profile convertion



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]