Blame gdk-pixbuf/io-png.c

Packit a4058c
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
Packit a4058c
/* GdkPixbuf library - PNG image loader
Packit a4058c
 *
Packit a4058c
 * Copyright (C) 1999 Mark Crichton
Packit a4058c
 * Copyright (C) 1999 The Free Software Foundation
Packit a4058c
 *
Packit a4058c
 * Authors: Mark Crichton <crichton@gimp.org>
Packit a4058c
 *          Federico Mena-Quintero <federico@gimp.org>
Packit a4058c
 *
Packit a4058c
 * This library is free software; you can redistribute it and/or
Packit a4058c
 * modify it under the terms of the GNU Lesser General Public
Packit a4058c
 * License as published by the Free Software Foundation; either
Packit a4058c
 * version 2 of the License, or (at your option) any later version.
Packit a4058c
 *
Packit a4058c
 * This library is distributed in the hope that it will be useful,
Packit a4058c
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit a4058c
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit a4058c
 * Lesser General Public License for more details.
Packit a4058c
 *
Packit a4058c
 * You should have received a copy of the GNU Lesser General Public
Packit a4058c
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit a4058c
 */
Packit a4058c
Packit a4058c
#include "config.h"
Packit a4058c
#include <stdio.h>
Packit a4058c
#include <stdlib.h>
Packit a4058c
#include <string.h>
Packit a4058c
#include <png.h>
Packit a4058c
#include <math.h>
Packit a4058c
#include "gdk-pixbuf-private.h"
Packit a4058c
#include "fallback-c89.c"
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
Packit a4058c
                          GError **error,
Packit a4058c
                          png_uint_32* width_p, png_uint_32* height_p,
Packit a4058c
                          int* color_type_p)
