Commit 2949dbaa authored by Paolo Bacchilega's avatar Paolo Bacchilega

image loader: added icc profile convertion

parent 3e04a97d
......@@ -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}
"
......@@ -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:
......
......@@ -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,
......
......@@ -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));
......
......@@ -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 {
......
......@@ -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)\" \
......
......@@ -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);