X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gdk-pixbuf%2Fio-png.c;h=5ddca7510836aa1b5dad842af4a0a8a24827d89e;hb=880f14ff0cf507e079ab735c7bcb32e2922b8de7;hp=84a2a0acb6ac6f1f9a07f9ca5211f35bb0a2e583;hpb=8f48c4b3cd6cf0643a7a1703de0f2c886a749228;p=~andy%2Fgtk diff --git a/gdk-pixbuf/io-png.c b/gdk-pixbuf/io-png.c index 84a2a0acb..5ddca7510 100644 --- a/gdk-pixbuf/io-png.c +++ b/gdk-pixbuf/io-png.c @@ -1,4 +1,5 @@ -/* GdkPixbuf library - JPEG image loader +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* GdkPixbuf library - PNG image loader * * Copyright (C) 1999 Mark Crichton * Copyright (C) 1999 The Free Software Foundation @@ -7,16 +8,16 @@ * Federico Mena-Quintero * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public + * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. + * Lesser General Public License for more details. * - * You should have received a copy of the GNU Library General Public + * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. @@ -24,15 +25,16 @@ #include #include +#include #include -#include "gdk-pixbuf.h" +#include "gdk-pixbuf-private.h" #include "gdk-pixbuf-io.h" -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) { @@ -42,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, @@ -114,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 libart pixbuf */ static void -free_buffer (gpointer user_data, gpointer data) +png_simple_error_callback(png_structp png_save_ptr, + png_const_charp error_msg) { - free (data); + 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 * -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; @@ -168,58 +272,65 @@ 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, ART_PIX_RGB, TRUE, - w, h, w * 4, - free_buffer, NULL); - else - return gdk_pixbuf_new_from_data (pixels, ART_PIX_RGB, FALSE, - w, h, w * 3, - free_buffer, NULL); + return pixbuf; } -/* These avoid the setjmp()/longjmp() crap in libpng */ +/* I wish these avoided the setjmp()/longjmp() crap in libpng instead + just allow you to change the error reporting. */ static void png_error_callback (png_structp png_read_ptr, png_const_charp error_msg); @@ -247,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; @@ -271,14 +382,15 @@ struct _LoadContext { guint fatal_error_occurred : 1; + GError **error; }; -gpointer -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; @@ -295,18 +407,37 @@ 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; + } /* Create the auxiliary context struct */ @@ -315,6 +446,7 @@ 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; } @@ -325,24 +457,38 @@ 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 -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 -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; @@ -354,13 +500,21 @@ 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 */ - png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size); + if (setjmp (lc->png_read_ptr->jmpbuf)) { + lc->error = NULL; + return FALSE; + } else { + 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; @@ -371,7 +525,7 @@ image_load_increment(gpointer context, guchar *buf, guint size) /* start and end row were in the same pass */ (lc->update_func)(lc->pixbuf, 0, lc->first_row_seen_in_chunk, - lc->pixbuf->art_pixbuf->width, + lc->pixbuf->width, (lc->last_row_seen_in_chunk - lc->first_row_seen_in_chunk) + 1, lc->notify_user_data); @@ -383,14 +537,14 @@ image_load_increment(gpointer context, guchar *buf, guint size) /* first row to end */ (lc->update_func)(lc->pixbuf, 0, lc->first_row_seen_in_chunk, - lc->pixbuf->art_pixbuf->width, + lc->pixbuf->width, (lc->max_row_seen_in_chunk - lc->first_row_seen_in_chunk) + 1, lc->notify_user_data); /* top to last row */ (lc->update_func)(lc->pixbuf, 0, 0, - lc->pixbuf->art_pixbuf->width, + lc->pixbuf->width, lc->last_row_seen_in_chunk + 1, lc->notify_user_data); } else { @@ -398,11 +552,13 @@ image_load_increment(gpointer context, guchar *buf, guint size) whole image */ (lc->update_func)(lc->pixbuf, 0, 0, - lc->pixbuf->art_pixbuf->width, + lc->pixbuf->width, lc->max_row_seen_in_chunk + 1, lc->notify_user_data); } } + + lc->error = NULL; return TRUE; } @@ -415,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; } @@ -439,18 +593,40 @@ png_info_callback (png_structp png_read_ptr, if (color_type & PNG_COLOR_MASK_ALPHA) have_alpha = TRUE; - lc->pixbuf = gdk_pixbuf_new(ART_PIX_RGB, have_alpha, 8, width, height); + lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height); 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; } @@ -471,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; @@ -480,7 +667,7 @@ png_row_callback (png_structp png_read_ptr, lc->last_row_seen_in_chunk = row_num; lc->last_pass_seen_in_chunk = pass_num; - old_row = lc->pixbuf->art_pixbuf->pixels + (row_num * lc->pixbuf->art_pixbuf->rowstride); + old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride); png_progressive_combine_row(lc->png_read_ptr, old_row, new_row); } @@ -507,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 @@ -518,7 +716,208 @@ 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 */ + +static gboolean +gdk_pixbuf__png_image_save (FILE *f, + GdkPixbuf *pixbuf, + gchar **keys, + gchar **values, + GError **error) +{ + png_structp png_ptr; + png_infop info_ptr; + png_textp text_ptr = NULL; + guchar *ptr; + guchar *pixels; + 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) { + 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); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + pixels = gdk_pixbuf_get_pixels (pixbuf); + + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, + 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) { + success = FALSE; + goto cleanup; + } + if (setjmp (png_ptr->jmpbuf)) { + 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); + } 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); + } + sig_bit.red = bpc; + sig_bit.green = bpc; + sig_bit.blue = bpc; + sig_bit.alpha = bpc; + png_set_sBIT (png_ptr, info_ptr, &sig_bit); + png_write_info (png_ptr, info_ptr); + png_set_shift (png_ptr, &sig_bit); + png_set_packing (png_ptr); + + ptr = pixels; + for (y = 0; y < h; y++) { + row_ptr = (png_bytep)ptr; + png_write_rows (png_ptr, &row_ptr, 1); + ptr += rowstride; + } + + png_write_end (png_ptr, info_ptr); + +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; +}