Packit a4058c
{
Packit a4058c
        png_uint_32 width, height;
Packit a4058c
        int bit_depth, color_type, interlace_type, compression_type, filter_type;
Packit a4058c
        int channels;
Packit a4058c
        
Packit a4058c
        /* Get the image info */
Packit a4058c
Packit a4058c
        /* Must check bit depth, since png_get_IHDR generates an 
Packit a4058c
           FPE on bit_depth 0.
Packit a4058c
        */
Packit a4058c
        bit_depth = png_get_bit_depth (png_read_ptr, png_info_ptr);
Packit a4058c
        if (bit_depth < 1 || bit_depth > 16) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Bits per channel of PNG image is invalid."));
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
        png_get_IHDR (png_read_ptr, png_info_ptr,
Packit a4058c
                      &width, &height,
Packit a4058c
                      &bit_depth,
Packit a4058c
                      &color_type,
Packit a4058c
                      &interlace_type,
Packit a4058c
                      &compression_type,
Packit a4058c
                      &filter_type);
Packit a4058c
Packit a4058c
        /* set_expand() basically needs to be called unless
Packit a4058c
           we are already in RGB/RGBA mode
Packit a4058c
        */
Packit a4058c
        if (color_type == PNG_COLOR_TYPE_PALETTE &&
Packit a4058c
            bit_depth <= 8) {
Packit a4058c
Packit a4058c
                /* Convert indexed images to RGB */
Packit a4058c
                png_set_expand (png_read_ptr);
Packit a4058c
Packit a4058c
        } else if (color_type == PNG_COLOR_TYPE_GRAY &&
Packit a4058c
                   bit_depth < 8) {
Packit a4058c
Packit a4058c
                /* Convert grayscale to RGB */
Packit a4058c
                png_set_expand (png_read_ptr);
Packit a4058c
Packit a4058c
        } else if (png_get_valid (png_read_ptr, 
Packit a4058c
                                  png_info_ptr, PNG_INFO_tRNS)) {
Packit a4058c
Packit a4058c
                /* If we have transparency header, convert it to alpha
Packit a4058c
                   channel */
Packit a4058c
                png_set_expand(png_read_ptr);
Packit a4058c
                
Packit a4058c
        } else if (bit_depth < 8) {
Packit a4058c
Packit a4058c
                /* If we have < 8 scale it up to 8 */
Packit a4058c
                png_set_expand(png_read_ptr);
Packit a4058c
Packit a4058c
Packit a4058c
                /* Conceivably, png_set_packing() is a better idea;
Packit a4058c
                 * God only knows how libpng works
Packit a4058c
                 */
Packit a4058c
        }
Packit a4058c
Packit a4058c
        /* If we are 16-bit, convert to 8-bit */
Packit a4058c
        if (bit_depth == 16) {
Packit a4058c
                png_set_strip_16(png_read_ptr);
Packit a4058c
        }
Packit a4058c
Packit a4058c
        /* If gray scale, convert to RGB */
Packit a4058c
        if (color_type == PNG_COLOR_TYPE_GRAY ||
Packit a4058c
            color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
Packit a4058c
                png_set_gray_to_rgb(png_read_ptr);
Packit a4058c
        }
Packit a4058c
        
Packit a4058c
        /* If interlaced, handle that */
Packit a4058c
        if (interlace_type != PNG_INTERLACE_NONE) {
Packit a4058c
                png_set_interlace_handling(png_read_ptr);
Packit a4058c
        }
Packit a4058c
        
Packit a4058c
        /* Update the info the reflect our transformations */
Packit a4058c
        png_read_update_info(png_read_ptr, png_info_ptr);
Packit a4058c
        
Packit a4058c
        png_get_IHDR (png_read_ptr, png_info_ptr,
Packit a4058c
                      &width, &height,
Packit a4058c
                      &bit_depth,
Packit a4058c
                      &color_type,
Packit a4058c
                      &interlace_type,
Packit a4058c
                      &compression_type,
Packit a4058c
                      &filter_type);
Packit a4058c
Packit a4058c
        *width_p = width;
Packit a4058c
        *height_p = height;
Packit a4058c
        *color_type_p = color_type;
Packit a4058c
        
Packit a4058c
        /* Check that the new info is what we want */
Packit a4058c
        
Packit a4058c
        if (width == 0 || height == 0) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Transformed PNG has zero width or height."));
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        if (bit_depth != 8) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Bits per channel of transformed PNG is not 8."));
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
Packit a4058c
                color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Transformed PNG not RGB or RGBA."));
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        channels = png_get_channels(png_read_ptr, png_info_ptr);
Packit a4058c
        if ( ! (channels == 3 || channels == 4) ) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
        return TRUE;
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_simple_error_callback(png_structp png_save_ptr,
Packit a4058c
                          png_const_charp error_msg)
Packit a4058c
{
Packit a4058c
        GError **error;
Packit a4058c
        
Packit a4058c
        error = png_get_error_ptr(png_save_ptr);
Packit a4058c
Packit a4058c
        /* I don't trust libpng to call the error callback only once,
Packit a4058c
         * so check for already-set error
Packit a4058c
         */
Packit a4058c
        if (error && *error == NULL) {
Packit a4058c
                g_set_error (error,
Packit a4058c
                             GDK_PIXBUF_ERROR,
Packit a4058c
                             GDK_PIXBUF_ERROR_FAILED,
Packit a4058c
                             _("Fatal error in PNG image file: %s"),
Packit a4058c
                             error_msg);
Packit a4058c
        }
Packit a4058c
Packit a4058c
        longjmp (png_jmpbuf(png_save_ptr), 1);
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_simple_warning_callback(png_structp png_save_ptr,
Packit a4058c
                            png_const_charp warning_msg)
Packit a4058c
{
Packit a4058c
        /* Don't print anything; we should not be dumping junk to
Packit a4058c
         * stderr, since that may be bad for some apps. If it's
Packit a4058c
         * important enough to display, we need to add a GError
Packit a4058c
         * **warning return location wherever we have an error return
Packit a4058c
         * location.
Packit a4058c
         */
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
png_text_to_pixbuf_option (png_text   text_ptr,
Packit a4058c
                           gchar    **key,
Packit a4058c
                           gchar    **value)
Packit a4058c
{
Packit a4058c
        gboolean is_ascii = TRUE;
Packit a4058c
        int i;
Packit a4058c
Packit a4058c
        /* Avoid loading iconv if the text is plain ASCII */
Packit a4058c
        for (i = 0; i < text_ptr.text_length; i++)
Packit a4058c
                if (text_ptr.text[i] & 0x80) {
Packit a4058c
                        is_ascii = FALSE;
Packit a4058c
                        break;
Packit a4058c
                }
Packit a4058c
Packit a4058c
        if (is_ascii) {
Packit a4058c
                *value = g_strdup (text_ptr.text);
Packit a4058c
        } else {
Packit a4058c
                *value = g_convert (text_ptr.text, -1,
Packit a4058c
                                     "UTF-8", "ISO-8859-1",
Packit a4058c
                                     NULL, NULL, NULL);
Packit a4058c
        }
Packit a4058c
Packit a4058c
        if (*value) {
Packit a4058c
                *key = g_strconcat ("tEXt::", text_ptr.key, NULL);
Packit a4058c
                return TRUE;
Packit a4058c
        } else {
Packit a4058c
                g_warning ("Couldn't convert text chunk value to UTF-8.");
Packit a4058c
                *key = NULL;
Packit a4058c
                return FALSE;
Packit a4058c
        }
Packit a4058c
}
Packit a4058c
Packit a4058c
static png_voidp
Packit a4058c
png_malloc_callback (png_structp o, png_size_t size)
Packit a4058c
{
Packit a4058c
        return g_try_malloc (size);
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_free_callback (png_structp o, png_voidp x)
Packit a4058c
{
Packit a4058c
        g_free (x);
Packit a4058c
}
Packit a4058c
Packit a4058c
/* Shared library entry point */
Packit a4058c
static GdkPixbuf *
Packit a4058c
gdk_pixbuf__png_image_load (FILE *f, GError **error)
Packit a4058c
{
Packit a4058c
        GdkPixbuf * volatile pixbuf = NULL;
Packit a4058c
	png_structp png_ptr;
Packit a4058c
	png_infop info_ptr;
Packit a4058c
        png_textp text_ptr;
Packit a4058c
	gint i, ctype;
Packit a4058c
	png_uint_32 w, h;
Packit a4058c
	png_bytepp volatile rows = NULL;
Packit a4058c
        gint    num_texts;
Packit a4058c
        gchar *key;
Packit a4058c
        gchar *value;
Packit a4058c
        gchar *icc_profile_base64;
Packit a4058c
        const gchar *icc_profile_title;
Packit a4058c
        const gchar *icc_profile;
Packit a4058c
        png_uint_32 icc_profile_size;
Packit a4058c
        png_uint_32 x_resolution;
Packit a4058c
        png_uint_32 y_resolution;
Packit a4058c
        int unit_type;
Packit a4058c
        gchar *density_str;
Packit a4058c
        guint32 retval;
Packit a4058c
        gint compression_type;
Packit a4058c
        gpointer ptr;
Packit a4058c
Packit a4058c
#ifdef PNG_USER_MEM_SUPPORTED
Packit a4058c
	png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
Packit a4058c
                                            error,
Packit a4058c
                                            png_simple_error_callback,
Packit a4058c
                                            png_simple_warning_callback,
Packit a4058c
                                            NULL, 
Packit a4058c
                                            png_malloc_callback, 
Packit a4058c
                                            png_free_callback);
Packit a4058c
#else
Packit a4058c
	png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
Packit a4058c
                                          error,
Packit a4058c
                                          png_simple_error_callback,
Packit a4058c
                                          png_simple_warning_callback);
Packit a4058c
#endif
Packit a4058c
	if (!png_ptr)
Packit a4058c
		return NULL;
Packit a4058c
Packit a4058c
	info_ptr = png_create_info_struct (png_ptr);
Packit a4058c
	if (!info_ptr) {
Packit a4058c
		png_destroy_read_struct (&png_ptr, NULL, NULL);
Packit a4058c
		return NULL;
Packit a4058c
	}
Packit a4058c
Packit a4058c
	if (setjmp (png_jmpbuf(png_ptr))) {
Packit a4058c
	    	g_free (rows);
Packit a4058c
Packit a4058c
		if (pixbuf)
Packit a4058c
			g_object_unref (pixbuf);
Packit a4058c
Packit a4058c
		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
Packit a4058c
		return NULL;
Packit a4058c
	}
Packit a4058c
Packit a4058c
	png_init_io (png_ptr, f);
Packit a4058c
	png_read_info (png_ptr, info_ptr);
Packit a4058c
Packit a4058c
        if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) {
Packit a4058c
                png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
Packit a4058c
                return NULL;
Packit a4058c
        }
Packit a4058c
        
Packit a4058c
        pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ctype & PNG_COLOR_MASK_ALPHA, 8, w, h);
Packit a4058c
Packit a4058c
	if (!pixbuf) {
Packit a4058c
                g_set_error_literal (error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
Packit a4058c
                                     _("Insufficient memory to load PNG file"));
Packit a4058c
Packit a4058c
		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
Packit a4058c
		return NULL;
Packit a4058c
	}
Packit a4058c
Packit a4058c
        gdk_pixbuf_fill (pixbuf, DEFAULT_FILL_COLOR);
Packit a4058c
Packit a4058c
	rows = g_new (png_bytep, h);
Packit a4058c
Packit a4058c
        for (i = 0, ptr = pixbuf->pixels; i < h; i++, ptr = (guchar *) ptr + pixbuf->rowstride)
Packit a4058c
		rows[i] = ptr;
Packit a4058c
Packit a4058c
	png_read_image (png_ptr, rows);
Packit a4058c
        png_read_end (png_ptr, info_ptr);
Packit a4058c
Packit a4058c
        if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) {
Packit a4058c
                for (i = 0; i < num_texts; i++) {
Packit a4058c
                        png_text_to_pixbuf_option (text_ptr[i], &key, &value);
Packit a4058c
                        gdk_pixbuf_set_option (pixbuf, key, value);
Packit a4058c
                        g_free (key);
Packit a4058c
                        g_free (value);
Packit a4058c
                }
Packit a4058c
        }
