]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-png.c
Pass TRUE for search_sensitive - prevents a problem where after hitting
[~andy/gtk] / gdk-pixbuf / io-png.c
index 33fa2cf68f6c923b470e8ffba8e34944ebdc9ba9..5ddca7510836aa1b5dad842af4a0a8a24827d89e 100644 (file)
@@ -1,3 +1,4 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
 /* GdkPixbuf library - PNG image loader
  *
  * Copyright (C) 1999 Mark Crichton
@@ -31,9 +32,9 @@
 
 \f
 
-static void
+static gboolean
 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
-                          gboolean *fatal_error_occurred,
+                          GError **error,
                           png_uint_32* width_p, png_uint_32* height_p,
                           int* color_type_p)
 {
@@ -43,6 +44,17 @@ setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
         
         /* Get the image info */
 
+        /* Must check bit depth, since png_get_IHDR generates an 
+           FPE on bit_depth 0.
+        */
+        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."));
+                return FALSE;
+        }
         png_get_IHDR (png_read_ptr, png_info_ptr,
                       &width, &height,
                       &bit_depth,
@@ -115,51 +127,142 @@ setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
         *height_p = height;
         *color_type_p = color_type;
         
-#ifndef G_DISABLE_CHECKS
         /* 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."));
+                return FALSE;
+        }
+
         if (bit_depth != 8) {
-                g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
-                *fatal_error_occurred = TRUE;
-                return;
+                g_set_error (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_warning("Transformed PNG not RGB or RGBA.");
-                *fatal_error_occurred = TRUE;
-                return;
+                g_set_error (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_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
-                *fatal_error_occurred = TRUE;
-                return;
+                g_set_error (error,
+                             GDK_PIXBUF_ERROR,
+                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                             _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
+                return FALSE;
         }
-#endif
+        return TRUE;
 }
 
-/* Destroy notification function for the pixbuf */
 static void
-free_buffer (guchar *pixels, gpointer data)
+png_simple_error_callback(png_structp png_save_ptr,
+                          png_const_charp error_msg)
 {
-       free (pixels);
+        GError **error;
+        
+        error = png_get_error_ptr(png_save_ptr);
+
+        /* I don't trust libpng to call the error callback only once,
+         * so check for already-set error
+         */
+        if (error && *error == NULL) {
+                g_set_error (error,
+                             GDK_PIXBUF_ERROR,
+                             GDK_PIXBUF_ERROR_FAILED,
+                             _("Fatal error in PNG image file: %s"),
+                             error_msg);
+        }
+
+        longjmp (png_save_ptr->jmpbuf, 1);
+}
+
+static void
+png_simple_warning_callback(png_structp png_save_ptr,
+                            png_const_charp warning_msg)
+{
+        /* Don't print anything; we should not be dumping junk to
+         * stderr, since that may be bad for some apps. If it's
+         * important enough to display, we need to add a GError
+         * **warning return location wherever we have an error return
+         * location.
+         */
+}
+
+static gboolean
+png_text_to_pixbuf_option (png_text   text_ptr,
+                           gchar    **key,
+                           gchar    **value)
+{
+        if (text_ptr.text_length > 0) {
+                *value = g_convert (text_ptr.text, -1, 
+                                    "UTF-8", "ISO-8859-1", 
+                                    NULL, NULL, NULL);
+        }
+        else {
+                *value = g_strdup (text_ptr.text);
+        }
+        if (*value) {
+                *key = g_strconcat ("tEXt::", text_ptr.key, NULL);
+                return TRUE;
+        } else {
+                g_warning ("Couldn't convert text chunk value to UTF-8.");
+                *key = NULL;
+                return FALSE;
+        }
+}
+
+static png_voidp
+png_malloc_callback (png_structp o, png_size_t size)
+{
+        return g_try_malloc (size);
+}
+
+static void
+png_free_callback (png_structp o, png_voidp x)
+{
+        g_free (x);
 }
 
 /* Shared library entry point */
-GdkPixbuf *
-gdk_pixbuf__png_image_load (FILE *f)
+static GdkPixbuf *
+gdk_pixbuf__png_image_load (FILE *f, GError **error)
 {
+        GdkPixbuf * volatile pixbuf = NULL;
        png_structp png_ptr;
-       png_infop info_ptr, end_info;
-        gboolean failed = FALSE;
-       gint i, ctype, bpp;
+       png_infop info_ptr;
+        png_textp text_ptr;
+       gint i, ctype;
        png_uint_32 w, h;
-       png_bytepp rows;
-       guchar *pixels;
-
-       png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+       png_bytepp volatile rows = NULL;
+        gint    num_texts;
+        gchar *key;
+        gchar *value;
+
+#ifdef PNG_USER_MEM_SUPPORTED
+       png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
+                                            error,
+                                            png_simple_error_callback,
+                                            png_simple_warning_callback,
+                                            NULL, 
+                                            png_malloc_callback, 
+                                            png_free_callback);
+#else
+       png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
+                                          error,
+                                          png_simple_error_callback,
+                                          png_simple_warning_callback);
+#endif
        if (!png_ptr)
                return NULL;
 
