mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-14 04:31:09 +00:00
7a4c689387
2000-10-18 Havoc Pennington <hp@redhat.com> * gtk/gtk-sections.txt: Some updates * gdk/gdk-sections.txt: remove GdkPixbufAlphaMode * gdk-pixbuf/gdk-pixbuf-sections.txt: Add new API, remove GdkPixbufClass/GdkAnimationClass since those are private * gdk-pixbuf/Makefile.am (IGNORE_HFILES): ignore more headers 2000-10-18 Havoc Pennington <hp@redhat.com> * gtk/gtktextiter.c (gtk_text_iter_forward_to_newline): Fix a bug where any number of empty lines would get skipped * gtk/gtktextiter.h: Remove padding from GtkTextIter; live on the edge. * gtk/gtktextiter.c (gtk_text_iter_make_surreal): enhance the warning about invalid iterators (explain more thoroughly) (gtk_text_iter_in_region): rename gtk_text_iter_in_range * gtk/gtktextview.c (FOCUS_EDGE_WIDTH): Make focus rectangle less big * demos/*.c: Add error handling * gtk/gtktextbuffer.c: don't modify const iterators * gtk/gdk-pixbuf-loader.c: Add full error handling here * gtk/gtkimage.c (gtk_image_set_from_file): ignore errors on file load * gtk/gtkiconfactory.c: Update to reflect addition of error handling to gdk-pixbuf loaders 2000-10-16 Havoc Pennington <hp@redhat.com> * gdk-pixbuf-io.c (gdk_pixbuf_get_module) (gdk_pixbuf_get_named_module) (gdk_pixbuf_load_module): add error reporting here also * make-inline-pixbuf.c (main): use GError * io-xpm.c: include unistd.h * gdk-pixbuf-util.c: include string.h * io-*.c: add error reporting * gdk-pixbuf-animation.c (gdk_pixbuf_animation_new_from_file): add error reporting * gdk-pixbuf-io.c (gdk_pixbuf_new_from_file): Add error reporting * gdk-pixbuf-io.h: Add GError** to load_increment and load methods * gdk-pixbuf-io.c (gdk_pixbuf_save) (gdk_pixbuf_savev): return a G_FILE_ERROR if we fail to write or close the file. * gdk-pixbuf.h: remove GDK_PIXBUF_ERROR_IO, instead we'll use G_FILE_ERROR_*. Rename enum to GdkPixbufError, properly following the GError naming rules. Add GError** to load functions.
746 lines
24 KiB
C
746 lines
24 KiB
C
/* GdkPixbuf library - PNG image loader
|
||
*
|
||
* Copyright (C) 1999 Mark Crichton
|
||
* Copyright (C) 1999 The Free Software Foundation
|
||
*
|
||
* Authors: Mark Crichton <crichton@gimp.org>
|
||
* Federico Mena-Quintero <federico@gimp.org>
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* 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
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* 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.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <png.h>
|
||
#include "gdk-pixbuf-private.h"
|
||
#include "gdk-pixbuf-io.h"
|
||
|
||
|
||
|
||
static void
|
||
setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
|
||
gboolean *fatal_error_occurred,
|
||
png_uint_32* width_p, png_uint_32* height_p,
|
||
int* color_type_p)
|
||
{
|
||
png_uint_32 width, height;
|
||
int bit_depth, color_type, interlace_type, compression_type, filter_type;
|
||
int channels;
|
||
|
||
/* Get the image info */
|
||
|
||
png_get_IHDR (png_read_ptr, png_info_ptr,
|
||
&width, &height,
|
||
&bit_depth,
|
||
&color_type,
|
||
&interlace_type,
|
||
&compression_type,
|
||
&filter_type);
|
||
|
||
/* set_expand() basically needs to be called unless
|
||
we are already in RGB/RGBA mode
|
||
*/
|
||
if (color_type == PNG_COLOR_TYPE_PALETTE &&
|
||
bit_depth <= 8) {
|
||
|
||
/* Convert indexed images to RGB */
|
||
png_set_expand (png_read_ptr);
|
||
|
||
} else if (color_type == PNG_COLOR_TYPE_GRAY &&
|
||
bit_depth < 8) {
|
||
|
||
/* Convert grayscale to RGB */
|
||
png_set_expand (png_read_ptr);
|
||
|
||
} else if (png_get_valid (png_read_ptr,
|
||
png_info_ptr, PNG_INFO_tRNS)) {
|
||
|
||
/* If we have transparency header, convert it to alpha
|
||
channel */
|
||
png_set_expand(png_read_ptr);
|
||
|
||
} else if (bit_depth < 8) {
|
||
|
||
/* If we have < 8 scale it up to 8 */
|
||
png_set_expand(png_read_ptr);
|
||
|
||
|
||
/* Conceivably, png_set_packing() is a better idea;
|
||
* God only knows how libpng works
|
||
*/
|
||
}
|
||
|
||
/* If we are 16-bit, convert to 8-bit */
|
||
if (bit_depth == 16) {
|
||
png_set_strip_16(png_read_ptr);
|
||
}
|
||
|
||
/* If gray scale, convert to RGB */
|
||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||
png_set_gray_to_rgb(png_read_ptr);
|
||
}
|
||
|
||
/* If interlaced, handle that */
|
||
if (interlace_type != PNG_INTERLACE_NONE) {
|
||
png_set_interlace_handling(png_read_ptr);
|
||
}
|
||
|
||
/* Update the info the reflect our transformations */
|
||
png_read_update_info(png_read_ptr, png_info_ptr);
|
||
|
||
png_get_IHDR (png_read_ptr, png_info_ptr,
|
||
&width, &height,
|
||
&bit_depth,
|
||
&color_type,
|
||
&interlace_type,
|
||
&compression_type,
|
||
&filter_type);
|
||
|
||
*width_p = width;
|
||
*height_p = height;
|
||
*color_type_p = color_type;
|
||
|
||
#ifndef G_DISABLE_CHECKS
|
||
/* Check that the new info is what we want */
|
||
|
||
if (bit_depth != 8) {
|
||
g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
|
||
*fatal_error_occurred = TRUE;
|
||
return;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
static void
|
||
png_simple_error_callback(png_structp png_save_ptr,
|
||
png_const_charp error_msg)
|
||
{
|
||
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 saving PNG image file: %s"),
|
||
error_msg);
|
||
}
|
||
}
|
||
|
||
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.
|
||
*/
|
||
}
|
||
|
||
/* Destroy notification function for the pixbuf */
|
||
static void
|
||
free_buffer (guchar *pixels, gpointer data)
|
||
{
|
||
free (pixels);
|
||
}
|
||
|
||
/* Shared library entry point */
|
||
GdkPixbuf *
|
||
gdk_pixbuf__png_image_load (FILE *f, GError **error)
|
||
{
|
||
png_structp png_ptr;
|
||
png_infop info_ptr, end_info;
|
||
gboolean failed = FALSE;
|
||
gint i, ctype, bpp;
|
||
png_uint_32 w, h;
|
||
png_bytepp rows;
|
||
guchar *pixels;
|
||
|
||
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
|
||
error,
|
||
png_simple_error_callback,
|
||
png_simple_warning_callback);
|
||
if (!png_ptr)
|
||
return NULL;
|
||
|
||
info_ptr = png_create_info_struct (png_ptr);
|
||
if (!info_ptr) {
|
||
png_destroy_read_struct (&png_ptr, NULL, NULL);
|
||
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);
|
||
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);
|
||
return NULL;
|
||
}
|
||
|
||
if (ctype & PNG_COLOR_MASK_ALPHA)
|
||
bpp = 4;
|
||
else
|
||
bpp = 3;
|
||
|
||
pixels = malloc (w * h * bpp);
|
||
if (!pixels) {
|
||
/* Check error NULL, normally this would be broken,
|
||
* but libpng makes me want to code defensively.
|
||
*/
|
||
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, &end_info);
|
||
return NULL;
|
||
}
|
||
|
||
rows = g_new (png_bytep, h);
|
||
|
||
for (i = 0; i < h; i++)
|
||
rows[i] = pixels + i * w * bpp;
|
||
|
||
png_read_image (png_ptr, rows);
|
||
png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
|
||
g_free (rows);
|
||
|
||
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);
|
||
}
|
||
|
||
/* 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);
|
||
|
||
static void png_warning_callback(png_structp png_read_ptr,
|
||
png_const_charp warning_msg);
|
||
|
||
/* Called at the start of the progressive load */
|
||
static void png_info_callback (png_structp png_read_ptr,
|
||
png_infop png_info_ptr);
|
||
|
||
/* Called for each row; note that you will get duplicate row numbers
|
||
for interlaced PNGs */
|
||
static void png_row_callback (png_structp png_read_ptr,
|
||
png_bytep new_row,
|
||
png_uint_32 row_num,
|
||
int pass_num);
|
||
|
||
/* Called after reading the entire image */
|
||
static void png_end_callback (png_structp png_read_ptr,
|
||
png_infop png_info_ptr);
|
||
|
||
typedef struct _LoadContext LoadContext;
|
||
|
||
struct _LoadContext {
|
||
png_structp png_read_ptr;
|
||
png_infop png_info_ptr;
|
||
|
||
ModulePreparedNotifyFunc prepare_func;
|
||
ModuleUpdatedNotifyFunc update_func;
|
||
gpointer notify_user_data;
|
||
|
||
GdkPixbuf* pixbuf;
|
||
|
||
/* row number of first row seen, or -1 if none yet seen */
|
||
|
||
gint first_row_seen_in_chunk;
|
||
|
||
/* pass number for the first row seen */
|
||
|
||
gint first_pass_seen_in_chunk;
|
||
|
||
/* row number of last row seen */
|
||
gint last_row_seen_in_chunk;
|
||
|
||
gint last_pass_seen_in_chunk;
|
||
|
||
/* highest row number seen */
|
||
gint max_row_seen_in_chunk;
|
||
|
||
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,
|
||
GError **error)
|
||
{
|
||
LoadContext* lc;
|
||
|
||
lc = g_new0(LoadContext, 1);
|
||
|
||
lc->fatal_error_occurred = FALSE;
|
||
|
||
lc->prepare_func = prepare_func;
|
||
lc->update_func = update_func;
|
||
lc->notify_user_data = user_data;
|
||
|
||
lc->first_row_seen_in_chunk = -1;
|
||
lc->last_row_seen_in_chunk = -1;
|
||
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 */
|
||
|
||
|
||
lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||
lc, /* error/warning callback data */
|
||
png_error_callback,
|
||
png_warning_callback);
|
||
|
||
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 */
|
||
|
||
lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
|
||
|
||
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;
|
||
}
|
||
|
||
png_set_progressive_read_fn(lc->png_read_ptr,
|
||
lc, /* callback data */
|
||
png_info_callback,
|
||
png_row_callback,
|
||
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)
|
||
{
|
||
LoadContext* lc = context;
|
||
|
||
g_return_if_fail(lc != NULL);
|
||
|
||
gdk_pixbuf_unref(lc->pixbuf);
|
||
|
||
png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
|
||
g_free(lc);
|
||
}
|
||
|
||
gboolean
|
||
gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size,
|
||
GError **error)
|
||
{
|
||
LoadContext* lc = context;
|
||
|
||
g_return_val_if_fail(lc != NULL, FALSE);
|
||
|
||
/* reset */
|
||
lc->first_row_seen_in_chunk = -1;
|
||
lc->last_row_seen_in_chunk = -1;
|
||
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);
|
||
}
|
||
|
||
if (lc->fatal_error_occurred) {
|
||
lc->error = NULL;
|
||
return FALSE;
|
||
} 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;
|
||
|
||
g_assert(pass_diff >= 0);
|
||
|
||
if (pass_diff == 0) {
|
||
/* start and end row were in the same pass */
|
||
(lc->update_func)(lc->pixbuf, 0,
|
||
lc->first_row_seen_in_chunk,
|
||
lc->pixbuf->width,
|
||
(lc->last_row_seen_in_chunk -
|
||
lc->first_row_seen_in_chunk) + 1,
|
||
lc->notify_user_data);
|
||
} else if (pass_diff == 1) {
|
||
/* We have from the first row seen to
|
||
the end of the image (max row
|
||
seen), then from the top of the
|
||
image to the last row seen */
|
||
/* first row to end */
|
||
(lc->update_func)(lc->pixbuf, 0,
|
||
lc->first_row_seen_in_chunk,
|
||
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->width,
|
||
lc->last_row_seen_in_chunk + 1,
|
||
lc->notify_user_data);
|
||
} else {
|
||
/* We made at least one entire pass, so update the
|
||
whole image */
|
||
(lc->update_func)(lc->pixbuf,
|
||
0, 0,
|
||
lc->pixbuf->width,
|
||
lc->max_row_seen_in_chunk + 1,
|
||
lc->notify_user_data);
|
||
}
|
||
}
|
||
|
||
lc->error = NULL;
|
||
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
/* Called at the start of the progressive load, once we have image info */
|
||
static void
|
||
png_info_callback (png_structp png_read_ptr,
|
||
png_infop png_info_ptr)
|
||
{
|
||
LoadContext* lc;
|
||
png_uint_32 width, height;
|
||
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) {
|
||
lc->fatal_error_occurred = TRUE;
|
||
return;
|
||
}
|
||
|
||
/* If we have alpha, set a flag */
|
||
if (color_type & PNG_COLOR_MASK_ALPHA)
|
||
have_alpha = TRUE;
|
||
|
||
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;
|
||
}
|
||
|
||
/* Notify the client that we are ready to go */
|
||
|
||
if (lc->prepare_func)
|
||
(* lc->prepare_func) (lc->pixbuf, lc->notify_user_data);
|
||
|
||
return;
|
||
}
|
||
|
||
/* Called for each row; note that you will get duplicate row numbers
|
||
for interlaced PNGs */
|
||
static void
|
||
png_row_callback (png_structp png_read_ptr,
|
||
png_bytep new_row,
|
||
png_uint_32 row_num,
|
||
int pass_num)
|
||
{
|
||
LoadContext* lc;
|
||
guchar* old_row = NULL;
|
||
|
||
lc = png_get_progressive_ptr(png_read_ptr);
|
||
|
||
if (lc->fatal_error_occurred)
|
||
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;
|
||
}
|
||
|
||
lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
|
||
lc->last_row_seen_in_chunk = row_num;
|
||
lc->last_pass_seen_in_chunk = pass_num;
|
||
|
||
old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
|
||
|
||
png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
|
||
}
|
||
|
||
/* Called after reading the entire image */
|
||
static void
|
||
png_end_callback (png_structp png_read_ptr,
|
||
png_infop png_info_ptr)
|
||
{
|
||
LoadContext* lc;
|
||
|
||
lc = png_get_progressive_ptr(png_read_ptr);
|
||
|
||
if (lc->fatal_error_occurred)
|
||
return;
|
||
}
|
||
|
||
static void
|
||
png_error_callback(png_structp png_read_ptr,
|
||
png_const_charp error_msg)
|
||
{
|
||
LoadContext* lc;
|
||
|
||
lc = png_get_error_ptr(png_read_ptr);
|
||
|
||
lc->fatal_error_occurred = TRUE;
|
||
|
||
/* 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);
|
||
}
|
||
}
|
||
|
||
static void
|
||
png_warning_callback(png_structp png_read_ptr,
|
||
png_const_charp warning_msg)
|
||
{
|
||
LoadContext* lc;
|
||
|
||
lc = png_get_error_ptr(png_read_ptr);
|
||
|
||
/* 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
|
||
gdk_pixbuf__png_image_save (FILE *f,
|
||
GdkPixbuf *pixbuf,
|
||
gchar **keys,
|
||
gchar **values,
|
||
GError **error)
|
||
{
|
||
png_structp png_ptr;
|
||
png_infop info_ptr;
|
||
guchar *ptr;
|
||
guchar *pixels;
|
||
int x, y, j;
|
||
png_bytep row_ptr, data = NULL;
|
||
png_color_8 sig_bit;
|
||
int w, h, rowstride;
|
||
int has_alpha;
|
||
int bpc;
|
||
|
||
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;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
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) {
|
||
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
|
||
return FALSE;
|
||
}
|
||
if (setjmp (png_ptr->jmpbuf)) {
|
||
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
|
||
return FALSE;
|
||
}
|
||
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
|
||
} 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));
|
||
|
||
if (data == NULL) {
|
||
/* Check error NULL, normally this would be broken,
|
||
* but libpng makes me want to code defensively.
|
||
*/
|
||
if (error && *error == NULL) {
|
||
g_set_error (error,
|
||
GDK_PIXBUF_ERROR,
|
||
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
||
_("Insufficient memory to save PNG file"));
|
||
}
|
||
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
|
||
return FALSE;
|
||
}
|
||
}
|
||
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++) {
|
||
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;
|
||
}
|
||
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;
|
||
}
|
||
|
||
|
||
|