]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-png.c
Add color management support to gdk_pixbuf_save
[~andy/gtk] / gdk-pixbuf / io-png.c
index 2c98d63c4a5e8423b9f1863d4e2871f399ff712d..8b90865855a26f9c6f451ea50b36d34b3c49ec9c 100644 (file)
@@ -23,7 +23,7 @@
  * Boston, MA 02111-1307, USA.
  */
 
-#include <config.h>
+#include "config.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <png.h>
@@ -49,10 +49,10 @@ setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
         */
         bit_depth = png_get_bit_depth (png_read_ptr, png_info_ptr);
         if (bit_depth < 1 || bit_depth > 16) {
-                g_set_error (error,
-                             GDK_PIXBUF_ERROR,
-                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                             _("Bits per channel of PNG image is invalid."));
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Bits per channel of PNG image is invalid."));
                 return FALSE;
         }
         png_get_IHDR (png_read_ptr, png_info_ptr,
@@ -130,36 +130,36 @@ setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
         /* Check that the new info is what we want */
         
         if (width == 0 || height == 0) {
-                g_set_error (error,
-                             GDK_PIXBUF_ERROR,
-                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                             _("Transformed PNG has zero width or height."));
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Transformed PNG has zero width or height."));
                 return FALSE;
         }
 
         if (bit_depth != 8) {
-                g_set_error (error,
-                             GDK_PIXBUF_ERROR,
-                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                             _("Bits per channel of transformed PNG is not 8."));
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Bits per channel of transformed PNG is not 8."));
                 return FALSE;
         }
 
         if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
                 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
-                g_set_error (error,
-                             GDK_PIXBUF_ERROR,
-                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                             _("Transformed PNG not RGB or RGBA."));
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Transformed PNG not RGB or RGBA."));
                 return FALSE;
         }
 
         channels = png_get_channels(png_read_ptr, png_info_ptr);
         if ( ! (channels == 3 || channels == 4) ) {
-                g_set_error (error,
-                             GDK_PIXBUF_ERROR,
-                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                             _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
+                g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
                 return FALSE;
         }
         return TRUE;
@@ -258,6 +258,12 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
         gint    num_texts;
         gchar *key;
         gchar *value;
+        gchar *icc_profile_base64;
+        const gchar *icc_profile_title;
+        const gchar *icc_profile;
+        guint icc_profile_size;
+        guint32 retval;
+        gint compression_type;
 
 #ifdef PNG_USER_MEM_SUPPORTED
        png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
@@ -283,8 +289,7 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
        }
 
        if (setjmp (png_ptr->jmpbuf)) {
-               if (rows)
-                       g_free (rows);
+               g_free (rows);
 
                if (pixbuf)
                        g_object_unref (pixbuf);
@@ -305,10 +310,10 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
 
        if (!pixbuf) {
                 if (error && *error == NULL) {
-                        g_set_error (error,
-                                     GDK_PIXBUF_ERROR,
-                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
-                                     _("Insufficient memory to load PNG file"));
+                        g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Insufficient memory to load PNG file"));
                 }
                 
 
@@ -333,6 +338,18 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
                 }
         }
 
+#if defined(PNG_cHRM_SUPPORTED)
+        /* Extract embedded ICC profile */
+        retval = png_get_iCCP (png_ptr, info_ptr,
+                               (png_charpp) &icc_profile_title, &compression_type,
+                               (png_charpp) &icc_profile, (png_uint_32*) &icc_profile_size);
+        if (retval != 0) {
+                icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, icc_profile_size);
+                gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
+                g_free (icc_profile_base64);
+        }
+#endif
+
        g_free (rows);
        png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
 
@@ -344,8 +361,8 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
 static void png_error_callback  (png_structp png_read_ptr,
                                  png_const_charp error_msg);
 