@@ -169,55 +272,61 @@ gdk_pixbuf__png_image_load (FILE *f)
                return NULL;
        }
 
-       end_info = png_create_info_struct (png_ptr);
-       if (!end_info) {
-               png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
-               return NULL;
-       }
-
        if (setjmp (png_ptr->jmpbuf)) {
-               png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
+               if (rows)
+                       g_free (rows);
+
+               if (pixbuf)
+                       g_object_unref (pixbuf);
+
+               png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
                return NULL;
        }
 
        png_init_io (png_ptr, f);
        png_read_info (png_ptr, info_ptr);
 
-        setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
-
-        if (failed) {
-                png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
+        if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) {
+                png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
                 return NULL;
         }
         
-       if (ctype & PNG_COLOR_MASK_ALPHA)
-               bpp = 4;
-       else
-               bpp = 3;
-
-       pixels = malloc (w * h * bpp);
-       if (!pixels) {
-               png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
+        pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ctype & PNG_COLOR_MASK_ALPHA, 8, w, h);
+
+       if (!pixbuf) {
+                if (error && *error == NULL) {
+                        g_set_error (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                     _("Insufficient memory to load PNG file"));
+                }
+                
+
+               png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
                return NULL;
        }
 
        rows = g_new (png_bytep, h);
 
        for (i = 0; i < h; i++)
-               rows[i] = pixels + i * w * bpp;
+               rows[i] = pixbuf->pixels + i * pixbuf->rowstride;
 
        png_read_image (png_ptr, rows);
-       png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
+        png_read_end (png_ptr, info_ptr);
+
+        if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) {
+                for (i = 0; i < num_texts; i++) {
+                        png_text_to_pixbuf_option (text_ptr[i], &key, &value);
+                        gdk_pixbuf_set_option (pixbuf, key, value);
+                        g_free (key);
+                        g_free (value);
+                }
+        }
+
        g_free (rows);
+       png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
 
-       if (ctype & PNG_COLOR_MASK_ALPHA)
-               return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
-                                                w, h, w * 4,
-                                                free_buffer, NULL);
-       else
-               return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
-                                                w, h, w * 3,
-                                                free_buffer, NULL);
+        return pixbuf;
 }
 
 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
@@ -249,8 +358,8 @@ struct _LoadContext {
         png_structp png_read_ptr;
         png_infop   png_info_ptr;
 
-        ModulePreparedNotifyFunc prepare_func;
-        ModuleUpdatedNotifyFunc update_func;
+        GdkPixbufModulePreparedFunc prepare_func;
+        GdkPixbufModuleUpdatedFunc update_func;
         gpointer notify_user_data;
 
         GdkPixbuf* pixbuf;
@@ -273,14 +382,15 @@ struct _LoadContext {
         
         guint fatal_error_occurred : 1;
 
+        GError **error;
 };
 
-gpointer
-gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
-                                 ModuleUpdatedNotifyFunc update_func,
-                                 ModuleFrameDoneNotifyFunc frame_done_func,
-                                 ModuleAnimationDoneNotifyFunc anim_done_func,
-                                 gpointer user_data)
+static gpointer
+gdk_pixbuf__png_image_begin_load (GdkPixbufModuleSizeFunc size_func,
+                                  GdkPixbufModulePreparedFunc prepare_func,
+                                 GdkPixbufModuleUpdatedFunc update_func,
+                                 gpointer user_data,
+                                  GError **error)
 {
         LoadContext* lc;
         
@@ -297,24 +407,35 @@ gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
         lc->first_pass_seen_in_chunk = -1;
         lc->last_pass_seen_in_chunk = -1;
         lc->max_row_seen_in_chunk = -1;
+        lc->error = error;
         
         /* Create the main PNG context struct */
 
-               
+#ifdef PNG_USER_MEM_SUPPORTED
+        lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
+                                                     lc, /* error/warning callback data */
+                                                     png_error_callback,
+                                                     png_warning_callback,
+                                                     NULL,
+                                                     png_malloc_callback,
+                                                     png_free_callback);
+#else
         lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                   lc, /* error/warning callback data */
                                                   png_error_callback,
                                                   png_warning_callback);
