1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /* GdkPixbuf library - PNG image loader
4 * Copyright (C) 1999 Mark Crichton
5 * Copyright (C) 1999 The Free Software Foundation
7 * Authors: Mark Crichton <crichton@gimp.org>
8 * Federico Mena-Quintero <federico@gimp.org>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
30 #include "gdk-pixbuf-private.h"
31 #include "gdk-pixbuf-io.h"
36 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
37 gboolean *fatal_error_occurred,
38 png_uint_32* width_p, png_uint_32* height_p,
41 png_uint_32 width, height;
42 int bit_depth, color_type, interlace_type, compression_type, filter_type;
43 #ifndef G_DISABLE_CHECKS
47 /* Get the image info */
49 png_get_IHDR (png_read_ptr, png_info_ptr,
57 /* set_expand() basically needs to be called unless
58 we are already in RGB/RGBA mode
60 if (color_type == PNG_COLOR_TYPE_PALETTE &&
63 /* Convert indexed images to RGB */
64 png_set_expand (png_read_ptr);
66 } else if (color_type == PNG_COLOR_TYPE_GRAY &&
69 /* Convert grayscale to RGB */
70 png_set_expand (png_read_ptr);
72 } else if (png_get_valid (png_read_ptr,
73 png_info_ptr, PNG_INFO_tRNS)) {
75 /* If we have transparency header, convert it to alpha
77 png_set_expand(png_read_ptr);
79 } else if (bit_depth < 8) {
81 /* If we have < 8 scale it up to 8 */
82 png_set_expand(png_read_ptr);
85 /* Conceivably, png_set_packing() is a better idea;
86 * God only knows how libpng works
90 /* If we are 16-bit, convert to 8-bit */
91 if (bit_depth == 16) {
92 png_set_strip_16(png_read_ptr);
95 /* If gray scale, convert to RGB */
96 if (color_type == PNG_COLOR_TYPE_GRAY ||
97 color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
98 png_set_gray_to_rgb(png_read_ptr);
101 /* If interlaced, handle that */
102 if (interlace_type != PNG_INTERLACE_NONE) {
103 png_set_interlace_handling(png_read_ptr);
106 /* Update the info the reflect our transformations */
107 png_read_update_info(png_read_ptr, png_info_ptr);
109 png_get_IHDR (png_read_ptr, png_info_ptr,
119 *color_type_p = color_type;
121 #ifndef G_DISABLE_CHECKS
122 /* Check that the new info is what we want */
124 if (bit_depth != 8) {
125 g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
126 *fatal_error_occurred = TRUE;
130 if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
131 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
132 g_warning("Transformed PNG not RGB or RGBA.");
133 *fatal_error_occurred = TRUE;
137 channels = png_get_channels(png_read_ptr, png_info_ptr);
138 if ( ! (channels == 3 || channels == 4) ) {
139 g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
140 *fatal_error_occurred = TRUE;
147 png_simple_error_callback(png_structp png_save_ptr,
148 png_const_charp error_msg)
152 error = png_get_error_ptr(png_save_ptr);
154 /* I don't trust libpng to call the error callback only once,
155 * so check for already-set error
157 if (error && *error == NULL) {
160 GDK_PIXBUF_ERROR_FAILED,
161 _("Fatal error in PNG image file: %s"),
167 png_simple_warning_callback(png_structp png_save_ptr,
168 png_const_charp warning_msg)
170 /* Don't print anything; we should not be dumping junk to
171 * stderr, since that may be bad for some apps. If it's
172 * important enough to display, we need to add a GError
173 * **warning return location wherever we have an error return
178 /* Destroy notification function for the pixbuf */
180 free_buffer (guchar *pixels, gpointer data)
185 /* Shared library entry point */
187 gdk_pixbuf__png_image_load (FILE *f, GError **error)
190 png_infop info_ptr, end_info;
191 gboolean failed = FALSE;
197 png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
199 png_simple_error_callback,
200 png_simple_warning_callback);
204 info_ptr = png_create_info_struct (png_ptr);
206 png_destroy_read_struct (&png_ptr, NULL, NULL);
210 end_info = png_create_info_struct (png_ptr);
212 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
216 if (setjmp (png_ptr->jmpbuf)) {
217 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
221 png_init_io (png_ptr, f);
222 png_read_info (png_ptr, info_ptr);
224 setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
227 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
231 if (ctype & PNG_COLOR_MASK_ALPHA)
236 pixels = g_try_malloc (w * h * bpp);
238 /* Check error NULL, normally this would be broken,
239 * but libpng makes me want to code defensively.
241 if (error && *error == NULL) {
244 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
245 _("Insufficient memory to load PNG file"));
248 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
252 rows = g_new (png_bytep, h);
254 for (i = 0; i < h; i++)
255 rows[i] = pixels + i * w * bpp;
257 png_read_image (png_ptr, rows);
258 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
261 if (ctype & PNG_COLOR_MASK_ALPHA)
262 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
266 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
271 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
272 just allow you to change the error reporting. */
273 static void png_error_callback (png_structp png_read_ptr,
274 png_const_charp error_msg);
276 static void png_warning_callback(png_structp png_read_ptr,
277 png_const_charp warning_msg);
279 /* Called at the start of the progressive load */
280 static void png_info_callback (png_structp png_read_ptr,
281 png_infop png_info_ptr);
283 /* Called for each row; note that you will get duplicate row numbers
284 for interlaced PNGs */
285 static void png_row_callback (png_structp png_read_ptr,
290 /* Called after reading the entire image */
291 static void png_end_callback (png_structp png_read_ptr,
292 png_infop png_info_ptr);
294 typedef struct _LoadContext LoadContext;
296 struct _LoadContext {
297 png_structp png_read_ptr;
298 png_infop png_info_ptr;
300 ModulePreparedNotifyFunc prepare_func;
301 ModuleUpdatedNotifyFunc update_func;
302 gpointer notify_user_data;
306 /* row number of first row seen, or -1 if none yet seen */
308 gint first_row_seen_in_chunk;
310 /* pass number for the first row seen */
312 gint first_pass_seen_in_chunk;
314 /* row number of last row seen */
315 gint last_row_seen_in_chunk;
317 gint last_pass_seen_in_chunk;
319 /* highest row number seen */
320 gint max_row_seen_in_chunk;
322 guint fatal_error_occurred : 1;
328 gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
329 ModuleUpdatedNotifyFunc update_func,
335 lc = g_new0(LoadContext, 1);
337 lc->fatal_error_occurred = FALSE;
339 lc->prepare_func = prepare_func;
340 lc->update_func = update_func;
341 lc->notify_user_data = user_data;
343 lc->first_row_seen_in_chunk = -1;
344 lc->last_row_seen_in_chunk = -1;
345 lc->first_pass_seen_in_chunk = -1;
346 lc->last_pass_seen_in_chunk = -1;
347 lc->max_row_seen_in_chunk = -1;
350 /* Create the main PNG context struct */
353 lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
354 lc, /* error/warning callback data */
356 png_warning_callback);
358 if (lc->png_read_ptr == NULL) {
360 /* error callback should have set the error */
364 if (setjmp (lc->png_read_ptr->jmpbuf)) {
365 if (lc->png_info_ptr)
366 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
368 /* error callback should have set the error */
372 /* Create the auxiliary context struct */
374 lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
376 if (lc->png_info_ptr == NULL) {
377 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
379 /* error callback should have set the error */
383 png_set_progressive_read_fn(lc->png_read_ptr,
384 lc, /* callback data */
390 /* We don't want to keep modifying error after returning here,
391 * it may no longer be valid.
399 gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
401 LoadContext* lc = context;
403 g_return_val_if_fail(lc != NULL, TRUE);
405 /* FIXME this thing needs to report errors if
406 * we have unused image data
410 gdk_pixbuf_unref (lc->pixbuf);
412 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
419 gdk_pixbuf__png_image_load_increment(gpointer context,
420 const guchar *buf, guint size,
423 LoadContext* lc = context;
425 g_return_val_if_fail(lc != NULL, FALSE);
428 lc->first_row_seen_in_chunk = -1;
429 lc->last_row_seen_in_chunk = -1;
430 lc->first_pass_seen_in_chunk = -1;
431 lc->last_pass_seen_in_chunk = -1;
432 lc->max_row_seen_in_chunk = -1;
435 /* Invokes our callbacks as needed */
436 if (setjmp (lc->png_read_ptr->jmpbuf)) {
440 png_process_data(lc->png_read_ptr, lc->png_info_ptr,
441 (guchar*) buf, size);
444 if (lc->fatal_error_occurred) {
448 if (lc->first_row_seen_in_chunk >= 0) {
449 /* We saw at least one row */
450 gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
452 g_assert(pass_diff >= 0);
454 if (pass_diff == 0) {
455 /* start and end row were in the same pass */
456 (lc->update_func)(lc->pixbuf, 0,
457 lc->first_row_seen_in_chunk,
459 (lc->last_row_seen_in_chunk -
460 lc->first_row_seen_in_chunk) + 1,
461 lc->notify_user_data);
462 } else if (pass_diff == 1) {
463 /* We have from the first row seen to
464 the end of the image (max row
465 seen), then from the top of the
466 image to the last row seen */
467 /* first row to end */
468 (lc->update_func)(lc->pixbuf, 0,
469 lc->first_row_seen_in_chunk,
471 (lc->max_row_seen_in_chunk -
472 lc->first_row_seen_in_chunk) + 1,
473 lc->notify_user_data);
474 /* top to last row */
475 (lc->update_func)(lc->pixbuf,
478 lc->last_row_seen_in_chunk + 1,
479 lc->notify_user_data);
481 /* We made at least one entire pass, so update the
483 (lc->update_func)(lc->pixbuf,
486 lc->max_row_seen_in_chunk + 1,
487 lc->notify_user_data);
497 /* Called at the start of the progressive load, once we have image info */
499 png_info_callback (png_structp png_read_ptr,
500 png_infop png_info_ptr)
503 png_uint_32 width, height;
505 gboolean have_alpha = FALSE;
506 gboolean failed = FALSE;
508 lc = png_get_progressive_ptr(png_read_ptr);
510 if (lc->fatal_error_occurred)
513 setup_png_transformations(lc->png_read_ptr,
516 &width, &height, &color_type);
519 lc->fatal_error_occurred = TRUE;
523 /* If we have alpha, set a flag */
524 if (color_type & PNG_COLOR_MASK_ALPHA)
527 lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
529 if (lc->pixbuf == NULL) {
530 /* Failed to allocate memory */
531 lc->fatal_error_occurred = TRUE;
532 if (lc->error && *lc->error == NULL) {
533 g_set_error (lc->error,
535 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
536 _("Insufficient memory to store a %ld by %ld image; try exiting some applications to reduce memory usage"),
542 /* Notify the client that we are ready to go */
544 if (lc->prepare_func)
545 (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
550 /* Called for each row; note that you will get duplicate row numbers
551 for interlaced PNGs */
553 png_row_callback (png_structp png_read_ptr,
559 guchar* old_row = NULL;
561 lc = png_get_progressive_ptr(png_read_ptr);
563 if (lc->fatal_error_occurred)
566 if (lc->first_row_seen_in_chunk < 0) {
567 lc->first_row_seen_in_chunk = row_num;
568 lc->first_pass_seen_in_chunk = pass_num;
571 lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
572 lc->last_row_seen_in_chunk = row_num;
573 lc->last_pass_seen_in_chunk = pass_num;
575 old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
577 png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
580 /* Called after reading the entire image */
582 png_end_callback (png_structp png_read_ptr,
583 png_infop png_info_ptr)
587 lc = png_get_progressive_ptr(png_read_ptr);
589 if (lc->fatal_error_occurred)
594 png_error_callback(png_structp png_read_ptr,
595 png_const_charp error_msg)
599 lc = png_get_error_ptr(png_read_ptr);
601 lc->fatal_error_occurred = TRUE;
603 /* I don't trust libpng to call the error callback only once,
604 * so check for already-set error
606 if (lc->error && *lc->error == NULL) {
607 g_set_error (lc->error,
609 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
610 _("Fatal error reading PNG image file: %s"),
614 longjmp (png_read_ptr->jmpbuf, 1);
618 png_warning_callback(png_structp png_read_ptr,
619 png_const_charp warning_msg)
623 lc = png_get_error_ptr(png_read_ptr);
625 /* Don't print anything; we should not be dumping junk to
626 * stderr, since that may be bad for some apps. If it's
627 * important enough to display, we need to add a GError
628 * **warning return location wherever we have an error return
637 gdk_pixbuf__png_image_save (FILE *f,
656 g_warning ("Bad option name '%s' passed to PNG saver",
660 gchar **kiter = keys;
661 gchar **viter = values;
673 bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
674 w = gdk_pixbuf_get_width (pixbuf);
675 h = gdk_pixbuf_get_height (pixbuf);
676 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
677 has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
678 pixels = gdk_pixbuf_get_pixels (pixbuf);
680 png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
682 png_simple_error_callback,
683 png_simple_warning_callback);
685 g_return_val_if_fail (png_ptr != NULL, FALSE);
687 info_ptr = png_create_info_struct (png_ptr);
688 if (info_ptr == NULL) {
689 png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
692 if (setjmp (png_ptr->jmpbuf)) {
693 png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
696 png_init_io (png_ptr, f);
698 png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
699 PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
700 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
701 #ifdef WORDS_BIGENDIAN
702 png_set_swap_alpha (png_ptr);
704 png_set_bgr (png_ptr);
707 png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
708 PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
709 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
710 data = g_try_malloc (w * 3 * sizeof (char));
713 /* Check error NULL, normally this would be broken,
714 * but libpng makes me want to code defensively.
716 if (error && *error == NULL) {
719 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
720 _("Insufficient memory to save PNG file"));
722 png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
730 png_set_sBIT (png_ptr, info_ptr, &sig_bit);
731 png_write_info (png_ptr, info_ptr);
732 png_set_shift (png_ptr, &sig_bit);
733 png_set_packing (png_ptr);
736 for (y = 0; y < h; y++) {
738 row_ptr = (png_bytep)ptr;
740 for (j = 0, x = 0; x < w; x++)
741 memcpy (&(data[x*3]), &(ptr[x*3]), 3);
743 row_ptr = (png_bytep)data;
745 png_write_rows (png_ptr, &row_ptr, 1);
752 png_write_end (png_ptr, info_ptr);
753 png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
761 gdk_pixbuf__png_fill_vtable (GdkPixbufModule *module)
763 module->load = gdk_pixbuf__png_image_load;
764 module->begin_load = gdk_pixbuf__png_image_begin_load;
765 module->stop_load = gdk_pixbuf__png_image_stop_load;
766 module->load_increment = gdk_pixbuf__png_image_load_increment;
767 module->save = gdk_pixbuf__png_image_save;