Packit a4058c
Packit a4058c
#if defined(PNG_cHRM_SUPPORTED)
Packit a4058c
        /* Extract embedded ICC profile */
Packit a4058c
        retval = png_get_iCCP (png_ptr, info_ptr,
Packit a4058c
                               (png_charpp) &icc_profile_title, &compression_type,
Packit a4058c
                               (png_bytepp) &icc_profile, (png_uint_32*) &icc_profile_size);
Packit a4058c
        if (retval != 0) {
Packit a4058c
                icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size);
Packit a4058c
                gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
Packit a4058c
                g_free (icc_profile_base64);
Packit a4058c
        }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
#ifdef PNG_pHYs_SUPPORTED
Packit a4058c
        retval = png_get_pHYs (png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
Packit a4058c
        if (retval != 0 && unit_type == PNG_RESOLUTION_METER) {
Packit a4058c
                density_str = g_strdup_printf ("%d", DPM_TO_DPI (x_resolution));
Packit a4058c
                gdk_pixbuf_set_option (pixbuf, "x-dpi", density_str);
Packit a4058c
                g_free (density_str);
Packit a4058c
                density_str = g_strdup_printf ("%d", DPM_TO_DPI (y_resolution));
Packit a4058c
                gdk_pixbuf_set_option (pixbuf, "y-dpi", density_str);
Packit a4058c
                g_free (density_str);
Packit a4058c
        }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
	g_free (rows);
Packit a4058c
	png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
Packit a4058c
Packit a4058c
        return pixbuf;
Packit a4058c
}
Packit a4058c
Packit a4058c
/* I wish these avoided the setjmp()/longjmp() crap in libpng instead
Packit a4058c
   just allow you to change the error reporting. */
Packit a4058c
static void png_error_callback  (png_structp png_read_ptr,
Packit a4058c
                                 png_const_charp error_msg);
Packit a4058c
Packit a4058c
static void png_warning_callback (png_structp png_read_ptr,
Packit a4058c
                                  png_const_charp warning_msg);
Packit a4058c
Packit a4058c
/* Called at the start of the progressive load */
Packit a4058c
static void png_info_callback   (png_structp png_read_ptr,
Packit a4058c
                                 png_infop   png_info_ptr);
Packit a4058c
Packit a4058c
/* Called for each row; note that you will get duplicate row numbers
Packit a4058c
   for interlaced PNGs */
Packit a4058c
static void png_row_callback   (png_structp png_read_ptr,
Packit a4058c
                                png_bytep   new_row,
Packit a4058c
                                png_uint_32 row_num,
Packit a4058c
                                int pass_num);
Packit a4058c
Packit a4058c
/* Called after reading the entire image */
Packit a4058c
static void png_end_callback   (png_structp png_read_ptr,
Packit a4058c
                                png_infop   png_info_ptr);
Packit a4058c
Packit a4058c
typedef struct _LoadContext LoadContext;
Packit a4058c
Packit a4058c
struct _LoadContext {
Packit a4058c
        png_structp png_read_ptr;
Packit a4058c
        png_infop   png_info_ptr;
Packit a4058c
Packit a4058c
        GdkPixbufModuleSizeFunc size_func;
Packit a4058c
        GdkPixbufModulePreparedFunc prepare_func;
Packit a4058c
        GdkPixbufModuleUpdatedFunc update_func;
Packit a4058c
        gpointer notify_user_data;
Packit a4058c
Packit a4058c
        GdkPixbuf* pixbuf;
Packit a4058c
Packit a4058c
        /* row number of first row seen, or -1 if none yet seen */
Packit a4058c
Packit a4058c
        gint first_row_seen_in_chunk;
Packit a4058c
Packit a4058c
        /* pass number for the first row seen */
Packit a4058c
Packit a4058c
        gint first_pass_seen_in_chunk;
Packit a4058c
        
Packit a4058c
        /* row number of last row seen */
Packit a4058c
        gint last_row_seen_in_chunk;
Packit a4058c
Packit a4058c
        gint last_pass_seen_in_chunk;
Packit a4058c
Packit a4058c
        /* highest row number seen */
Packit a4058c
        gint max_row_seen_in_chunk;
Packit a4058c
        
Packit a4058c
        guint fatal_error_occurred : 1;
Packit a4058c
Packit a4058c
        GError **error;
Packit a4058c
};
Packit a4058c
Packit a4058c
static gpointer
Packit a4058c
gdk_pixbuf__png_image_begin_load (GdkPixbufModuleSizeFunc size_func,
Packit a4058c
                                  GdkPixbufModulePreparedFunc prepare_func,
Packit a4058c
				  GdkPixbufModuleUpdatedFunc update_func,
Packit a4058c
				  gpointer user_data,
Packit a4058c
                                  GError **error)
Packit a4058c
{
Packit a4058c
        LoadContext* lc;
Packit a4058c
        
Packit a4058c
        lc = g_new0(LoadContext, 1);
Packit a4058c
        
Packit a4058c
        lc->fatal_error_occurred = FALSE;
Packit a4058c
Packit a4058c
        lc->size_func = size_func;
Packit a4058c
        lc->prepare_func = prepare_func;
Packit a4058c
        lc->update_func = update_func;
Packit a4058c
        lc->notify_user_data = user_data;
Packit a4058c
Packit a4058c
        lc->first_row_seen_in_chunk = -1;
Packit a4058c
        lc->last_row_seen_in_chunk = -1;
Packit a4058c
        lc->first_pass_seen_in_chunk = -1;
Packit a4058c
        lc->last_pass_seen_in_chunk = -1;
Packit a4058c
        lc->max_row_seen_in_chunk = -1;
Packit a4058c
        lc->error = error;
Packit a4058c
        
Packit a4058c
        /* Create the main PNG context struct */
Packit a4058c
Packit a4058c
#ifdef PNG_USER_MEM_SUPPORTED
Packit a4058c
        lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
Packit a4058c
                                                     lc, /* error/warning callback data */
Packit a4058c
                                                     png_error_callback,
Packit a4058c
                                                     png_warning_callback,
Packit a4058c
                                                     NULL,
Packit a4058c
                                                     png_malloc_callback,
Packit a4058c
                                                     png_free_callback);
Packit a4058c
#else
Packit a4058c
        lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
Packit a4058c
                                                  lc, /* error/warning callback data */
Packit a4058c
                                                  png_error_callback,
Packit a4058c
                                                  png_warning_callback);
Packit a4058c
#endif
Packit a4058c
        if (lc->png_read_ptr == NULL) {
Packit a4058c
                g_free(lc);
Packit a4058c
                /* error callback should have set the error */
Packit a4058c
                return NULL;
Packit a4058c
        }