-
+#endif
         if (lc->png_read_ptr == NULL) {
                 g_free(lc);
+                /* error callback should have set the error */
                 return NULL;
         }
-
+        
        if (setjmp (lc->png_read_ptr->jmpbuf)) {
                if (lc->png_info_ptr)
                        png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
                 g_free(lc);
+                /* error callback should have set the error */
                 return NULL;
        }
 
@@ -325,6 +446,7 @@ gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
         if (lc->png_info_ptr == NULL) {
                 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
                 g_free(lc);
+                /* error callback should have set the error */
                 return NULL;
         }
 
@@ -335,24 +457,38 @@ gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
                                     png_end_callback);
         
 
+        /* We don't want to keep modifying error after returning here,
+         * it may no longer be valid.
+         */
+        lc->error = NULL;
+        
         return lc;
 }
 
-void
-gdk_pixbuf__png_image_stop_load (gpointer context)
+static gboolean
+gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
 {
         LoadContext* lc = context;
 
-        g_return_if_fail(lc != NULL);
+        g_return_val_if_fail(lc != NULL, TRUE);
 
-        gdk_pixbuf_unref(lc->pixbuf);
+        /* FIXME this thing needs to report errors if
+         * we have unused image data
+         */
+        
+        if (lc->pixbuf)
+                g_object_unref (lc->pixbuf);
         
-        png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
+        png_destroy_read_struct(&lc->png_read_ptr, &lc->png_info_ptr, NULL);
         g_free(lc);
+
+        return TRUE;
 }
 
-gboolean
-gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size)
+static gboolean
+gdk_pixbuf__png_image_load_increment(gpointer context,
+                                     const guchar *buf, guint size,
+                                     GError **error)
 {
         LoadContext* lc = context;
 
@@ -364,17 +500,21 @@ gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size)
         lc->first_pass_seen_in_chunk = -1;
         lc->last_pass_seen_in_chunk = -1;
         lc->max_row_seen_in_chunk = -1;
+        lc->error = error;
         
         /* Invokes our callbacks as needed */
        if (setjmp (lc->png_read_ptr->jmpbuf)) {
+                lc->error = NULL;
                return FALSE;
        } else {
-               png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size);
+               png_process_data(lc->png_read_ptr, lc->png_info_ptr,
+                                 (guchar*) buf, size);
        }
 
-        if (lc->fatal_error_occurred)
+        if (lc->fatal_error_occurred) {
+                lc->error = NULL;
                 return FALSE;
-        else {
+        else {
                 if (lc->first_row_seen_in_chunk >= 0) {
                         /* We saw at least one row */
                         gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
@@ -417,6 +557,8 @@ gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size)
                                                  lc->notify_user_data);
                         }
                 }
+
+                lc->error = NULL;
                 
                 return TRUE;
         }
