[gdk-pixbuf] Add icc-profile option to gdk-pixbuf for the JPEG image format
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gdk-pixbuf] Add icc-profile option to gdk-pixbuf for the JPEG image format
- Date: Mon, 3 Sep 2012 16:16:31 +0000 (UTC)
commit 0179dfdc2bfe02bcf3f3a2c92878be0c3da9c192
Author: Richard Hughes <richard hughsie com>
Date: Mon Sep 3 12:13:20 2012 -0400
Add icc-profile option to gdk-pixbuf for the JPEG image format
This makes it possible to provide a color profile when saving jpeg
files and to read color profile information when loading it.
https://bugzilla.gnome.org/show_bug.cgi?id=604610
gdk-pixbuf/gdk-pixbuf-io.c | 2 +-
gdk-pixbuf/io-jpeg.c | 468 ++++++++++++++++++++++++++++++--------------
2 files changed, 317 insertions(+), 153 deletions(-)
---
diff --git a/gdk-pixbuf/gdk-pixbuf-io.c b/gdk-pixbuf/gdk-pixbuf-io.c
index 00f9122..47f7748 100644
--- a/gdk-pixbuf/gdk-pixbuf-io.c
+++ b/gdk-pixbuf/gdk-pixbuf-io.c
@@ -2262,7 +2262,7 @@ gdk_pixbuf_real_save_to_callback (GdkPixbuf *pixbuf,
* be specified using the "compression" parameter; it's value is in an
* integer in the range of [0,9].
*
- * ICC color profiles can also be embedded into PNG and TIFF images.
+ * ICC color profiles can also be embedded into PNG, JPEG and TIFF images.
* The "icc-profile" value should be the complete ICC profile encoded
* into base64.
*
diff --git a/gdk-pixbuf/io-jpeg.c b/gdk-pixbuf/io-jpeg.c
index 8560c94..bcdf0bb 100644
--- a/gdk-pixbuf/io-jpeg.c
+++ b/gdk-pixbuf/io-jpeg.c
@@ -34,6 +34,8 @@
#include <setjmp.h>
#include <jpeglib.h>
#include <jerror.h>
+#include <math.h>
+
#include "gdk-pixbuf-private.h"
#ifndef HAVE_SIGSETJMP
@@ -81,6 +83,14 @@ typedef struct {
struct error_handler_data jerr;
} JpegProgContext;
+/* EXIF context */
+typedef struct {
+ gint orientation;
+ gchar *icc_profile;
+ gsize icc_profile_size;
+ gsize icc_profile_size_allocated;
+} JpegExifContext;
+
static GdkPixbuf *gdk_pixbuf__jpeg_image_load (FILE *f, GError **error);
static gpointer gdk_pixbuf__jpeg_image_begin_load (GdkPixbufModuleSizeFunc func0,
GdkPixbufModulePreparedFunc func1,
@@ -276,20 +286,11 @@ colorspace_name (const J_COLOR_SPACE jpeg_color_space)
}
}
-
-const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header
-const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header
-const char types[] = {0x00, 0x01, 0x01, 0x02, 0x04, 0x08, 0x00,
- 0x08, 0x00, 0x04, 0x08}; // size in bytes for EXIF types
-
#define DE_ENDIAN16(val) endian == G_BIG_ENDIAN ? GUINT16_FROM_BE(val) : GUINT16_FROM_LE(val)
#define DE_ENDIAN32(val) endian == G_BIG_ENDIAN ? GUINT32_FROM_BE(val) : GUINT32_FROM_LE(val)
-
+
#define ENDIAN16_IT(val) endian == G_BIG_ENDIAN ? GUINT16_TO_BE(val) : GUINT16_TO_LE(val)
#define ENDIAN32_IT(val) endian == G_BIG_ENDIAN ? GUINT32_TO_BE(val) : GUINT32_TO_LE(val)
-
-#define EXIF_JPEG_MARKER JPEG_APP0+1
-#define EXIF_IDENT_STRING "Exif\000\000"
static unsigned short de_get16(void *ptr, guint endian)
{
@@ -311,145 +312,219 @@ static unsigned int de_get32(void *ptr, guint endian)
return val;
}
-static gint
-get_orientation (j_decompress_ptr cinfo)
+/* application specific data segment */
+static gboolean
+jpeg_parse_exif_app2_segment (JpegExifContext *context, jpeg_saved_marker_ptr marker)
{
- /* This function looks through the meta data in the libjpeg decompress structure to
- determine if an EXIF Orientation tag is present and if so return its value (1-8).
- If no EXIF Orientation tag is found 0 (zero) is returned. */
-
- guint i; /* index into working buffer */
- guint orient_tag_id; /* endianed version of orientation tag ID */
- guint ret; /* Return value */
- guint offset; /* de-endianed offset in various situations */
- guint tags; /* number of tags in current ifd */
- guint type; /* de-endianed type of tag used as index into types[] */
- guint count; /* de-endianed count of elements in a tag */
- guint tiff = 0; /* offset to active tiff header */
- guint endian = 0; /* detected endian of data */
-
- jpeg_saved_marker_ptr exif_marker; /* Location of the Exif APP1 marker */
- jpeg_saved_marker_ptr cmarker; /* Location to check for Exif APP1 marker */
-
- /* check for Exif marker (also called the APP1 marker) */
- exif_marker = NULL;
- cmarker = cinfo->marker_list;
- while (cmarker) {
- if (cmarker->marker == EXIF_JPEG_MARKER) {
- /* The Exif APP1 marker should contain a unique
- identification string ("Exif\0\0"). Check for it. */
- if (!memcmp (cmarker->data, EXIF_IDENT_STRING, 6)) {
- exif_marker = cmarker;
- }
- }
- cmarker = cmarker->next;
+ guint ret = FALSE;
+ guint sequence_number;
+ guint number_of_chunks;
+ guint chunk_size;
+ guint offset;
+
+ /* do we have enough data? */
+ if (marker->data_length < 16)
+ goto out;
+
+ /* unique identification string */
+ if (memcmp (marker->data, "ICC_PROFILE\0", 12) != 0)
+ goto out;
+
+ /* get data about this segment */
+ sequence_number = marker->data[12];
+ number_of_chunks = marker->data[13];
+
+ /* this is invalid, the base offset is 1 */
+ if (sequence_number == 0)
+ goto out;
+
+ /* this is invalid, the base offset is 1 */
+ if (sequence_number > number_of_chunks)
+ goto out;
+
+ /* size includes the id (12 bytes), length field (1 byte), and sequence field (1 byte) */
+ chunk_size = marker->data_length - 14;
+ offset = (sequence_number - 1) * 0xffef;
+
+ /* Deal with the trivial profile (99% of images) to avoid allocating
+ * 64kb when we might only use a few kb. */
+ if (number_of_chunks == 1) {
+ if (context->icc_profile_size_allocated > 0)
+ goto out;
+ context->icc_profile_size = chunk_size;
+ context->icc_profile_size_allocated = chunk_size;
+ context->icc_profile = g_new (gchar, chunk_size);
+ /* copy the segment data to the profile space */
+ memcpy (context->icc_profile, marker->data + 14, chunk_size);
+ goto out;
+ }
+
+ /* There is no promise the APP2 segments are going to be in order, so we
+ * have to allocate a huge swathe of memory and fill in the gaps when
+ * (if) we get the segment.
+ * Theoretically this could be as much as 16Mb, but display profiles are
+ * vary rarely above 100kb, and printer profiles are usually less than
+ * 2Mb */
+ if (context->icc_profile_size_allocated == 0) {
+ context->icc_profile_size_allocated = number_of_chunks * 0xffff;
+ context->icc_profile = g_new0 (gchar, number_of_chunks * 0xffff);
}
-
- /* Did we find the Exif APP1 marker? */
- if (exif_marker == NULL)
- return 0;
- /* Do we have enough data? */
- if (exif_marker->data_length < 32)
- return 0;
+ /* check the data will fit in our previously allocated buffer */
+ if (offset + chunk_size > context->icc_profile_size_allocated)
+ goto out;
- /* Check for TIFF header and catch endianess */
- i = 0;
+ /* copy the segment data to the profile space */
+ memcpy (context->icc_profile + offset, marker->data + 14, chunk_size);
+
+ /* it's now this big plus the new data we've just copied */
+ context->icc_profile_size += chunk_size;
+
+ /* success */
+ ret = TRUE;
+out:
+ return ret;
+}
+
+static gboolean
+jpeg_parse_exif_app1 (JpegExifContext *context, jpeg_saved_marker_ptr marker)
+{
+ guint i;
+ guint ret = FALSE;
+ guint offset;
+ guint tags; /* number of tags in current ifd */
+ guint tag;
+ guint type;
+ guint count;
+ guint endian = 0; /* detected endian of data */
+ const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header
+ const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header
+
+ /* do we have enough data? */
+ if (marker->data_length < 4)
+ goto out;
+
+ /* unique identification string */
+ if (memcmp (marker->data, "Exif", 4) != 0)
+ goto out;
+
+ /* do we have enough data? */
+ if (marker->data_length < 32)
+ goto out;
/* Just skip data until TIFF header - it should be within 16 bytes from marker start.
Normal structure relative to APP1 marker -
0x0000: APP1 marker entry = 2 bytes
- 0x0002: APP1 length entry = 2 bytes
+ 0x0002: APP1 length entry = 2 bytes
0x0004: Exif Identifier entry = 6 bytes
- 0x000A: Start of TIFF header (Byte order entry) - 4 bytes
- - This is what we look for, to determine endianess.
+ 0x000A: Start of TIFF header (Byte order entry) - 4 bytes
+ - This is what we look for, to determine endianess.
0x000E: 0th IFD offset pointer - 4 bytes
- exif_marker->data points to the first data after the APP1 marker
+ marker->data points to the first data after the APP1 marker
and length entries, which is the exif identification string.
The TIFF header should thus normally be found at i=6, below,
and the pointer to IFD0 will be at 6+4 = 10.
- */
-
- while (i < 16) {
-
- /* Little endian TIFF header */
- if (memcmp (&exif_marker->data[i], leth, 4) == 0){
- endian = G_LITTLE_ENDIAN;
- }
-
- /* Big endian TIFF header */
- else if (memcmp (&exif_marker->data[i], beth, 4) == 0){
- endian = G_BIG_ENDIAN;
- }
-
- /* Keep looking through buffer */
- else {
- i++;
- continue;
- }
- /* We have found either big or little endian TIFF header */
- tiff = i;
- break;
- }
+ */
- /* So did we find a TIFF header or did we just hit end of buffer? */
- if (tiff == 0)
- return 0;
-
- /* Endian the orientation tag ID, to locate it more easily */
- orient_tag_id = ENDIAN16_IT(0x112);
-
- /* Read out the offset pointer to IFD0 */
- offset = de_get32(&exif_marker->data[i] + 4, endian);
- i = i + offset;
+ for (i=0; i<16; i++) {
+ /* little endian TIFF header */
+ if (memcmp (&marker->data[i], leth, 4) == 0) {
+ endian = G_LITTLE_ENDIAN;
+ ret = TRUE;
+ break;
+ }
- /* Check that we still are within the buffer and can read the tag count */
- if ((i + 2) > exif_marker->data_length)
- return 0;
+ /* big endian TIFF header */
+ if (memcmp (&marker->data[i], beth, 4) == 0) {
+ endian = G_BIG_ENDIAN;
+ ret = TRUE;
+ break;
+ }
+ }
+
+ /* could not find header */
+ if (!ret)
+ goto out;
+
+ /* read out the offset pointer to IFD0 */
+ offset = de_get32(&marker->data[i] + 4, endian);
+ i = i + offset;
+
+ /* check that we still are within the buffer and can read the tag count */
+ if ((i + 2) > marker->data_length) {
+ ret = FALSE;
+ goto out;
+ }
- /* Find out how many tags we have in IFD0. As per the TIFF spec, the first
+ /* find out how many tags we have in IFD0. As per the TIFF spec, the first
two bytes of the IFD contain a count of the number of tags. */
- tags = de_get16(&exif_marker->data[i], endian);
- i = i + 2;
+ tags = de_get16(&marker->data[i], endian);
+ i = i + 2;
- /* Check that we still have enough data for all tags to check. The tags
+ /* check that we still have enough data for all tags to check. The tags
are listed in consecutive 12-byte blocks. The tag ID, type, size, and
a pointer to the actual value, are packed into these 12 byte entries. */
- if ((i + tags * 12) > exif_marker->data_length)
- return 0;
+ if ((i + tags * 12) > marker->data_length) {
+ ret = FALSE;
+ goto out;
+ }
- /* Check through IFD0 for tags of interest */
+ /* check through IFD0 for tags */
while (tags--){
- type = de_get16(&exif_marker->data[i + 2], endian);
- count = de_get32(&exif_marker->data[i + 4], endian);
+ tag = de_get16(&marker->data[i + 0], endian);
+ type = de_get16(&marker->data[i + 2], endian);
+ count = de_get32(&marker->data[i + 4], endian);
+ offset = de_get32(&marker->data[i + 8], endian);
- /* Is this the orientation tag? */
- if (memcmp (&exif_marker->data[i], (char *) &orient_tag_id, 2) == 0){
-
- /* Check that type and count fields are OK. The orientation field
- will consist of a single (count=1) 2-byte integer (type=3). */
- if (type != 3 || count != 1) return 0;
-
- /* Return the orientation value. Within the 12-byte block, the
- pointer to the actual data is at offset 8. */
- ret = de_get16(&exif_marker->data[i + 8], endian);
- return ret <= 8 ? ret : 0;
+ /* orientation tag? */
+ if (tag == 0x112){
+
+ /* The orientation field should consist of a single 2-byte integer */
+ if (type != 0x3 || count != 1)
+ continue;
+
+ /* get the orientation value */
+ context->orientation = offset <= 8 ? offset : 0;
}
/* move the pointer to the next 12-byte tag field. */
i = i + 12;
}
- return 0; /* No EXIF Orientation tag found */
+out:
+ return ret;
}
+static void
+jpeg_parse_exif (JpegExifContext *context, j_decompress_ptr cinfo)
+{
+ jpeg_saved_marker_ptr cmarker;
+
+ /* check for interesting Exif markers */
+ cmarker = cinfo->marker_list;
+ while (cmarker != NULL) {
+ if (cmarker->marker == JPEG_APP0+1)
+ jpeg_parse_exif_app1 (context, cmarker);
+ else if (cmarker->marker == JPEG_APP0+2)
+ jpeg_parse_exif_app2_segment (context, cmarker);
+ cmarker = cmarker->next;
+ }
+}
+
+static void
+jpeg_destroy_exif_context (JpegExifContext *context)
+{
+ if (context == NULL)
+ return;
+ g_free (context->icc_profile);
+ g_free (context);
+}
/* Shared library entry point */
static GdkPixbuf *
gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
{
gint i;
- int is_otag;
char otag_str[5];
GdkPixbuf * volatile pixbuf = NULL;
guchar *dptr;
@@ -462,6 +537,8 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
struct jpeg_decompress_struct cinfo;
struct error_handler_data jerr;
stdio_src_ptr src;
+ gchar *icc_profile_base64;
+ JpegExifContext *exif_context;
/* setup error handler */
cinfo.err = jpeg_std_error (&jerr.pub);
@@ -500,11 +577,13 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
src->pub.next_input_byte = NULL; /* until buffer loaded */
- jpeg_save_markers (&cinfo, EXIF_JPEG_MARKER, 0xffff);
+ jpeg_save_markers (&cinfo, JPEG_APP0+1, 0xffff);
+ jpeg_save_markers (&cinfo, JPEG_APP0+2, 0xffff);
jpeg_read_header (&cinfo, TRUE);
- /* check for orientation tag */
- is_otag = get_orientation (&cinfo);
+ /* parse exif data */
+ exif_context = g_new0 (JpegExifContext, 1);
+ jpeg_parse_exif (exif_context, &cinfo);
jpeg_start_decompress (&cinfo);
cinfo.do_fancy_upsampling = FALSE;
@@ -530,12 +609,18 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
return NULL;
}
- /* if orientation tag was found set an option to remember its value */
- if (is_otag) {
- g_snprintf (otag_str, sizeof (otag_str), "%d", is_otag);
+ /* if orientation tag was found */
+ if (exif_context->orientation != 0) {
+ g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context->orientation);
gdk_pixbuf_set_option (pixbuf, "orientation", otag_str);
}
+ /* if icc profile was found */
+ if (exif_context->icc_profile != NULL) {
+ icc_profile_base64 = g_base64_encode ((const guchar *) exif_context->icc_profile, exif_context->icc_profile_size);
+ gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
+ g_free (icc_profile_base64);
+ }
dptr = pixbuf->pixels;
@@ -576,6 +661,7 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
jpeg_finish_decompress (&cinfo);
jpeg_destroy_decompress (&cinfo);
+ jpeg_destroy_exif_context (exif_context);
return pixbuf;
}
@@ -822,8 +908,9 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
gboolean first;
const guchar *bufhd;
gint width, height;
- int is_otag;
char otag_str[5];
+ JpegExifContext *exif_context = NULL;
+ gboolean retval;
g_return_val_if_fail (context != NULL, FALSE);
g_return_val_if_fail (buf != NULL, FALSE);
@@ -836,7 +923,8 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
/* check for fatal error */
if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
- return FALSE;
+ retval = FALSE;
+ goto out;
}
/* skip over data if requested, handle unsigned int sizes cleanly */
@@ -844,7 +932,8 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
if (context->src_initialized && src->skip_next) {
if (src->skip_next > size) {
src->skip_next -= size;
- return TRUE;
+ retval = TRUE;
+ goto out;
} else {
num_left = size - src->skip_next;
bufhd = buf + src->skip_next;
@@ -855,8 +944,12 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
bufhd = buf;
}
- if (num_left == 0)
- return TRUE;
+ if (num_left == 0) {
+ retval = TRUE;
+ goto out;
+ }
+ /* collect exif data */
+ exif_context = g_new0 (JpegExifContext, 1);
last_num_left = num_left;
last_bytes_left = 0;
@@ -895,14 +988,16 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
}
/* should not go through twice and not pull bytes out of buf */
- if (spinguard > 2)
- return TRUE;
+ if (spinguard > 2) {
+ retval = TRUE;
+ goto out;
+ }
/* try to load jpeg header */
if (!context->got_header) {
int rc;
- jpeg_save_markers (cinfo, EXIF_JPEG_MARKER, 0xffff);
+ jpeg_save_markers (cinfo, JPEG_APP0+1, 0xffff);
rc = jpeg_read_header (cinfo, TRUE);
context->src_initialized = TRUE;
@@ -911,8 +1006,8 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
context->got_header = TRUE;
- /* check for orientation tag */
- is_otag = get_orientation (cinfo);
+ /* parse exif data */
+ jpeg_parse_exif (exif_context, cinfo);
width = cinfo->image_width;
height = cinfo->image_height;
@@ -923,7 +1018,8 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
_("Transformed JPEG has zero width or height."));
- return FALSE;
+ retval = FALSE;
+ goto out;
}
}
@@ -948,12 +1044,13 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Couldn't allocate memory for loading JPEG file"));
- return FALSE;
+ retval = FALSE;
+ goto out;
}
/* if orientation tag was found set an option to remember its value */
- if (is_otag) {
- g_snprintf (otag_str, sizeof (otag_str), "%d", is_otag);
+ if (exif_context->orientation != 0) {
+ g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context->orientation);
gdk_pixbuf_set_option (context->pixbuf, "orientation", otag_str);
}
@@ -984,11 +1081,15 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
* simply get scanline by scanline from jpeg lib
*/
if (! gdk_pixbuf__jpeg_image_load_lines (context,
- error))
- return FALSE;
+ error)) {
+ retval = FALSE;
+ goto out;
+ }
- if (cinfo->output_scanline >= cinfo->output_height)
- return TRUE;
+ if (cinfo->output_scanline >= cinfo->output_height) {
+ retval = TRUE;
+ goto out;
+ }
} else {
/* we're decompressing buffered (progressive)
* so feed jpeg lib scanlines
@@ -1007,8 +1108,10 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
/* get scanlines from jpeg lib */
if (! gdk_pixbuf__jpeg_image_load_lines (context,
- error))
- return FALSE;
+ error)) {
+ retval = FALSE;
+ goto out;
+ }
if (cinfo->output_scanline >= cinfo->output_height &&
jpeg_finish_output (cinfo))
@@ -1016,13 +1119,18 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data,
else
break;
}
- if (jpeg_input_complete (cinfo))
+ if (jpeg_input_complete (cinfo)) {
/* did entire image */
- return TRUE;
+ retval = TRUE;
+ goto out;
+ }
else
continue;
}
}
+out:
+ jpeg_destroy_exif_context (exif_context);
+ return retval;
}
/* Save */
@@ -1120,6 +1228,10 @@ real_save_jpeg (GdkPixbuf *pixbuf,
int n_channels;
struct error_handler_data jerr;
ToFunctionDestinationManager to_callback_destmgr;
+ gchar *icc_profile = NULL;
+ gchar *data;
+ gint retval = TRUE;
+ gsize icc_profile_size = 0;
to_callback_destmgr.buffer = NULL;
@@ -1139,7 +1251,8 @@ real_save_jpeg (GdkPixbuf *pixbuf,
_("JPEG quality must be a value between 0 and 100; value '%s' could not be parsed."),
*viter);
- return FALSE;
+ retval = FALSE;
+ goto cleanup;
}
if (quality < 0 ||
@@ -1154,7 +1267,21 @@ real_save_jpeg (GdkPixbuf *pixbuf,
_("JPEG quality must be a value between 0 and 100; value '%d' is not allowed."),
quality);
- return FALSE;
+ retval = FALSE;
+ goto cleanup;
+ }
+ } else if (strcmp (*kiter, "icc-profile") == 0) {
+ /* decode from base64 */
+ icc_profile = (gchar*) g_base64_decode (*viter, &icc_profile_size);
+ if (icc_profile_size < 127) {
+ /* This is a user-visible error */
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_BAD_OPTION,
+ _("Color profile has invalid length '%d'."),
+ icc_profile_size);
+ retval = FALSE;
+ goto cleanup;
}
} else {
g_warning ("Unrecognized parameter (%s) passed to JPEG saver.", *kiter);
@@ -1181,7 +1308,8 @@ real_save_jpeg (GdkPixbuf *pixbuf,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Couldn't allocate memory for loading JPEG file"));
- return FALSE;
+ retval = FALSE;
+ goto cleanup;
}
if (to_callback) {
to_callback_destmgr.buffer = g_try_malloc (TO_FUNCTION_BUF_SIZE);
@@ -1190,8 +1318,8 @@ real_save_jpeg (GdkPixbuf *pixbuf,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
_("Couldn't allocate memory for loading JPEG file"));
- g_free (buf);
- return FALSE;
+ retval = FALSE;
+ goto cleanup;
}
}
@@ -1203,9 +1331,8 @@ real_save_jpeg (GdkPixbuf *pixbuf,
if (sigsetjmp (jerr.setjmp_buffer, 1)) {
jpeg_destroy_compress (&cinfo);
- g_free (buf);
- g_free (to_callback_destmgr.buffer);
- return FALSE;
+ retval = FALSE;
+ goto cleanup;
}
/* setup compress params */
@@ -1229,7 +1356,42 @@ real_save_jpeg (GdkPixbuf *pixbuf,
/* set up jepg compression parameters */
jpeg_set_defaults (&cinfo);
jpeg_set_quality (&cinfo, quality, TRUE);
+
jpeg_start_compress (&cinfo, TRUE);
+
+ /* write ICC profile data */
+ if (icc_profile != NULL) {
+ /* optimise for the common case where only one APP2 segment is required */
+ if (icc_profile_size < 0xffef) {
+ data = g_new (gchar, icc_profile_size + 14);
+ memcpy (data, "ICC_PROFILE\000\001\001", 14);
+ memcpy (data + 14, icc_profile, icc_profile_size);
+ jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, icc_profile_size + 14);
+ g_free (data);
+ } else {
+ guint segments;
+ guint size = 0xffef;
+ guint offset;
+
+ segments = (guint) ceilf ((gfloat) icc_profile_size / (gfloat) 0xffef);
+ data = g_new (gchar, 0xffff);
+ memcpy (data, "ICC_PROFILE\000", 12);
+ data[13] = segments;
+ for (i=0; i<=segments; i++) {
+ data[12] = i;
+ offset = 0xffef * i;
+
+ /* last segment */
+ if (i == segments)
+ size = icc_profile_size % 0xffef;
+
+ memcpy (data + 14, icc_profile + offset, size);
+ jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, size + 14);
+ }
+ g_free (data);
+ }
+ }
+
/* get the start pointer */
ptr = pixels;
/* go one scanline at a time... and save */
@@ -1246,13 +1408,15 @@ real_save_jpeg (GdkPixbuf *pixbuf,
y++;
}
-
+
/* finish off */
jpeg_finish_compress (&cinfo);
jpeg_destroy_compress(&cinfo);
- g_free (buf);
- g_free (to_callback_destmgr.buffer);
- return TRUE;
+cleanup:
+ g_free (buf);
+ g_free (to_callback_destmgr.buffer);
+ g_free (icc_profile);
+ return retval;
}
static gboolean
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]