[gthumb] jpeg info: make the api more generic, allow to load the icc profile



commit 3e04a97de7f49173fe4c9ed8eb10238421ec9154
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sun Jan 25 22:35:01 2015 +0100

    jpeg info: make the api more generic, allow to load the icc profile

 extensions/cairo_io/cairo-image-surface-jpeg.c     |   10 +-
 .../image_viewer/gth-metadata-provider-image.c     |   40 ++-
 extensions/jpeg_utils/jpeg-info.c                  |  308 +++++++++++++-------
 extensions/jpeg_utils/jpeg-info.h                  |   40 ++-
 4 files changed, 266 insertions(+), 132 deletions(-)
---
diff --git a/extensions/cairo_io/cairo-image-surface-jpeg.c b/extensions/cairo_io/cairo-image-surface-jpeg.c
index a9ecb61..0dd4201 100644
--- a/extensions/cairo_io/cairo-image-surface-jpeg.c
+++ b/extensions/cairo_io/cairo-image-surface-jpeg.c
@@ -164,6 +164,7 @@ _cairo_image_surface_create_from_jpeg (GInputStream  *istream,
        int                            pixel_step;
        void                          *in_buffer;
        gsize                          in_buffer_size;
+       JpegInfoData                   jpeg_info;
        struct error_handler_data      jsrcerr;
        struct jpeg_decompress_struct  srcinfo;
        cairo_surface_t               *surface;
@@ -191,6 +192,14 @@ _cairo_image_surface_create_from_jpeg (GInputStream  *istream,
                return image;
        }
 
+       _jpeg_info_data_init (&jpeg_info);
+       _jpeg_info_get_from_buffer (in_buffer, in_buffer_size, _JPEG_INFO_EXIF_ORIENTATION, &jpeg_info);
+       if (jpeg_info.valid & _JPEG_INFO_EXIF_ORIENTATION)
+               orientation = jpeg_info.orientation;
+       else
+               orientation = GTH_TRANSFORM_NONE;
+       _jpeg_info_data_dispose (&jpeg_info);
+
        srcinfo.err = jpeg_std_error (&(jsrcerr.pub));
        jsrcerr.pub.error_exit = fatal_error_handler;
        jsrcerr.pub.output_message = output_message_handler;
@@ -233,7 +242,6 @@ _cairo_image_surface_create_from_jpeg (GInputStream  *istream,
 
        jpeg_start_decompress (&srcinfo);
 
-       orientation = _jpeg_exif_orientation (in_buffer, in_buffer_size);
        _cairo_image_surface_transform_get_steps (CAIRO_FORMAT_ARGB32,
                                                  MIN (srcinfo.output_width, CAIRO_MAX_IMAGE_SIZE),
                                                  MIN (srcinfo.output_height, CAIRO_MAX_IMAGE_SIZE),
diff --git a/extensions/image_viewer/gth-metadata-provider-image.c 
b/extensions/image_viewer/gth-metadata-provider-image.c
index 1c077bc..13b45ea 100644
--- a/extensions/image_viewer/gth-metadata-provider-image.c
+++ b/extensions/image_viewer/gth-metadata-provider-image.c
@@ -120,7 +120,9 @@ gth_metadata_provider_image_read (GthMetadataProvider *self,
                        {
                                /* JPEG */
 
-                               GthTransform orientation;
+                               JpegInfoData jpeg_info;
+
+                               _jpeg_info_data_init (&jpeg_info);
 
                                if (g_seekable_can_seek (G_SEEKABLE (stream))) {
                                        g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, 
NULL);
@@ -130,27 +132,33 @@ gth_metadata_provider_image_read (GthMetadataProvider *self,
                                        stream = g_file_read (file_data->file, cancellable, NULL);
                                }
 
-                               if (_jpeg_get_image_info (G_INPUT_STREAM (stream),
-                                                         &width,
-                                                         &height,
-                                                         &orientation,
-                                                         cancellable,
-                                                         NULL))
-                               {
+                               _jpeg_info_get_from_stream (G_INPUT_STREAM (stream),
+                                                           _JPEG_INFO_IMAGE_SIZE | 
_JPEG_INFO_EXIF_ORIENTATION,
+                                                           &jpeg_info,
+                                                           cancellable,
+                                                           NULL);
+
+                               if (jpeg_info.valid & _JPEG_INFO_IMAGE_SIZE) {
+                                       width = jpeg_info.width;
+                                       height = jpeg_info.height;
                                        description = _("JPEG");
                                        mime_type = "image/jpeg";
                                        format_recognized = TRUE;
 
-                                       if ((orientation == GTH_TRANSFORM_ROTATE_90)
-                                            || (orientation == GTH_TRANSFORM_ROTATE_270)
-                                            || (orientation == GTH_TRANSFORM_TRANSPOSE)
-                                            || (orientation == GTH_TRANSFORM_TRANSVERSE))
-                                       {
-                                               int tmp = width;
-                                               width = height;
-                                               height = tmp;
+                                       if (jpeg_info.valid & _JPEG_INFO_EXIF_ORIENTATION) {
+                                               if ((jpeg_info.orientation == GTH_TRANSFORM_ROTATE_90)
+                                                    || (jpeg_info.orientation == GTH_TRANSFORM_ROTATE_270)
+                                                    || (jpeg_info.orientation == GTH_TRANSFORM_TRANSPOSE)
+                                                    || (jpeg_info.orientation == GTH_TRANSFORM_TRANSVERSE))
+                                               {
+                                                       int tmp = width;
+                                                       width = height;
+                                                       height = tmp;
+                                               }
                                        }
                                }
+
+                               _jpeg_info_data_dispose (&jpeg_info);
                        }
 #endif /* HAVE_LIBJPEG */
 
diff --git a/extensions/jpeg_utils/jpeg-info.c b/extensions/jpeg_utils/jpeg-info.c
index 915320e..8626cb1 100644
--- a/extensions/jpeg_utils/jpeg-info.c
+++ b/extensions/jpeg_utils/jpeg-info.c
@@ -3,7 +3,7 @@
 /*
  *  GThumb
  *
- *  Copyright (C) 2011 Free Software Foundation, Inc.
+ *  Copyright (C) 2011-2015 Free Software Foundation, Inc.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -23,6 +23,28 @@
 #include <config.h>
 #include "jpeg-info.h"
 
+
+void
+_jpeg_info_data_init (JpegInfoData *data)
+{
+       data->valid = _JPEG_INFO_NONE;
+       data->width = 0;
+       data->height = 0;
+       data->orientation = GTH_TRANSFORM_NONE;
+       data->icc_data = NULL;
+       data->icc_data_size = 0;
+}
+
+
+void
+_jpeg_info_data_dispose (JpegInfoData *data)
+{
+       if (data->valid & _JPEG_INFO_ICC_PROFILE) {
+               g_free (data->icc_data);
+       }
+}
+
+
 static guchar
 _g_input_stream_read_byte (GInputStream  *stream,
                           GCancellable  *cancellable,
@@ -89,25 +111,6 @@ _jpeg_skip_segment_data (GInputStream  *stream,
 }
 
 
-static gboolean
-_jpeg_skip_to_segment (GInputStream  *stream,
-                      guchar         segment_id,
-                      GCancellable  *cancellable,
-                      GError       **error)
-{
-       guchar marker_id = 0x00;
-
-       while ((marker_id = _jpeg_read_segment_marker (stream, cancellable, error)) != 0x00) {
-               if (marker_id == segment_id)
-                       return TRUE;
-               if (! _jpeg_skip_segment_data (stream, marker_id, cancellable, error))
-                       return FALSE;
-       }
-
-       return FALSE;
-}
-
-
 static GthTransform
 _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
                                          gsize   app1_segment_size)
@@ -130,10 +133,10 @@ _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
 
        /* Read Exif head, check for "Exif" */
 
-       if ((in_buffer[pos++] != 0x45)
-           || (in_buffer[pos++] != 0x78)
-           || (in_buffer[pos++] != 0x69)
-           || (in_buffer[pos++] != 0x66)
+       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))
        {
@@ -260,34 +263,112 @@ _jpeg_exif_orientation_from_app1_segment (guchar *in_buffer,
 }
 
 
-gboolean
-_jpeg_get_image_info (GInputStream  *stream,
-                     int           *width,
-                     int           *height,
-                     GthTransform  *orientation,
-                     GCancellable  *cancellable,
-                     GError       **error)
+/* -- _jpeg_get_icc_profile_chunk_from_app2_segment -- */
+
+
+typedef struct {
+       int      seq_n;
+       int      tot;
+       guchar  *in_buffer;
+       guchar  *data;
+       gsize    size;
+} ICCProfileChunk;
+
+
+static void
+icc_profile_chunk_free (ICCProfileChunk *chunk)
 {
-       gboolean size_read;
-       guchar   marker_id;
+       g_free (chunk->in_buffer);
+       g_free (chunk);
+}
 
-       size_read = FALSE;
 
-       if (orientation != NULL)
-               *orientation = GTH_TRANSFORM_NONE;
+static int
+icc_chunk_compare (gconstpointer a,
+                  gconstpointer b)
+{
+       const ICCProfileChunk *chunk_a = a;
+       const ICCProfileChunk *chunk_b = b;
+
+       if (chunk_a->seq_n < chunk_b->seq_n)
+               return -1;
+       if (chunk_a->seq_n > chunk_b->seq_n)
+               return 1;
+       return 0;
+}
+
+
+static ICCProfileChunk *
+_jpeg_get_icc_profile_chunk_from_app2_segment (guchar *in_buffer,
+                                              gsize   app2_segment_size)
+{
+       int              pos;
+       guint            length;
+       ICCProfileChunk *chunk;
+
+       length = app2_segment_size;
+       if (length <= 14)
+               return NULL;
+
+       pos = 0;
+
+       /* check for "ICC_PROFILE" */
+
+       if ((in_buffer[pos++] != 'I')
+           || (in_buffer[pos++] != 'C')
+           || (in_buffer[pos++] != 'C')
+           || (in_buffer[pos++] != '_')
+           || (in_buffer[pos++] != 'P')
+           || (in_buffer[pos++] != 'R')
+           || (in_buffer[pos++] != 'O')
+           || (in_buffer[pos++] != 'F')
+           || (in_buffer[pos++] != 'I')
+           || (in_buffer[pos++] != 'L')
+           || (in_buffer[pos++] != 'E')
+           || (in_buffer[pos++] != 0))
+       {
+               return NULL;
+       }
+
+       chunk = g_new (ICCProfileChunk, 1);
+       chunk->in_buffer = in_buffer;
+       chunk->seq_n = in_buffer[pos++];
+       chunk->tot = in_buffer[pos++];
+       chunk->data = in_buffer + 14;
+       chunk->size = app2_segment_size - 14;
+
+       return chunk;
+}
+
 
+gboolean
+_jpeg_info_get_from_stream (GInputStream        *stream,
+                           JpegInfoFlags         flags,
+                           JpegInfoData         *data,
+                           GCancellable         *cancellable,
+                           GError              **error)
+{
+       GList    *icc_chunks;
+       guchar    marker_id;
+
+       g_return_val_if_fail (data->valid == _JPEG_INFO_NONE, FALSE);
+
+       icc_chunks = NULL;
        while ((marker_id = _jpeg_read_segment_marker (stream, cancellable, error)) != 0x00) {
                gboolean segment_data_consumed = FALSE;
 
-               if ((marker_id == 0xc0) || (marker_id == 0xc2)) { /* SOF0 or SOF1 */
+               if ((flags & _JPEG_INFO_IMAGE_SIZE)
+                   && ! (data->valid & _JPEG_INFO_IMAGE_SIZE)
+                   && ((marker_id == 0xc0) || (marker_id == 0xc2))) /* SOF0 or SOF1 */
+               {
                        guint h, l;
-                       /*guint size;*/
+                       guint size;
 
                        /* size */
 
                        h = _g_input_stream_read_byte (stream, cancellable, error);
                        l = _g_input_stream_read_byte (stream, cancellable, error);
-                       /*size = (h << 8) + l;*/
+                       size = (h << 8) + l;
 
                        /* data precision */
 
@@ -297,107 +378,128 @@ _jpeg_get_image_info (GInputStream  *stream,
 
                        h = _g_input_stream_read_byte (stream, cancellable, error);
                        l = _g_input_stream_read_byte (stream, cancellable, error);
-                       if (height != NULL)
-                               *height = (h << 8) + l;
+                       data->height = (h << 8) + l;
 
                        /* width */
 
                        h = _g_input_stream_read_byte (stream, cancellable, error);
                        l = _g_input_stream_read_byte (stream, cancellable, error);
-                       if (width != NULL)
-                               *width = (h << 8) + l;
-
-                       size_read = TRUE;
+                       data->width = (h << 8) + l;
 
-                       /* Skip to the end of the segment. In general this is
-                        * needed but in this case we exit after reading the
-                        * size. */
-                       /* g_input_stream_skip (stream, size - 7, cancellable, error); */
+                       g_input_stream_skip (stream, size - 7, cancellable, error);
 
                        segment_data_consumed = TRUE;
                }
-               else if (marker_id == 0xe1) { /* APP1 */
+
+               if ((flags & _JPEG_INFO_EXIF_ORIENTATION)
+                   && ! (data->valid & _JPEG_INFO_EXIF_ORIENTATION)
+                   && (marker_id == 0xe1)) { /* APP1 */
                        guint   h, l;
-                       gsize   size;
+                       guint   app1_segment_size;
                        guchar *app1_segment;
 
-                       /* size */
-
                        h = _g_input_stream_read_byte (stream, cancellable, error);
                        l = _g_input_stream_read_byte (stream, cancellable, error);
-                       size = (h << 8) + l - 2;
+                       app1_segment_size = (h << 8) + l - 2;
 
-                       app1_segment = g_new (guchar, size);
-                       if (g_input_stream_read (stream, app1_segment, size, cancellable, error) > 0)
-                               *orientation = _jpeg_exif_orientation_from_app1_segment (app1_segment, size);
+                       app1_segment = g_new (guchar, app1_segment_size);
+                       if (g_input_stream_read (stream,
+                                                app1_segment,
+                                                app1_segment_size,
+                                                cancellable,
+                                                error) > 0)
+                       {
+                               data->valid |= _JPEG_INFO_EXIF_ORIENTATION;
+                               data->orientation = _jpeg_exif_orientation_from_app1_segment (app1_segment, 
app1_segment_size);
+                       }
 
                        segment_data_consumed = TRUE;
 
                        g_free (app1_segment);
                }
 
-               /* If we have read the size we are done because the APP1
-                * segment, if present, is always the first.  */
+               if ((flags & _JPEG_INFO_ICC_PROFILE)
+                   && ! (data->valid & _JPEG_INFO_ICC_PROFILE)
+                   && (marker_id == 0xe2)) /* APP2 */
+               {
+                       guint   h, l;
+                       gsize   app2_segment_size;
+                       guchar *app2_segment;
 
-               if (size_read)
-                       break;
+                       /* size */
+
+                       h = _g_input_stream_read_byte (stream, cancellable, error);
+                       l = _g_input_stream_read_byte (stream, cancellable, error);
+                       app2_segment_size = (h << 8) + l - 2;
+
+                       app2_segment = g_new (guchar, app2_segment_size);
+                       if (g_input_stream_read (stream, app2_segment, app2_segment_size, cancellable, error) 
0) {
+                               ICCProfileChunk *chunk;
+
+                               chunk = _jpeg_get_icc_profile_chunk_from_app2_segment (app2_segment, 
app2_segment_size);
+                               if (chunk != NULL)
+                                       icc_chunks = g_list_prepend (icc_chunks, chunk);
+                       }
+
+                       segment_data_consumed = TRUE;
+
+                       g_free (app2_segment);
+               }
 
                if (! segment_data_consumed && ! _jpeg_skip_segment_data (stream, marker_id, cancellable, 
error))
                        break;
        }
 
-       return size_read;
-}
+       if (flags & _JPEG_INFO_ICC_PROFILE) {
+               gboolean         valid_icc = (icc_chunks != NULL);
+               GOutputStream   *ostream;
+               GList           *scan;
+               int              seq_n;
+
+               ostream = g_memory_output_stream_new_resizable ();
+               icc_chunks = g_list_sort (icc_chunks, icc_chunk_compare);
+               seq_n = 1;
+               for (scan = icc_chunks; scan; scan = scan->next) {
+                       ICCProfileChunk *chunk = scan->data;
+
+                       if (chunk->seq_n != seq_n) {
+                               valid_icc = FALSE;
+                               g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ICC 
data");
+                               break;
+                       }
 
+                       g_output_stream_write_all (ostream, chunk->data, chunk->size, NULL, cancellable, 
error);
+                       seq_n++;
+               }
 
-GthTransform
-_jpeg_exif_orientation (guchar *in_buffer,
-                       gsize   in_buffer_size)
-{
-       GInputStream *stream;
-       GthTransform  orientation;
+               if (valid_icc) {
+                       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));
+               }
 
-       stream = g_memory_input_stream_new_from_data (in_buffer, in_buffer_size, NULL);
-       orientation = _jpeg_exif_orientation_from_stream (stream, NULL, NULL);
+               g_object_unref (ostream);
+       }
 
-       g_object_unref (stream);
+       g_list_free_full (icc_chunks, (GDestroyNotify) icc_profile_chunk_free);
 
-       return orientation;
+       return (flags == data->valid);
 }
 
 
-GthTransform
-_jpeg_exif_orientation_from_stream (GInputStream  *stream,
-                                   GCancellable  *cancellable,
-                                   GError       **error)
+gboolean
+_jpeg_info_get_from_buffer (guchar              *in_buffer,
+                           gsize                 in_buffer_size,
+                           JpegInfoFlags         flags,
+                           JpegInfoData         *data)
 {
-       GthTransform   orientation;
-
-       orientation = GTH_TRANSFORM_NONE;
-
-       if (_jpeg_read_segment_marker (stream, cancellable, error) == 0xd8) {
-               if (_jpeg_skip_to_segment (stream, 0xe1, cancellable, error)) {
-                       guint   h, l;
-                       guint   app1_segment_size;
-                       guchar *app1_segment;
-
-                       h = _g_input_stream_read_byte (stream, cancellable, error);
-                       l = _g_input_stream_read_byte (stream, cancellable, error);
-                       app1_segment_size = (h << 8) + l - 2;
+       GInputStream *stream;
+       gboolean      result;
 
-                       app1_segment = g_new (guchar, app1_segment_size);
-                       if (g_input_stream_read (stream,
-                                                app1_segment,
-                                                app1_segment_size,
-                                                cancellable,
-                                                error) > 0)
-                       {
-                               orientation = _jpeg_exif_orientation_from_app1_segment (app1_segment, 
app1_segment_size);
-                       }
+       stream = g_memory_input_stream_new_from_data (in_buffer, in_buffer_size, NULL);
+       result = _jpeg_info_get_from_stream (stream, flags, data, NULL, NULL);
 
-                       g_free (app1_segment);
-               }
-       }
+       g_object_unref (stream);
 
-       return orientation;
+       return result;
 }
diff --git a/extensions/jpeg_utils/jpeg-info.h b/extensions/jpeg_utils/jpeg-info.h
index 4807fa3..09c718b 100644
--- a/extensions/jpeg_utils/jpeg-info.h
+++ b/extensions/jpeg_utils/jpeg-info.h
@@ -3,7 +3,7 @@
 /*
  *  GThumb
  *
- *  Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *  Copyright (C) 2001-2015 The Free Software Foundation, Inc.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -26,16 +26,32 @@
 
 #define JPEG_SEGMENT_MAX_SIZE (64 * 1024)
 
-gboolean      _jpeg_get_image_info                     (GInputStream      *stream,
-                                                       int               *width,
-                                                       int               *height,
-                                                       GthTransform      *orientation,
-                                                       GCancellable      *cancellable,
-                                                       GError           **error);
-GthTransform  _jpeg_exif_orientation                   (guchar            *in_buffer,
-                                                       gsize              in_buffer_size);
-GthTransform  _jpeg_exif_orientation_from_stream       (GInputStream      *stream,
-                                                       GCancellable      *cancellable,
-                                                       GError           **error);
+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
+} JpegInfoFlags;
+
+typedef struct {
+       JpegInfoFlags   valid;
+       int             width;
+       int             height;
+       GthTransform    orientation;
+       gpointer        icc_data;
+       gsize           icc_data_size;
+} JpegInfoData;
+
+void           _jpeg_info_data_init                    (JpegInfoData            *data);
+void           _jpeg_info_data_dispose                 (JpegInfoData            *data);
+gboolean       _jpeg_info_get_from_stream              (GInputStream            *stream,
+                                                        JpegInfoFlags            flags,
+                                                        JpegInfoData            *data,
+                                                        GCancellable            *cancellable,
+                                                        GError                 **error);
+gboolean       _jpeg_info_get_from_buffer              (guchar                  *in_buffer,
+                                                        gsize                    in_buffer_size,
+                                                        JpegInfoFlags            flags,
+                                                        JpegInfoData            *data);
 
 #endif /* JPEG_INFO_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]