@@ -429,22 +571,20 @@ png_info_callback   (png_structp png_read_ptr,
 {
         LoadContext* lc;
         png_uint_32 width, height;
+        png_textp png_text_ptr;
+        int i, num_texts;
         int color_type;
         gboolean have_alpha = FALSE;
-        gboolean failed = FALSE;
         
         lc = png_get_progressive_ptr(png_read_ptr);
 
         if (lc->fatal_error_occurred)
                 return;
-        
 
-        setup_png_transformations(lc->png_read_ptr,
-                                  lc->png_info_ptr,
-                                  &failed,
-                                  &width, &height, &color_type);
-
-        if (failed) {
+        if (!setup_png_transformations(lc->png_read_ptr,
+                                       lc->png_info_ptr,
+                                       lc->error,
+                                       &width, &height, &color_type)) {
                 lc->fatal_error_occurred = TRUE;
                 return;
         }
@@ -458,13 +598,35 @@ png_info_callback   (png_structp png_read_ptr,
         if (lc->pixbuf == NULL) {
                 /* Failed to allocate memory */
                 lc->fatal_error_occurred = TRUE;
+                if (lc->error && *lc->error == NULL) {
+                        g_set_error (lc->error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                     _("Insufficient memory to store a %ld by %ld image; try exiting some applications to reduce memory usage"),
+                                     width, height);
+                }
                 return;
         }
+
+        /* Extract text chunks and attach them as pixbuf options */
         
+        if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) {
+                for (i = 0; i < num_texts; i++) {
+                        gchar *key, *value;
+
+                        if (png_text_to_pixbuf_option (png_text_ptr[i],
+                                                       &key, &value)) {
+                                gdk_pixbuf_set_option (lc->pixbuf, key, value);
+                                g_free (key);
+                                g_free (value);
+                        }
+                }
+        }
+
         /* Notify the client that we are ready to go */
 
         if (lc->prepare_func)
-                (* lc->prepare_func) (lc->pixbuf, lc->notify_user_data);
+                (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
         
         return;
 }
@@ -485,6 +647,17 @@ png_row_callback   (png_structp png_read_ptr,
         if (lc->fatal_error_occurred)
                 return;
 
+        if (row_num < 0 || 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"));
+                }
+                return;
+        }
+
         if (lc->first_row_seen_in_chunk < 0) {
                 lc->first_row_seen_in_chunk = row_num;
                 lc->first_pass_seen_in_chunk = pass_num;
@@ -521,8 +694,19 @@ png_error_callback(png_structp png_read_ptr,
         lc = png_get_error_ptr(png_read_ptr);
         
         lc->fatal_error_occurred = TRUE;
-        
-        fprintf(stderr, "Fatal error loading PNG: %s\n", error_msg);
+
+        /* I don't trust libpng to call the error callback only once,
+         * so check for already-set error
+         */
+        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: %s"),
+                             error_msg);
+        }
+
+        longjmp (png_read_ptr->jmpbuf, 1);
 }
 
 static void
@@ -532,49 +716,109 @@ png_warning_callback(png_structp png_read_ptr,
         LoadContext* lc;
         
         lc = png_get_error_ptr(png_read_ptr);
-        
-        fprintf(stderr, "Warning loading PNG: %s\n", warning_msg);
+
+        /* Don't print anything; we should not be dumping junk to
+         * stderr, since that may be bad for some apps. If it's
+         * important enough to display, we need to add a GError
+         * **warning return location wherever we have an error return
+         * location.
+         */
 }
 
 
 /* Save */
-gboolean
+
+static gboolean
 gdk_pixbuf__png_image_save (FILE          *f, 
                             GdkPixbuf     *pixbuf, 
                             gchar        **keys,
                             gchar        **values,
                             GError       **error)
 {
-        /* FIXME error handling is broken */
-        
        png_structp png_ptr;
        png_infop info_ptr;
+       png_textp text_ptr = NULL;
        guchar *ptr;
        guchar *pixels;
-       int x, y, j;
-       png_bytep row_ptr, data = NULL;
+       int y;
+       int i;
+       png_bytep row_ptr;
        png_color_8 sig_bit;
        int w, h, rowstride;
        int has_alpha;
        int bpc;
+       int num_keys;
+       gboolean success = TRUE;
+
+       num_keys = 0;
 
        if (keys && *keys) {
-               g_warning ("Bad option name '%s' passed to PNG saver",
-                          *keys);
-               return FALSE;
-#if 0
-               gchar **kiter = keys;
-               gchar **viter = values;
-
-               
-               while (*kiter) {
-                       
-                       ++kiter;
-                       ++viter;
+               gchar **kiter;
+               gchar  *key;
+               int     len;
+
+               for (kiter = keys; *kiter; kiter++) {
+                       if (strncmp (*kiter, "tEXt::", 6) != 0) {
+                               g_warning ("Bad option name '%s' passed to PNG saver", *kiter);
+                               return FALSE;
+                       }
+                       key = *kiter + 6;
+                       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;
+                       }
+                       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;
+                               }
+                       }
+                       num_keys++;
                }
+       }
+
+       if (num_keys > 0) {
+               text_ptr = g_new0 (png_text, num_keys);
+               for (i = 0; i < num_keys; i++) {
+                       text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
+                       text_ptr[i].key  = keys[i] + 6;
+                       text_ptr[i].text = g_convert (values[i], -1, 
+                                                     "ISO-8859-1", "UTF-8", 
+                                                     NULL, &text_ptr[i].text_length, 
+                                                     NULL);
+
+#ifdef PNG_iTXt_SUPPORTED 
+                       if (!text_ptr[i].text) {
+                               text_ptr[i].compression = PNG_ITXT_COMPRESSION_NONE;
+                               text_ptr[i].text = g_strdup (values[i]);
+                               text_ptr[i].text_length = 0;
+                               text_ptr[i].itxt_length = strlen (text_ptr[i].text);
+                               text_ptr[i].lang = NULL;
+                               text_ptr[i].lang_key = NULL;
+                       }
 #endif
+
+                       if (!text_ptr[i].text) {
+                               g_set_error (error,
+                                            GDK_PIXBUF_ERROR,
+                                            GDK_PIXBUF_ERROR_BAD_OPTION,
+                                            _("Value for PNG text chunk %s can not be converted to ISO-8859-1 encoding."), keys[i] + 6);
+                               num_keys = i;
+                               for (i = 0; i < num_keys; i++)
+                                       g_free (text_ptr[i].text);
+                               g_free (text_ptr);
+                               return FALSE;
+                       }
+               }
        }
-       
+
        bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
        w = gdk_pixbuf_get_width (pixbuf);
        h = gdk_pixbuf_get_height (pixbuf);
@@ -583,34 +827,36 @@ gdk_pixbuf__png_image_save (FILE          *f,
        pixels = gdk_pixbuf_get_pixels (pixbuf);
 
        png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
-                                          NULL, NULL, NULL);
+                                          error,
+                                          png_simple_error_callback,
+                                          png_simple_warning_callback);
 
        g_return_val_if_fail (png_ptr != NULL, FALSE);
 
        info_ptr = png_create_info_struct (png_ptr);
        if (info_ptr == NULL) {
-               png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
-               return FALSE;
+              success = FALSE;
+              goto cleanup;
        }
        if (setjmp (png_ptr->jmpbuf)) {
-               png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
-               return FALSE;
+              success = FALSE;
+              goto cleanup;
+       }
+
+       if (num_keys > 0) {
+               png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
        }