-static void png_warning_callback(png_structp png_read_ptr,
-                                 png_const_charp warning_msg);
+static void png_warning_callback (png_structp png_read_ptr,
+                                  png_const_charp warning_msg);
 
 /* Called at the start of the progressive load */
 static void png_info_callback   (png_structp png_read_ptr,
@@ -587,7 +604,13 @@ png_info_callback   (png_structp png_read_ptr,
         int i, num_texts;
         int color_type;
         gboolean have_alpha = FALSE;
-        
+        gchar *icc_profile_base64;
+        const gchar *icc_profile_title;
+        const gchar *icc_profile;
+        guint icc_profile_size;
+        guint32 retval;
+        gint compression_type;
+
         lc = png_get_progressive_ptr(png_read_ptr);
 
         if (lc->fatal_error_occurred)
@@ -613,10 +636,10 @@ png_info_callback   (png_structp png_read_ptr,
                 if (w == 0 || h == 0) {
                         lc->fatal_error_occurred = TRUE;
                         if (lc->error && *lc->error == NULL) {
-                                g_set_error (lc->error,
-                                             GDK_PIXBUF_ERROR,
-                                             GDK_PIXBUF_ERROR_FAILED,
-                                             _("Transformed PNG has zero width or height."));
+                                g_set_error_literal (lc->error,
+                                                     GDK_PIXBUF_ERROR,
+                                                     GDK_PIXBUF_ERROR_FAILED,
+                                                     _("Transformed PNG has zero width or height."));
                         }
                         return;
                 }
@@ -652,11 +675,23 @@ png_info_callback   (png_structp png_read_ptr,
                 }
         }
 
+#if defined(PNG_cHRM_SUPPORTED)
+        /* Extract embedded ICC profile */
+        retval = png_get_iCCP (png_read_ptr, png_info_ptr,
+                               (png_charpp) &icc_profile_title, &compression_type,
+                               (png_charpp) &icc_profile, (png_uint_32*) &icc_profile_size);
+        if (retval != 0) {
+                icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, icc_profile_size);
+                gdk_pixbuf_set_option (lc->pixbuf, "icc-profile", icc_profile_base64);
+                g_free (icc_profile_base64);
+        }
+#endif
+
         /* Notify the client that we are ready to go */
 
         if (lc->prepare_func)
                 (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
-        
+
         return;
 }
 
@@ -679,10 +714,10 @@ png_row_callback   (png_structp png_read_ptr,
         if (row_num >= lc->pixbuf->height) {
                 lc->fatal_error_occurred = TRUE;
                 if (lc->error && *lc->error == NULL) {
-                        g_set_error (lc->error,
-                                     GDK_PIXBUF_ERROR,
-                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                                     _("Fatal error reading PNG image file"));
+                        g_set_error_literal (lc->error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                             _("Fatal error reading PNG image file"));
                 }
                 return;
         }
@@ -739,8 +774,8 @@ png_error_callback(png_structp png_read_ptr,
 }
 
 static void
-png_warning_callback(png_structp png_read_ptr,
-                     png_const_charp warning_msg)
+png_warning_callback (png_structp png_read_ptr,
+                      png_const_charp warning_msg)
 {
         LoadContext* lc;
         
@@ -792,7 +827,7 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
                                GdkPixbufSaveFunc save_func,
                                gpointer          user_data)
 {
-       png_structp png_ptr;
+       png_structp png_ptr = NULL;
        png_infop info_ptr;
        png_textp text_ptr = NULL;
        guchar *ptr;
@@ -807,6 +842,8 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
        int num_keys;
        int compression = -1;
        gboolean success = TRUE;
+       guchar *icc_profile = NULL;
+       gsize icc_profile_size = 0;
        SaveToFunctionIoPtr to_callback_ioptr;
 
        num_keys = 0;
@@ -820,22 +857,37 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
                                gchar  *key = *kiter + 6;
                                int     len = strlen (key);
                                if (len <= 1 || len > 79) {
-                                       g_set_error (error,
-                                                    GDK_PIXBUF_ERROR,
-                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
-                                                    _("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
-                                       return FALSE;
+                                       g_set_error_literal (error,
+                                                            GDK_PIXBUF_ERROR,
+                                                            GDK_PIXBUF_ERROR_BAD_OPTION,
+                                                            _("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
+                                       success = FALSE;
+                                       goto cleanup;
                                }
                                for (i = 0; i < len; i++) {
                                        if ((guchar) key[i] > 127) {
-                                               g_set_error (error,
-                                                            GDK_PIXBUF_ERROR,
-                                                            GDK_PIXBUF_ERROR_BAD_OPTION,
-                                                            _("Keys for PNG text chunks must be ASCII characters."));
-                                               return FALSE;
+                                               g_set_error_literal (error,
+                                                                    GDK_PIXBUF_ERROR,
+                                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
+                                                                    _("Keys for PNG text chunks must be ASCII characters."));
+                                               success = FALSE;
+                                               goto cleanup;
                                        }
                                }
                                num_keys++;
+                       } else if (strcmp (*kiter, "icc-profile") == 0) {
+                               /* decode from base64 */
+                               icc_profile = 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);
+                                       success = FALSE;
+                                       goto cleanup;
+                               }
                        } else if (strcmp (*kiter, "compression") == 0) {
                                char *endptr = NULL;
                                compression = strtol (*viter, &endptr, 10);
@@ -846,7 +898,8 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
                                                     _("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
                                                     *viter);
-                                       return FALSE;
+                                       success = FALSE;
+                                       goto cleanup;
                                }
                                if (compression < 0 || compression > 9) {
                                        /* This is a user-visible error;
@@ -858,12 +911,11 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
                                                     _("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
                                                     compression);
-                                       return FALSE;
+                                       success = FALSE;
+                                       goto cleanup;
                                }
                        } else {
-                               g_warning ("Bad option name '%s' passed to PNG saver",
-                                          *kiter);
-                               return FALSE;
+                               g_warning ("Unrecognized parameter (%s) passed to PNG saver.", *kiter);
                        }
 
                        ++kiter;
@@ -917,8 +969,10 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
                                           error,
                                           png_simple_error_callback,
                                           png_simple_warning_callback);
-
-       g_return_val_if_fail (png_ptr != NULL, FALSE);
+       if (png_ptr == NULL) {
+              success = FALSE;
+              goto cleanup;
+       }
 
        info_ptr = png_create_info_struct (png_ptr);
        if (info_ptr == NULL) {
@@ -948,6 +1002,15 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
        if (compression >= 0)
                png_set_compression_level (png_ptr, compression);
 
+#if defined(PNG_iCCP_SUPPORTED)
+        /* the proper ICC profile title is encoded in the profile */
+        if (icc_profile != NULL) {
+                png_set_iCCP (png_ptr, info_ptr,
+                              "ICC profile", PNG_COMPRESSION_TYPE_BASE,
+                              (gchar*) icc_profile, icc_profile_size);
+        }
+#endif
+
        if (has_alpha) {
                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
@@ -976,13 +1039,16 @@ static gboolean real_save_png (GdkPixbuf        *pixbuf,
        png_write_end (png_ptr, info_ptr);
 
 cleanup:
-       png_destroy_write_struct (&png_ptr, &info_ptr);
+        if (png_ptr != NULL)
+                png_destroy_write_struct (&png_ptr, &info_ptr);
 
-       if (num_keys > 0) {
-               for (i = 0; i < num_keys; i++)
-                       g_free (text_ptr[i].text);
-               g_free (text_ptr);
-       }
+        g_free (icc_profile);
+
+        if (text_ptr != NULL) {
+                for (i = 0; i < num_keys; i++)
+                        g_free (text_ptr[i].text);
+                g_free (text_ptr);
+        }
 
        return success;
 }
@@ -1011,13 +1077,12 @@ gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc   save_func,
 }
 
 #ifndef INCLUDE_png
-#define MODULE_ENTRY(type,function) function
+#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
 #else
-#define MODULE_ENTRY(type,function) _gdk_pixbuf__ ## type ## _ ## function
+#define MODULE_ENTRY(function) void _gdk_pixbuf__png_ ## function
 #endif
 
-void
-MODULE_ENTRY (png, fill_vtable) (GdkPixbufModule *module)
+MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
 {
         module->load = gdk_pixbuf__png_image_load;
         module->begin_load = gdk_pixbuf__png_image_begin_load;
@@ -1027,8 +1092,7 @@ MODULE_ENTRY (png, fill_vtable) (GdkPixbufModule *module)
         module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
 }
 
-void
-MODULE_ENTRY (png, fill_info) (GdkPixbufFormat *info)
+MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
 {
         static GdkPixbufModulePattern signature[] = {
                 { "\x89PNG\r\n\x1a\x0a", NULL, 100 },