Packit a4058c
        
Packit a4058c
	if (setjmp (png_jmpbuf(lc->png_read_ptr))) {
Packit a4058c
		if (lc->png_info_ptr)
Packit a4058c
			png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
Packit a4058c
                g_free(lc);
Packit a4058c
                /* error callback should have set the error */
Packit a4058c
                return NULL;
Packit a4058c
	}
Packit a4058c
Packit a4058c
        /* Create the auxiliary context struct */
Packit a4058c
Packit a4058c
        lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
Packit a4058c
Packit a4058c
        if (lc->png_info_ptr == NULL) {
Packit a4058c
                png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
Packit a4058c
                g_free(lc);
Packit a4058c
                /* error callback should have set the error */
Packit a4058c
                return NULL;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        png_set_progressive_read_fn(lc->png_read_ptr,
Packit a4058c
                                    lc, /* callback data */
Packit a4058c
                                    png_info_callback,
Packit a4058c
                                    png_row_callback,
Packit a4058c
                                    png_end_callback);
Packit a4058c
        
Packit a4058c
Packit a4058c
        /* We don't want to keep modifying error after returning here,
Packit a4058c
         * it may no longer be valid.
Packit a4058c
         */
Packit a4058c
        lc->error = NULL;
Packit a4058c
        
Packit a4058c
        return lc;
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
Packit a4058c
{
Packit a4058c
        LoadContext* lc = context;
Packit a4058c
        gboolean retval = TRUE;
Packit a4058c
Packit a4058c
        g_return_val_if_fail(lc != NULL, TRUE);
Packit a4058c
Packit a4058c
        /* FIXME this thing needs to report errors if
Packit a4058c
         * we have unused image data
Packit a4058c
         */
Packit a4058c
        
Packit a4058c
        if (lc->pixbuf)
Packit a4058c
                g_object_unref (lc->pixbuf);
Packit a4058c
        else {
Packit a4058c
                g_set_error_literal (error, GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Premature end-of-file encountered"));
Packit a4058c
                retval = FALSE;
Packit a4058c
	}
Packit a4058c
        
Packit a4058c
        png_destroy_read_struct(&lc->png_read_ptr, &lc->png_info_ptr, NULL);
Packit a4058c
        g_free(lc);
Packit a4058c
Packit a4058c
        return retval;
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
gdk_pixbuf__png_image_load_increment(gpointer context,
Packit a4058c
                                     const guchar *buf, guint size,
Packit a4058c
                                     GError **error)
Packit a4058c
{
Packit a4058c
        LoadContext* lc = context;
Packit a4058c
Packit a4058c
        g_return_val_if_fail(lc != NULL, FALSE);
Packit a4058c
Packit a4058c
        /* reset */
Packit a4058c
        lc->first_row_seen_in_chunk = -1;
Packit a4058c
        lc->last_row_seen_in_chunk = -1;
Packit a4058c
        lc->first_pass_seen_in_chunk = -1;
Packit a4058c
        lc->last_pass_seen_in_chunk = -1;
Packit a4058c
        lc->max_row_seen_in_chunk = -1;
Packit a4058c
        lc->error = error;
Packit a4058c
        
Packit a4058c
        /* Invokes our callbacks as needed */
Packit a4058c
	if (setjmp (png_jmpbuf(lc->png_read_ptr))) {
Packit a4058c
                lc->error = NULL;
Packit a4058c
		return FALSE;
Packit a4058c
	} else {
Packit a4058c
		png_process_data(lc->png_read_ptr, lc->png_info_ptr,
Packit a4058c
                                 (guchar*) buf, size);
Packit a4058c
	}
Packit a4058c
Packit a4058c
        if (lc->fatal_error_occurred) {
Packit a4058c
                lc->error = NULL;
Packit a4058c
                return FALSE;
Packit a4058c
        } else {
Packit a4058c
                if (lc->first_row_seen_in_chunk >= 0 && lc->update_func) {
Packit a4058c
                        /* We saw at least one row */
Packit a4058c
                        gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
Packit a4058c
                        
Packit a4058c
                        g_assert(pass_diff >= 0);
Packit a4058c
                        
Packit a4058c
                        if (pass_diff == 0) {
Packit a4058c
                                /* start and end row were in the same pass */
Packit a4058c
                                (lc->update_func)(lc->pixbuf, 0,
Packit a4058c
                                                  lc->first_row_seen_in_chunk,
Packit a4058c
                                                  lc->pixbuf->width,
Packit a4058c
                                                  (lc->last_row_seen_in_chunk -
Packit a4058c
                                                   lc->first_row_seen_in_chunk) + 1,
Packit a4058c
						  lc->notify_user_data);
Packit a4058c
                        } else if (pass_diff == 1) {
Packit a4058c
                                /* We have from the first row seen to
Packit a4058c
                                   the end of the image (max row
Packit a4058c
                                   seen), then from the top of the
Packit a4058c
                                   image to the last row seen */
Packit a4058c
                                /* first row to end */
Packit a4058c
                                (lc->update_func)(lc->pixbuf, 0,
Packit a4058c
                                                  lc->first_row_seen_in_chunk,
Packit a4058c
                                                  lc->pixbuf->width,
Packit a4058c
                                                  (lc->max_row_seen_in_chunk -
Packit a4058c
                                                   lc->first_row_seen_in_chunk) + 1,
Packit a4058c
						  lc->notify_user_data);
Packit a4058c
                                /* top to last row */
Packit a4058c
                                (lc->update_func)(lc->pixbuf,
Packit a4058c
                                                  0, 0, 
Packit a4058c
                                                  lc->pixbuf->width,
Packit a4058c
                                                  lc->last_row_seen_in_chunk + 1,
Packit a4058c
						  lc->notify_user_data);
Packit a4058c
                        } else {
Packit a4058c
                                /* We made at least one entire pass, so update the
Packit a4058c
                                   whole image */
Packit a4058c
                                (lc->update_func)(lc->pixbuf,
Packit a4058c
                                                  0, 0, 
Packit a4058c
                                                  lc->pixbuf->width,
Packit a4058c
                                                  lc->max_row_seen_in_chunk + 1,
Packit a4058c
						  lc->notify_user_data);
Packit a4058c
                        }
Packit a4058c
                }
Packit a4058c
Packit a4058c
                lc->error = NULL;
Packit a4058c
                
Packit a4058c
                return TRUE;
Packit a4058c
        }
Packit a4058c
}
Packit a4058c
Packit a4058c
/* Called at the start of the progressive load, once we have image info */
Packit a4058c
static void
Packit a4058c
png_info_callback   (png_structp png_read_ptr,
Packit a4058c
                     png_infop   png_info_ptr)
Packit a4058c
{
Packit a4058c
        LoadContext* lc;
Packit a4058c
        png_uint_32 width, height;
Packit a4058c
        png_textp png_text_ptr;
Packit a4058c
        int i, num_texts;
Packit a4058c
        int color_type;
Packit a4058c
        gboolean have_alpha = FALSE;
Packit a4058c
        gchar *icc_profile_base64;
Packit a4058c
        const gchar *icc_profile_title;
Packit a4058c
        const gchar *icc_profile;
Packit a4058c
        png_uint_32 icc_profile_size;
Packit a4058c
        png_uint_32 x_resolution;
Packit a4058c
        png_uint_32 y_resolution;
Packit a4058c
        int unit_type;
Packit a4058c
        gchar *density_str;
Packit a4058c
        guint32 retval;
Packit a4058c
        gint compression_type;
Packit a4058c
Packit a4058c
        lc = png_get_progressive_ptr(png_read_ptr);
Packit a4058c
Packit a4058c
        if (lc->fatal_error_occurred)
Packit a4058c
                return;
Packit a4058c
Packit a4058c
        if (!setup_png_transformations(lc->png_read_ptr,
Packit a4058c
                                       lc->png_info_ptr,
Packit a4058c
                                       lc->error,
Packit a4058c
                                       &width, &height, &color_type)) {
Packit a4058c
                lc->fatal_error_occurred = TRUE;
Packit a4058c
                return;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        /* If we have alpha, set a flag */
Packit a4058c
        if (color_type & PNG_COLOR_MASK_ALPHA)
Packit a4058c
                have_alpha = TRUE;
Packit a4058c
        
Packit a4058c
        if (lc->size_func) {
Packit a4058c
                gint w = width;
Packit a4058c
                gint h = height;
Packit a4058c
                (* lc->size_func) (&w, &h, lc->notify_user_data);
Packit a4058c
                
Packit a4058c
                if (w == 0 || h == 0) {
Packit a4058c
                        lc->fatal_error_occurred = TRUE;
Packit a4058c
                        g_set_error_literal (lc->error,
Packit a4058c
                                             GDK_PIXBUF_ERROR,
Packit a4058c
                                             GDK_PIXBUF_ERROR_FAILED,
Packit a4058c
                                             _("Transformed PNG has zero width or height."));
Packit a4058c
                        return;
Packit a4058c
                }
Packit a4058c
        }
Packit a4058c
Packit a4058c
        lc->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
Packit a4058c
Packit a4058c
        if (lc->pixbuf == NULL) {
Packit a4058c
                /* Failed to allocate memory */
Packit a4058c
                lc->fatal_error_occurred = TRUE;
Packit a4058c
                g_set_error (lc->error,
Packit a4058c
                             GDK_PIXBUF_ERROR,
Packit a4058c
                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
Packit a4058c
                             _("Insufficient memory to store a %lu by %lu image; try exiting some applications to reduce memory usage"),
Packit a4058c
                             (gulong) width, (gulong) height);
Packit a4058c
                return;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        gdk_pixbuf_fill (lc->pixbuf, DEFAULT_FILL_COLOR);
Packit a4058c
Packit a4058c
        /* Extract text chunks and attach them as pixbuf options */
Packit a4058c
        
Packit a4058c
        if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) {
Packit a4058c
                for (i = 0; i < num_texts; i++) {
Packit a4058c
                        gchar *key, *value;
Packit a4058c
Packit a4058c
                        if (png_text_to_pixbuf_option (png_text_ptr[i],
Packit a4058c
                                                       &key, &value)) {
Packit a4058c
                                gdk_pixbuf_set_option (lc->pixbuf, key, value);
Packit a4058c
                                g_free (key);
Packit a4058c
                                g_free (value);
Packit a4058c
                        }
Packit a4058c
                }
Packit a4058c
        }
Packit a4058c
Packit a4058c
#if defined(PNG_cHRM_SUPPORTED)
Packit a4058c
        /* Extract embedded ICC profile */
Packit a4058c
        retval = png_get_iCCP (png_read_ptr, png_info_ptr,
Packit a4058c
                               (png_charpp) &icc_profile_title, &compression_type,
Packit a4058c
                               (png_bytepp) &icc_profile, &icc_profile_size);
Packit a4058c
        if (retval != 0) {
Packit a4058c
                icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size);
Packit a4058c
                gdk_pixbuf_set_option (lc->pixbuf, "icc-profile", icc_profile_base64);
Packit a4058c
                g_free (icc_profile_base64);
Packit a4058c
        }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
#ifdef PNG_pHYs_SUPPORTED
Packit a4058c
        retval = png_get_pHYs (png_read_ptr, png_info_ptr, &x_resolution, &y_resolution, &unit_type);
Packit a4058c
        if (retval != 0 && unit_type == PNG_RESOLUTION_METER) {
Packit a4058c
                density_str = g_strdup_printf ("%d", DPM_TO_DPI (x_resolution));
Packit a4058c
                gdk_pixbuf_set_option (lc->pixbuf, "x-dpi", density_str);
Packit a4058c
                g_free (density_str);
Packit a4058c
                density_str = g_strdup_printf ("%d", DPM_TO_DPI (y_resolution));
Packit a4058c
                gdk_pixbuf_set_option (lc->pixbuf, "y-dpi", density_str);
Packit a4058c
                g_free (density_str);
Packit a4058c
        }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
        /* Notify the client that we are ready to go */
Packit a4058c
Packit a4058c
        if (lc->prepare_func)
Packit a4058c
                (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
Packit a4058c
Packit a4058c
        return;
Packit a4058c
}
Packit a4058c
Packit a4058c
/* Called for each row; note that you will get duplicate row numbers
Packit a4058c
   for interlaced PNGs */
Packit a4058c
static void
Packit a4058c
png_row_callback   (png_structp png_read_ptr,
Packit a4058c
                    png_bytep   new_row,
Packit a4058c
                    png_uint_32 row_num,
Packit a4058c
                    int pass_num)
Packit a4058c
{
Packit a4058c
        LoadContext* lc;
Packit a4058c
        guchar* old_row = NULL;
Packit a4058c
        gsize rowstride;
Packit a4058c
Packit a4058c
        lc = png_get_progressive_ptr(png_read_ptr);
Packit a4058c
Packit a4058c
        if (lc->fatal_error_occurred)
Packit a4058c
                return;
Packit a4058c
Packit a4058c
        if (row_num >= lc->pixbuf->height) {
Packit a4058c
                lc->fatal_error_occurred = TRUE;
Packit a4058c
                g_set_error_literal (lc->error,
Packit a4058c
                                     GDK_PIXBUF_ERROR,
Packit a4058c
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                                     _("Fatal error reading PNG image file"));
Packit a4058c
                return;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        if (lc->first_row_seen_in_chunk < 0) {
Packit a4058c
                lc->first_row_seen_in_chunk = row_num;
Packit a4058c
                lc->first_pass_seen_in_chunk = pass_num;
Packit a4058c
        }
Packit a4058c
Packit a4058c
        lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
Packit a4058c
        lc->last_row_seen_in_chunk = row_num;
Packit a4058c
        lc->last_pass_seen_in_chunk = pass_num;
Packit a4058c
Packit a4058c
        rowstride = lc->pixbuf->rowstride;
Packit a4058c
        old_row = lc->pixbuf->pixels + (row_num * rowstride);
Packit a4058c
Packit a4058c
        png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
Packit a4058c
}
Packit a4058c
Packit a4058c
/* Called after reading the entire image */
Packit a4058c
static void
Packit a4058c
png_end_callback   (png_structp png_read_ptr,
Packit a4058c
                    png_infop   png_info_ptr)
Packit a4058c
{
Packit a4058c
        LoadContext* lc;
Packit a4058c
Packit a4058c
        lc = png_get_progressive_ptr(png_read_ptr);
Packit a4058c
Packit a4058c
        if (lc->fatal_error_occurred)
Packit a4058c
                return;
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_error_callback(png_structp png_read_ptr,
Packit a4058c
                   png_const_charp error_msg)
Packit a4058c
{
Packit a4058c
        LoadContext* lc;
Packit a4058c
        
Packit a4058c
        lc = png_get_error_ptr(png_read_ptr);
Packit a4058c
        
Packit a4058c
        lc->fatal_error_occurred = TRUE;
Packit a4058c
Packit a4058c
        /* I don't trust libpng to call the error callback only once,
Packit a4058c
         * so check for already-set error
Packit a4058c
         */
Packit a4058c
        if (lc->error && *lc->error == NULL) {
Packit a4058c
                g_set_error (lc->error,
Packit a4058c
                             GDK_PIXBUF_ERROR,
Packit a4058c
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
Packit a4058c
                             _("Fatal error reading PNG image file: %s"),
Packit a4058c
                             error_msg);
Packit a4058c
        }
Packit a4058c
Packit a4058c
        longjmp (png_jmpbuf(png_read_ptr), 1);
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_warning_callback (png_structp png_read_ptr,
Packit a4058c
                      png_const_charp warning_msg)
Packit a4058c
{
Packit a4058c
        /* Don't print anything; we should not be dumping junk to
Packit a4058c
         * stderr, since that may be bad for some apps. If it's
Packit a4058c
         * important enough to display, we need to add a GError
Packit a4058c
         * **warning return location wherever we have an error return
Packit a4058c
         * location.
Packit a4058c
         */
Packit a4058c
}
Packit a4058c
Packit a4058c
Packit a4058c
/* Save */
Packit a4058c
Packit a4058c
typedef struct {
Packit a4058c
        GdkPixbufSaveFunc save_func;
Packit a4058c
        gpointer user_data;
Packit a4058c
        GError **error;
Packit a4058c
} SaveToFunctionIoPtr;
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_save_to_callback_write_func (png_structp png_ptr,
Packit a4058c
                                 png_bytep   data,
Packit a4058c
                                 png_size_t  length)
Packit a4058c
{
Packit a4058c
        SaveToFunctionIoPtr *ioptr = png_get_io_ptr (png_ptr);
Packit a4058c
Packit a4058c
        if (!ioptr->save_func ((gchar *)data, length, ioptr->error, ioptr->user_data)) {
Packit a4058c
                /* If save_func has already set an error, which it
Packit a4058c
                   should have done, this won't overwrite it. */
Packit a4058c
                png_error (png_ptr, "write function failed");
Packit a4058c
        }
Packit a4058c
}
Packit a4058c
Packit a4058c
static void
Packit a4058c
png_save_to_callback_flush_func (png_structp png_ptr)
Packit a4058c
{
Packit a4058c
        ;
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean real_save_png (GdkPixbuf        *pixbuf, 
Packit a4058c
                               gchar           **keys,
Packit a4058c
                               gchar           **values,
Packit a4058c
                               GError          **error,
Packit a4058c
                               gboolean          to_callback,
Packit a4058c
                               FILE             *f,
Packit a4058c
                               GdkPixbufSaveFunc save_func,
Packit a4058c
                               gpointer          user_data)
Packit a4058c
{
Packit a4058c
       png_structp png_ptr = NULL;
Packit a4058c
       png_infop info_ptr;
Packit a4058c
       png_textp text_ptr = NULL;
Packit a4058c
       guchar *ptr;
Packit a4058c
       guchar *pixels;
Packit a4058c
       int y;
Packit a4058c
       int i;
Packit a4058c
       png_bytep row_ptr;
Packit a4058c
       png_color_8 sig_bit;
Packit a4058c
       int w, h, rowstride;
Packit a4058c
       int has_alpha;
Packit a4058c
       int bpc;
Packit a4058c
       int num_keys;
Packit a4058c
       int compression = -1;
Packit a4058c
       int x_density = 0;
Packit a4058c
       int y_density = 0;
Packit a4058c
       gboolean success = TRUE;
Packit a4058c
       guchar *icc_profile = NULL;
Packit a4058c
       gsize icc_profile_size = 0;
Packit a4058c
       SaveToFunctionIoPtr to_callback_ioptr;
Packit a4058c
Packit a4058c
       num_keys = 0;
Packit a4058c
Packit a4058c
       if (keys && *keys) {
Packit a4058c
               gchar **kiter = keys;
Packit a4058c
               gchar **viter = values;
Packit a4058c
Packit a4058c
               while (*kiter) {
Packit a4058c
                       if (strncmp (*kiter, "tEXt::", 6) == 0) {
Packit a4058c
                               gchar  *key = *kiter + 6;
Packit a4058c
                               int     len = strlen (key);
Packit a4058c
                               if (len < 1 || len > 79) {
Packit a4058c
                                       g_set_error_literal (error,
Packit a4058c
                                                            GDK_PIXBUF_ERROR,
Packit a4058c
                                                            GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                            _("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                               for (i = 0; i < len; i++) {
Packit a4058c
                                       if ((guchar) key[i] > 127) {
Packit a4058c
                                               g_set_error_literal (error,
Packit a4058c
                                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                                    _("Keys for PNG text chunks must be ASCII characters."));
Packit a4058c
                                               success = FALSE;
Packit a4058c
                                               goto cleanup;
Packit a4058c
                                       }
Packit a4058c
                               }
Packit a4058c
                               num_keys++;
Packit a4058c
                       } else if (strcmp (*kiter, "icc-profile") == 0) {
Packit a4058c
                               /* decode from base64 */
Packit a4058c
                               icc_profile = g_base64_decode (*viter, &icc_profile_size);
Packit a4058c
                               if (icc_profile_size < 127) {
Packit a4058c
                                       /* This is a user-visible error */
Packit a4058c
                                       g_set_error (error,
Packit a4058c
                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                    _("Color profile has invalid length %d."),
Packit a4058c
                                                    (gint)icc_profile_size);
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                       } else if (strcmp (*kiter, "compression") == 0) {
Packit a4058c
                               char *endptr = NULL;
Packit a4058c
                               compression = strtol (*viter, &endptr, 10);
Packit a4058c
Packit a4058c
                               if (endptr == *viter) {
Packit a4058c
                                       g_set_error (error,
Packit a4058c
                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                    _("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
Packit a4058c
                                                    *viter);
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                               if (compression < 0 || compression > 9) {
Packit a4058c
                                       /* This is a user-visible error;
Packit a4058c
                                        * lets people skip the range-checking
Packit a4058c
                                        * in their app.
Packit a4058c
                                        */
Packit a4058c
                                       g_set_error (error,
Packit a4058c
                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                    _("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
Packit a4058c
                                                    compression);
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                       } else if (strcmp (*kiter, "x-dpi") == 0) {
Packit a4058c
                               char *endptr = NULL;
Packit a4058c
                               x_density = strtol (*viter, &endptr, 10);
Packit a4058c
                               if (endptr == *viter)
Packit a4058c
                                       x_density = -1;
Packit a4058c
Packit a4058c
                               if (x_density <= 0) {
Packit a4058c
                                       /* This is a user-visible error;
Packit a4058c
                                        * lets people skip the range-checking
Packit a4058c
                                        * in their app.
Packit a4058c
                                        */
Packit a4058c
                                       g_set_error (error,
Packit a4058c
                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                    _("PNG x-dpi must be greater than zero; value '%s' is not allowed."),
Packit a4058c
                                                    *viter);
Packit a4058c
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                       } else if (strcmp (*kiter, "y-dpi") == 0) {
Packit a4058c
                               char *endptr = NULL;
Packit a4058c
                               y_density = strtol (*viter, &endptr, 10);
Packit a4058c
                               if (endptr == *viter)
Packit a4058c
                                       y_density = -1;
Packit a4058c
Packit a4058c
                               if (y_density <= 0) {
Packit a4058c
                                       /* This is a user-visible error;
Packit a4058c
                                        * lets people skip the range-checking
Packit a4058c
                                        * in their app.
Packit a4058c
                                        */
Packit a4058c
                                       g_set_error (error,
Packit a4058c
                                                    GDK_PIXBUF_ERROR,
Packit a4058c
                                                    GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                                    _("PNG y-dpi must be greater than zero; value '%s' is not allowed."),
Packit a4058c
                                                    *viter);
Packit a4058c
Packit a4058c
                                       success = FALSE;
Packit a4058c
                                       goto cleanup;
Packit a4058c
                               }
Packit a4058c
                       } else {
Packit a4058c
                               g_warning ("Unrecognized parameter (%s) passed to PNG saver.", *kiter);
Packit a4058c
                       }
Packit a4058c
Packit a4058c
                       ++kiter;
Packit a4058c
                       ++viter;
Packit a4058c
               }
Packit a4058c
       }
Packit a4058c
Packit a4058c
       if (num_keys > 0) {
Packit a4058c
               gchar **kiter = keys;
Packit a4058c
               gchar **viter = values;
Packit a4058c
Packit a4058c
               text_ptr = g_new0 (png_text, num_keys);
Packit a4058c
               for (i = 0; i < num_keys; i++) {
Packit a4058c
                       if (strncmp (*kiter, "tEXt::", 6) != 0) {
Packit a4058c
                                kiter++;
Packit a4058c
                                viter++;
Packit a4058c
                       }
Packit a4058c
Packit a4058c
                       text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
Packit a4058c
                       text_ptr[i].key  = *kiter + 6;
Packit a4058c
                       text_ptr[i].text = g_convert (*viter, -1, 
Packit a4058c
                                                     "ISO-8859-1", "UTF-8", 
Packit a4058c
                                                     NULL, &text_ptr[i].text_length, 
Packit a4058c
                                                     NULL);
Packit a4058c
Packit a4058c
#ifdef PNG_iTXt_SUPPORTED 
Packit a4058c
                       if (!text_ptr[i].text) {
Packit a4058c
                               text_ptr[i].compression = PNG_ITXT_COMPRESSION_NONE;
Packit a4058c
                               text_ptr[i].text = g_strdup (*viter);
Packit a4058c
                               text_ptr[i].text_length = 0;
Packit a4058c
                               text_ptr[i].itxt_length = strlen (text_ptr[i].text);
Packit a4058c
                               text_ptr[i].lang = NULL;
Packit a4058c
                               text_ptr[i].lang_key = NULL;
Packit a4058c
                       }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
                       if (!text_ptr[i].text) {
Packit a4058c
                               gint j;
Packit a4058c
                               g_set_error (error,
Packit a4058c
                                            GDK_PIXBUF_ERROR,
Packit a4058c
                                            GDK_PIXBUF_ERROR_BAD_OPTION,
Packit a4058c
                                            _("Value for PNG text chunk %s cannot be converted to ISO-8859-1 encoding."), *kiter + 6);
Packit a4058c
                               for (j = 0; j < i; j++)
Packit a4058c
                                       g_free (text_ptr[j].text);
Packit a4058c
                               g_free (text_ptr);
Packit a4058c
                               return FALSE;
Packit a4058c
                       }
Packit a4058c
Packit a4058c
                        kiter++;
Packit a4058c
                        viter++;
Packit a4058c
               }
Packit a4058c
       }
Packit a4058c
Packit a4058c
       bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
Packit a4058c
       w = gdk_pixbuf_get_width (pixbuf);
Packit a4058c
       h = gdk_pixbuf_get_height (pixbuf);
Packit a4058c
       rowstride = gdk_pixbuf_get_rowstride (pixbuf);
Packit a4058c
       has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
Packit a4058c
       pixels = gdk_pixbuf_get_pixels (pixbuf);
Packit a4058c
Packit a4058c
       /* Guaranteed by the caller. */
Packit a4058c
       g_assert (w >= 0);
Packit a4058c
       g_assert (h >= 0);
Packit a4058c
       g_assert (rowstride >= 0);
Packit a4058c
Packit a4058c
       png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
Packit a4058c
                                          error,
Packit a4058c
                                          png_simple_error_callback,
Packit a4058c
                                          png_simple_warning_callback);
Packit a4058c
       if (png_ptr == NULL) {
Packit a4058c
	       success = FALSE;
Packit a4058c
	       goto cleanup;
Packit a4058c
       }
Packit a4058c
Packit a4058c
       info_ptr = png_create_info_struct (png_ptr);
Packit a4058c
       if (info_ptr == NULL) {
Packit a4058c
	       success = FALSE;
Packit a4058c
	       goto cleanup;
Packit a4058c
       }
Packit a4058c
       if (setjmp (png_jmpbuf(png_ptr))) {
Packit a4058c
	       success = FALSE;
Packit a4058c
	       goto cleanup;
Packit a4058c
       }
Packit a4058c
Packit a4058c
       if (num_keys > 0) {
Packit a4058c
               png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
Packit a4058c
       }
Packit a4058c
Packit a4058c
       if (to_callback) {
Packit a4058c
               to_callback_ioptr.save_func = save_func;
Packit a4058c
               to_callback_ioptr.user_data = user_data;
Packit a4058c
               to_callback_ioptr.error = error;
Packit a4058c
               png_set_write_fn (png_ptr, &to_callback_ioptr,
Packit a4058c
                                 png_save_to_callback_write_func,
Packit a4058c
                                 png_save_to_callback_flush_func);
Packit a4058c
       } else {
Packit a4058c
               png_init_io (png_ptr, f);
Packit a4058c
       }
Packit a4058c
Packit a4058c
       if (compression >= 0)
Packit a4058c
               png_set_compression_level (png_ptr, compression);
Packit a4058c
Packit a4058c
#ifdef PNG_pHYs_SUPPORTED
Packit a4058c
       if (x_density > 0 && y_density > 0)
Packit a4058c
               png_set_pHYs (png_ptr, info_ptr, DPI_TO_DPM (x_density), DPI_TO_DPM (y_density), PNG_RESOLUTION_METER);
Packit a4058c
#endif
Packit a4058c
Packit a4058c
#if defined(PNG_iCCP_SUPPORTED)
Packit a4058c
        /* the proper ICC profile title is encoded in the profile */
Packit a4058c
        if (icc_profile != NULL) {
Packit a4058c
                png_set_iCCP (png_ptr, info_ptr,
Packit a4058c
                              "ICC profile", PNG_COMPRESSION_TYPE_BASE,
Packit a4058c
                              (png_bytep) icc_profile, icc_profile_size);
Packit a4058c
        }
Packit a4058c
#endif
Packit a4058c
Packit a4058c
       if (has_alpha) {
Packit a4058c
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Packit a4058c
                             PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
Packit a4058c
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
Packit a4058c
       } else {
Packit a4058c
               png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
Packit a4058c
                             PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
Packit a4058c
                             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
Packit a4058c
       }
Packit a4058c
       sig_bit.red = bpc;
Packit a4058c
       sig_bit.green = bpc;
Packit a4058c
       sig_bit.blue = bpc;
Packit a4058c
       sig_bit.alpha = bpc;
Packit a4058c
       png_set_sBIT (png_ptr, info_ptr, &sig_bit);
Packit a4058c
       png_write_info (png_ptr, info_ptr);
Packit a4058c
       png_set_shift (png_ptr, &sig_bit);
Packit a4058c
       png_set_packing (png_ptr);
Packit a4058c
Packit a4058c
       for (y = 0, ptr = pixels; y < h; y++, ptr += rowstride) {
Packit a4058c
               row_ptr = (png_bytep)ptr;
Packit a4058c
               png_write_rows (png_ptr, &row_ptr, 1);
Packit a4058c
       }
Packit a4058c
Packit a4058c
       png_write_end (png_ptr, info_ptr);
Packit a4058c
Packit a4058c
cleanup:
Packit a4058c
        if (png_ptr != NULL)
Packit a4058c
                png_destroy_write_struct (&png_ptr, &info_ptr);
Packit a4058c
Packit a4058c
        g_free (icc_profile);
Packit a4058c
Packit a4058c
        if (text_ptr != NULL) {
Packit a4058c
                for (i = 0; i < num_keys; i++)
Packit a4058c
                        g_free (text_ptr[i].text);
Packit a4058c
                g_free (text_ptr);
Packit a4058c
        }
Packit a4058c
Packit a4058c
       return success;
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
gdk_pixbuf__png_image_save (FILE          *f, 
Packit a4058c
                            GdkPixbuf     *pixbuf, 
Packit a4058c
                            gchar        **keys,
Packit a4058c
                            gchar        **values,
Packit a4058c
                            GError       **error)
Packit a4058c
{
Packit a4058c
        return real_save_png (pixbuf, keys, values, error,
Packit a4058c
                              FALSE, f, NULL, NULL);
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc   save_func,
Packit a4058c
                                        gpointer            user_data,
Packit a4058c
                                        GdkPixbuf          *pixbuf, 
Packit a4058c
                                        gchar             **keys,
Packit a4058c
                                        gchar             **values,
Packit a4058c
                                        GError            **error)
Packit a4058c
{
Packit a4058c
        return real_save_png (pixbuf, keys, values, error,
Packit a4058c
                              TRUE, NULL, save_func, user_data);
Packit a4058c
}
Packit a4058c
Packit a4058c
static gboolean
Packit a4058c
gdk_pixbuf__png_is_save_option_supported (const gchar *option_key)
Packit a4058c
{
Packit a4058c
        if (g_strcmp0 (option_key, "compression") == 0 ||
Packit a4058c
            g_strcmp0 (option_key, "icc-profile") == 0 ||
Packit a4058c
            g_strcmp0 (option_key, "x-dpi") == 0 ||
Packit a4058c
            g_strcmp0 (option_key, "y-dpi") == 0 ||
Packit a4058c
            strncmp (option_key, "tEXt::", 6) == 0)
Packit a4058c
                return TRUE;
Packit a4058c
Packit a4058c
        return FALSE;
Packit a4058c
}
Packit a4058c
Packit a4058c
#ifndef INCLUDE_png
Packit a4058c
#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
Packit a4058c
#else
Packit a4058c
#define MODULE_ENTRY(function) void _gdk_pixbuf__png_ ## function
Packit a4058c
#endif
Packit a4058c
Packit a4058c
MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
Packit a4058c
{
Packit a4058c
        module->load = gdk_pixbuf__png_image_load;
Packit a4058c
        module->begin_load = gdk_pixbuf__png_image_begin_load;
Packit a4058c
        module->stop_load = gdk_pixbuf__png_image_stop_load;
Packit a4058c
        module->load_increment = gdk_pixbuf__png_image_load_increment;
Packit a4058c
        module->save = gdk_pixbuf__png_image_save;
Packit a4058c
        module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
Packit a4058c
        module->is_save_option_supported = gdk_pixbuf__png_is_save_option_supported;
Packit a4058c
}
Packit a4058c
Packit a4058c
MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
Packit a4058c
{
Packit a4058c
        static const GdkPixbufModulePattern signature[] = {
Packit a4058c
                { "\x89PNG\r\n\x1a\x0a", NULL, 100 },
Packit a4058c
                { NULL, NULL, 0 }
Packit a4058c
        };
Packit a4058c
	static const gchar *mime_types[] = {
Packit a4058c
		"image/png",
Packit a4058c
		NULL
Packit a4058c
	};
Packit a4058c
	static const gchar *extensions[] = {
Packit a4058c
		"png",
Packit a4058c
		NULL
Packit a4058c
	};
Packit a4058c
Packit a4058c
	info->name = "png";
Packit a4058c
        info->signature = (GdkPixbufModulePattern *) signature;
Packit a4058c
	info->description = NC_("image format", "PNG");
Packit a4058c
	info->mime_types = (gchar **) mime_types;
Packit a4058c
	info->extensions = (gchar **) extensions;
Packit a4058c
	info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
Packit a4058c
	info->license = "LGPL";
Packit a4058c
}