+
        png_init_io (png_ptr, f);
+
        if (has_alpha) {
                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
-                            PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
-                            PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
-#ifdef WORDS_BIGENDIAN
-               png_set_swap_alpha (png_ptr);
-#else
-               png_set_bgr (png_ptr);
-#endif
+                             PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        } else {
                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
-                            PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
-                            PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
-               data = malloc (w * 3 * sizeof (char));
+                             PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
        }
        sig_bit.red = bpc;
        sig_bit.green = bpc;
@@ -623,26 +869,55 @@ gdk_pixbuf__png_image_save (FILE          *f,
 
        ptr = pixels;
        for (y = 0; y < h; y++) {
-               if (has_alpha)
-                       row_ptr = (png_bytep)ptr;
-               else {
-                       for (j = 0, x = 0; x < w; x++)
-                               memcpy (&(data[x*3]), &(ptr[x*3]), 3);
-
-                       row_ptr = (png_bytep)data;
-               }
+               row_ptr = (png_bytep)ptr;
                png_write_rows (png_ptr, &row_ptr, 1);
                ptr += rowstride;
        }
 
-       if (data)
-               free (data);
-
        png_write_end (png_ptr, info_ptr);
-       png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
 
-       return TRUE;
-}
+cleanup:
+       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);
+       }
 
+       return success;
+}
 
+void
+MODULE_ENTRY (png, fill_vtable) (GdkPixbufModule *module)
+{
+        module->load = gdk_pixbuf__png_image_load;
+        module->begin_load = gdk_pixbuf__png_image_begin_load;
+        module->stop_load = gdk_pixbuf__png_image_stop_load;
+        module->load_increment = gdk_pixbuf__png_image_load_increment;
+        module->save = gdk_pixbuf__png_image_save;
+}
 
+void
+MODULE_ENTRY (png, fill_info) (GdkPixbufFormat *info)
+{
+        static GdkPixbufModulePattern signature[] = {
+                { "\x89PNG\r\n\x1a\x0a", NULL, 100 },
+                { NULL, NULL, 0 }
+        };
+       static gchar * mime_types[] = {
+               "image/png",
+               NULL
+       };
+       static gchar * extensions[] = {
+               "png",
+               NULL
+       };
+
+       info->name = "png";
+        info->signature = signature;
+       info->description = N_("The PNG image format");
+       info->mime_types = mime_types;
+       info->extensions = extensions;
+       info->flags = GDK_PIXBUF_FORMAT_WRITABLE;
+}