1 /* GdkPixbuf library - PNG image loader
3 * Copyright (C) 1999 Mark Crichton
4 * Copyright (C) 1999 The Free Software Foundation
6 * Authors: Mark Crichton <crichton@gimp.org>
7 * Federico Mena-Quintero <federico@gimp.org>
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
29 #include "gdk-pixbuf-private.h"
30 #include "gdk-pixbuf-io.h"
35 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
36 gboolean *fatal_error_occurred,
37 png_uint_32* width_p, png_uint_32* height_p,
40 png_uint_32 width, height;
41 int bit_depth, color_type, interlace_type, compression_type, filter_type;
44 /* Get the image info */
46 png_get_IHDR (png_read_ptr, png_info_ptr,
54 /* set_expand() basically needs to be called unless
55 we are already in RGB/RGBA mode
57 if (color_type == PNG_COLOR_TYPE_PALETTE &&
60 /* Convert indexed images to RGB */
61 png_set_expand (png_read_ptr);
63 } else if (color_type == PNG_COLOR_TYPE_GRAY &&
66 /* Convert grayscale to RGB */
67 png_set_expand (png_read_ptr);
69 } else if (png_get_valid (png_read_ptr,
70 png_info_ptr, PNG_INFO_tRNS)) {
72 /* If we have transparency header, convert it to alpha
74 png_set_expand(png_read_ptr);
76 } else if (bit_depth < 8) {
78 /* If we have < 8 scale it up to 8 */
79 png_set_expand(png_read_ptr);
82 /* Conceivably, png_set_packing() is a better idea;
83 * God only knows how libpng works
87 /* If we are 16-bit, convert to 8-bit */
88 if (bit_depth == 16) {
89 png_set_strip_16(png_read_ptr);
92 /* If gray scale, convert to RGB */
93 if (color_type == PNG_COLOR_TYPE_GRAY ||
94 color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
95 png_set_gray_to_rgb(png_read_ptr);
98 /* If interlaced, handle that */
99 if (interlace_type != PNG_INTERLACE_NONE) {
100 png_set_interlace_handling(png_read_ptr);
103 /* Update the info the reflect our transformations */
104 png_read_update_info(png_read_ptr, png_info_ptr);
106 png_get_IHDR (png_read_ptr, png_info_ptr,
116 *color_type_p = color_type;
118 #ifndef G_DISABLE_CHECKS
119 /* Check that the new info is what we want */
121 if (bit_depth != 8) {
122 g_warning("Bits per channel of transformed PNG is %d, not 8.", bit_depth);
123 *fatal_error_occurred = TRUE;
127 if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
128 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
129 g_warning("Transformed PNG not RGB or RGBA.");
130 *fatal_error_occurred = TRUE;
134 channels = png_get_channels(png_read_ptr, png_info_ptr);
135 if ( ! (channels == 3 || channels == 4) ) {
136 g_warning("Transformed PNG has %d channels, must be 3 or 4.", channels);
137 *fatal_error_occurred = TRUE;
143 /* Destroy notification function for the pixbuf */
145 free_buffer (guchar *pixels, gpointer data)
150 /* Shared library entry point */
152 gdk_pixbuf__png_image_load (FILE *f)
155 png_infop info_ptr, end_info;
156 gboolean failed = FALSE;
162 png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
166 info_ptr = png_create_info_struct (png_ptr);
168 png_destroy_read_struct (&png_ptr, NULL, NULL);
172 end_info = png_create_info_struct (png_ptr);
174 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
178 if (setjmp (png_ptr->jmpbuf)) {
179 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
183 png_init_io (png_ptr, f);
184 png_read_info (png_ptr, info_ptr);
186 setup_png_transformations(png_ptr, info_ptr, &failed, &w, &h, &ctype);
189 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
193 if (ctype & PNG_COLOR_MASK_ALPHA)
198 pixels = malloc (w * h * bpp);
200 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
204 rows = g_new (png_bytep, h);
206 for (i = 0; i < h; i++)
207 rows[i] = pixels + i * w * bpp;
209 png_read_image (png_ptr, rows);
210 png_destroy_read_struct (&png_ptr, &info_ptr, &end_info);
213 if (ctype & PNG_COLOR_MASK_ALPHA)
214 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8,
218 return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
223 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
224 just allow you to change the error reporting. */
225 static void png_error_callback (png_structp png_read_ptr,
226 png_const_charp error_msg);
228 static void png_warning_callback(png_structp png_read_ptr,
229 png_const_charp warning_msg);
231 /* Called at the start of the progressive load */
232 static void png_info_callback (png_structp png_read_ptr,
233 png_infop png_info_ptr);
235 /* Called for each row; note that you will get duplicate row numbers
236 for interlaced PNGs */
237 static void png_row_callback (png_structp png_read_ptr,
242 /* Called after reading the entire image */
243 static void png_end_callback (png_structp png_read_ptr,
244 png_infop png_info_ptr);
246 typedef struct _LoadContext LoadContext;
248 struct _LoadContext {
249 png_structp png_read_ptr;
250 png_infop png_info_ptr;
252 ModulePreparedNotifyFunc prepare_func;
253 ModuleUpdatedNotifyFunc update_func;
254 gpointer notify_user_data;
258 /* row number of first row seen, or -1 if none yet seen */
260 gint first_row_seen_in_chunk;
262 /* pass number for the first row seen */
264 gint first_pass_seen_in_chunk;
266 /* row number of last row seen */
267 gint last_row_seen_in_chunk;
269 gint last_pass_seen_in_chunk;
271 /* highest row number seen */
272 gint max_row_seen_in_chunk;
274 guint fatal_error_occurred : 1;
279 gdk_pixbuf__png_image_begin_load (ModulePreparedNotifyFunc prepare_func,
280 ModuleUpdatedNotifyFunc update_func,
281 ModuleFrameDoneNotifyFunc frame_done_func,
282 ModuleAnimationDoneNotifyFunc anim_done_func,
287 lc = g_new0(LoadContext, 1);
289 lc->fatal_error_occurred = FALSE;
291 lc->prepare_func = prepare_func;
292 lc->update_func = update_func;
293 lc->notify_user_data = user_data;
295 lc->first_row_seen_in_chunk = -1;
296 lc->last_row_seen_in_chunk = -1;
297 lc->first_pass_seen_in_chunk = -1;
298 lc->last_pass_seen_in_chunk = -1;
299 lc->max_row_seen_in_chunk = -1;
301 /* Create the main PNG context struct */
304 lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
305 lc, /* error/warning callback data */
307 png_warning_callback);
309 if (lc->png_read_ptr == NULL) {
314 if (setjmp (lc->png_read_ptr->jmpbuf)) {
315 if (lc->png_info_ptr)
316 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
321 /* Create the auxiliary context struct */
323 lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
325 if (lc->png_info_ptr == NULL) {
326 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
331 png_set_progressive_read_fn(lc->png_read_ptr,
332 lc, /* callback data */
342 gdk_pixbuf__png_image_stop_load (gpointer context)
344 LoadContext* lc = context;
346 g_return_if_fail(lc != NULL);
348 gdk_pixbuf_unref(lc->pixbuf);
350 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
355 gdk_pixbuf__png_image_load_increment(gpointer context, guchar *buf, guint size)
357 LoadContext* lc = context;
359 g_return_val_if_fail(lc != NULL, FALSE);
362 lc->first_row_seen_in_chunk = -1;
363 lc->last_row_seen_in_chunk = -1;
364 lc->first_pass_seen_in_chunk = -1;
365 lc->last_pass_seen_in_chunk = -1;
366 lc->max_row_seen_in_chunk = -1;
368 /* Invokes our callbacks as needed */
369 if (setjmp (lc->png_read_ptr->jmpbuf)) {
372 png_process_data(lc->png_read_ptr, lc->png_info_ptr, buf, size);
375 if (lc->fatal_error_occurred)
378 if (lc->first_row_seen_in_chunk >= 0) {
379 /* We saw at least one row */
380 gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
382 g_assert(pass_diff >= 0);
384 if (pass_diff == 0) {
385 /* start and end row were in the same pass */
386 (lc->update_func)(lc->pixbuf, 0,
387 lc->first_row_seen_in_chunk,
389 (lc->last_row_seen_in_chunk -
390 lc->first_row_seen_in_chunk) + 1,
391 lc->notify_user_data);
392 } else if (pass_diff == 1) {
393 /* We have from the first row seen to
394 the end of the image (max row
395 seen), then from the top of the
396 image to the last row seen */
397 /* first row to end */
398 (lc->update_func)(lc->pixbuf, 0,
399 lc->first_row_seen_in_chunk,
401 (lc->max_row_seen_in_chunk -
402 lc->first_row_seen_in_chunk) + 1,
403 lc->notify_user_data);
404 /* top to last row */
405 (lc->update_func)(lc->pixbuf,
408 lc->last_row_seen_in_chunk + 1,
409 lc->notify_user_data);
411 /* We made at least one entire pass, so update the
413 (lc->update_func)(lc->pixbuf,
416 lc->max_row_seen_in_chunk + 1,
417 lc->notify_user_data);
425 /* Called at the start of the progressive load, once we have image info */
427 png_info_callback (png_structp png_read_ptr,
428 png_infop png_info_ptr)
431 png_uint_32 width, height;
433 gboolean have_alpha = FALSE;
434 gboolean failed = FALSE;
436 lc = png_get_progressive_ptr(png_read_ptr);
438 if (lc->fatal_error_occurred)
442 setup_png_transformations(lc->png_read_ptr,
445 &width, &height, &color_type);
448 lc->fatal_error_occurred = TRUE;
452 /* If we have alpha, set a flag */
453 if (color_type & PNG_COLOR_MASK_ALPHA)
456 lc->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
458 if (lc->pixbuf == NULL) {
459 /* Failed to allocate memory */
460 lc->fatal_error_occurred = TRUE;
464 /* Notify the client that we are ready to go */
466 if (lc->prepare_func)
467 (* lc->prepare_func) (lc->pixbuf, lc->notify_user_data);
472 /* Called for each row; note that you will get duplicate row numbers
473 for interlaced PNGs */
475 png_row_callback (png_structp png_read_ptr,
481 guchar* old_row = NULL;
483 lc = png_get_progressive_ptr(png_read_ptr);
485 if (lc->fatal_error_occurred)
488 if (lc->first_row_seen_in_chunk < 0) {
489 lc->first_row_seen_in_chunk = row_num;
490 lc->first_pass_seen_in_chunk = pass_num;
493 lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
494 lc->last_row_seen_in_chunk = row_num;
495 lc->last_pass_seen_in_chunk = pass_num;
497 old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
499 png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
502 /* Called after reading the entire image */
504 png_end_callback (png_structp png_read_ptr,
505 png_infop png_info_ptr)
509 lc = png_get_progressive_ptr(png_read_ptr);
511 if (lc->fatal_error_occurred)
516 png_error_callback(png_structp png_read_ptr,
517 png_const_charp error_msg)
521 lc = png_get_error_ptr(png_read_ptr);
523 lc->fatal_error_occurred = TRUE;
525 fprintf(stderr, "Fatal error loading PNG: %s\n", error_msg);
529 png_warning_callback(png_structp png_read_ptr,
530 png_const_charp warning_msg)
534 lc = png_get_error_ptr(png_read_ptr);
536 fprintf(stderr, "Warning loading PNG: %s\n", warning_msg);