]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-png.c
[quartz] Delete the typedef of GdkDevicePrivate
[~andy/gtk] / gdk-pixbuf / io-png.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /* GdkPixbuf library - PNG image loader
3  *
4  * Copyright (C) 1999 Mark Crichton
5  * Copyright (C) 1999 The Free Software Foundation
6  *
7  * Authors: Mark Crichton <crichton@gimp.org>
8  *          Federico Mena-Quintero <federico@gimp.org>
9  *
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.
14  *
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.
19  *
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.
24  */
25
26 #include "config.h"
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <png.h>
30 #include "gdk-pixbuf-private.h"
31 #include "gdk-pixbuf-io.h"
32
33 \f
34
35 static gboolean
36 setup_png_transformations(png_structp png_read_ptr, png_infop png_info_ptr,
37                           GError **error,
38                           png_uint_32* width_p, png_uint_32* height_p,
39                           int* color_type_p)
40 {
41         png_uint_32 width, height;
42         int bit_depth, color_type, interlace_type, compression_type, filter_type;
43         int channels;
44         
45         /* Get the image info */
46
47         /* Must check bit depth, since png_get_IHDR generates an 
48            FPE on bit_depth 0.
49         */
50         bit_depth = png_get_bit_depth (png_read_ptr, png_info_ptr);
51         if (bit_depth < 1 || bit_depth > 16) {
52                 g_set_error_literal (error,
53                                      GDK_PIXBUF_ERROR,
54                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
55                                      _("Bits per channel of PNG image is invalid."));
56                 return FALSE;
57         }
58         png_get_IHDR (png_read_ptr, png_info_ptr,
59                       &width, &height,
60                       &bit_depth,
61                       &color_type,
62                       &interlace_type,
63                       &compression_type,
64                       &filter_type);
65
66         /* set_expand() basically needs to be called unless
67            we are already in RGB/RGBA mode
68         */
69         if (color_type == PNG_COLOR_TYPE_PALETTE &&
70             bit_depth <= 8) {
71
72                 /* Convert indexed images to RGB */
73                 png_set_expand (png_read_ptr);
74
75         } else if (color_type == PNG_COLOR_TYPE_GRAY &&
76                    bit_depth < 8) {
77
78                 /* Convert grayscale to RGB */
79                 png_set_expand (png_read_ptr);
80
81         } else if (png_get_valid (png_read_ptr, 
82                                   png_info_ptr, PNG_INFO_tRNS)) {
83
84                 /* If we have transparency header, convert it to alpha
85                    channel */
86                 png_set_expand(png_read_ptr);
87                 
88         } else if (bit_depth < 8) {
89
90                 /* If we have < 8 scale it up to 8 */
91                 png_set_expand(png_read_ptr);
92
93
94                 /* Conceivably, png_set_packing() is a better idea;
95                  * God only knows how libpng works
96                  */
97         }
98
99         /* If we are 16-bit, convert to 8-bit */
100         if (bit_depth == 16) {
101                 png_set_strip_16(png_read_ptr);
102         }
103
104         /* If gray scale, convert to RGB */
105         if (color_type == PNG_COLOR_TYPE_GRAY ||
106             color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
107                 png_set_gray_to_rgb(png_read_ptr);
108         }
109         
110         /* If interlaced, handle that */
111         if (interlace_type != PNG_INTERLACE_NONE) {
112                 png_set_interlace_handling(png_read_ptr);
113         }
114         
115         /* Update the info the reflect our transformations */
116         png_read_update_info(png_read_ptr, png_info_ptr);
117         
118         png_get_IHDR (png_read_ptr, png_info_ptr,
119                       &width, &height,
120                       &bit_depth,
121                       &color_type,
122                       &interlace_type,
123                       &compression_type,
124                       &filter_type);
125
126         *width_p = width;
127         *height_p = height;
128         *color_type_p = color_type;
129         
130         /* Check that the new info is what we want */
131         
132         if (width == 0 || height == 0) {
133                 g_set_error_literal (error,
134                                      GDK_PIXBUF_ERROR,
135                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
136                                      _("Transformed PNG has zero width or height."));
137                 return FALSE;
138         }
139
140         if (bit_depth != 8) {
141                 g_set_error_literal (error,
142                                      GDK_PIXBUF_ERROR,
143                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
144                                      _("Bits per channel of transformed PNG is not 8."));
145                 return FALSE;
146         }
147
148         if ( ! (color_type == PNG_COLOR_TYPE_RGB ||
149                 color_type == PNG_COLOR_TYPE_RGB_ALPHA) ) {
150                 g_set_error_literal (error,
151                                      GDK_PIXBUF_ERROR,
152                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
153                                      _("Transformed PNG not RGB or RGBA."));
154                 return FALSE;
155         }
156
157         channels = png_get_channels(png_read_ptr, png_info_ptr);
158         if ( ! (channels == 3 || channels == 4) ) {
159                 g_set_error_literal (error,
160                                      GDK_PIXBUF_ERROR,
161                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
162                                      _("Transformed PNG has unsupported number of channels, must be 3 or 4."));
163                 return FALSE;
164         }
165         return TRUE;
166 }
167
168 static void
169 png_simple_error_callback(png_structp png_save_ptr,
170                           png_const_charp error_msg)
171 {
172         GError **error;
173         
174         error = png_get_error_ptr(png_save_ptr);
175
176         /* I don't trust libpng to call the error callback only once,
177          * so check for already-set error
178          */
179         if (error && *error == NULL) {
180                 g_set_error (error,
181                              GDK_PIXBUF_ERROR,
182                              GDK_PIXBUF_ERROR_FAILED,
183                              _("Fatal error in PNG image file: %s"),
184                              error_msg);
185         }
186
187         longjmp (png_save_ptr->jmpbuf, 1);
188 }
189
190 static void
191 png_simple_warning_callback(png_structp png_save_ptr,
192                             png_const_charp warning_msg)
193 {
194         /* Don't print anything; we should not be dumping junk to
195          * stderr, since that may be bad for some apps. If it's
196          * important enough to display, we need to add a GError
197          * **warning return location wherever we have an error return
198          * location.
199          */
200 }
201
202 static gboolean
203 png_text_to_pixbuf_option (png_text   text_ptr,
204                            gchar    **key,
205                            gchar    **value)
206 {
207         gboolean is_ascii = TRUE;
208         int i;
209
210         /* Avoid loading iconv if the text is plain ASCII */
211         for (i = 0; i < text_ptr.text_length; i++)
212                 if (text_ptr.text[i] & 0x80) {
213                         is_ascii = FALSE;
214                         break;
215                 }
216
217         if (is_ascii) {
218                 *value = g_strdup (text_ptr.text);
219         } else {
220                 *value = g_convert (text_ptr.text, -1,
221                                      "UTF-8", "ISO-8859-1",
222                                      NULL, NULL, NULL);
223         }
224
225         if (*value) {
226                 *key = g_strconcat ("tEXt::", text_ptr.key, NULL);
227                 return TRUE;
228         } else {
229                 g_warning ("Couldn't convert text chunk value to UTF-8.");
230                 *key = NULL;
231                 return FALSE;
232         }
233 }
234
235 static png_voidp
236 png_malloc_callback (png_structp o, png_size_t size)
237 {
238         return g_try_malloc (size);
239 }
240
241 static void
242 png_free_callback (png_structp o, png_voidp x)
243 {
244         g_free (x);
245 }
246
247 /* Shared library entry point */
248 static GdkPixbuf *
249 gdk_pixbuf__png_image_load (FILE *f, GError **error)
250 {
251         GdkPixbuf * volatile pixbuf = NULL;
252         png_structp png_ptr;
253         png_infop info_ptr;
254         png_textp text_ptr;
255         gint i, ctype;
256         png_uint_32 w, h;
257         png_bytepp volatile rows = NULL;
258         gint    num_texts;
259         gchar *key;
260         gchar *value;
261         gchar *icc_profile_base64;
262         const gchar *icc_profile_title;
263         const gchar *icc_profile;
264         png_uint_32 icc_profile_size;
265         guint32 retval;
266         gint compression_type;
267
268 #ifdef PNG_USER_MEM_SUPPORTED
269         png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
270                                             error,
271                                             png_simple_error_callback,
272                                             png_simple_warning_callback,
273                                             NULL, 
274                                             png_malloc_callback, 
275                                             png_free_callback);
276 #else
277         png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
278                                           error,
279                                           png_simple_error_callback,
280                                           png_simple_warning_callback);
281 #endif
282         if (!png_ptr)
283                 return NULL;
284
285         info_ptr = png_create_info_struct (png_ptr);
286         if (!info_ptr) {
287                 png_destroy_read_struct (&png_ptr, NULL, NULL);
288                 return NULL;
289         }
290
291         if (setjmp (png_ptr->jmpbuf)) {
292                 g_free (rows);
293
294                 if (pixbuf)
295                         g_object_unref (pixbuf);
296
297                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
298                 return NULL;
299         }
300
301         png_init_io (png_ptr, f);
302         png_read_info (png_ptr, info_ptr);
303
304         if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) {
305                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
306                 return NULL;
307         }
308         
309         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ctype & PNG_COLOR_MASK_ALPHA, 8, w, h);
310
311         if (!pixbuf) {
312                 if (error && *error == NULL) {
313                         g_set_error_literal (error,
314                                              GDK_PIXBUF_ERROR,
315                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
316                                              _("Insufficient memory to load PNG file"));
317                 }
318                 
319
320                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
321                 return NULL;
322         }
323
324         rows = g_new (png_bytep, h);
325
326         for (i = 0; i < h; i++)
327                 rows[i] = pixbuf->pixels + i * pixbuf->rowstride;
328
329         png_read_image (png_ptr, rows);
330         png_read_end (png_ptr, info_ptr);
331
332         if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) {
333                 for (i = 0; i < num_texts; i++) {
334                         png_text_to_pixbuf_option (text_ptr[i], &key, &value);
335                         gdk_pixbuf_set_option (pixbuf, key, value);
336                         g_free (key);
337                         g_free (value);
338                 }
339         }
340
341 #if defined(PNG_cHRM_SUPPORTED)
342         /* Extract embedded ICC profile */
343         retval = png_get_iCCP (png_ptr, info_ptr,
344                                (png_charpp) &icc_profile_title, &compression_type,
345                                (png_charpp) &icc_profile, (png_uint_32*) &icc_profile_size);
346         if (retval != 0) {
347                 icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size);
348                 gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
349                 g_free (icc_profile_base64);
350         }
351 #endif
352
353         g_free (rows);
354         png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
355
356         return pixbuf;
357 }
358
359 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
360    just allow you to change the error reporting. */
361 static void png_error_callback  (png_structp png_read_ptr,
362                                  png_const_charp error_msg);
363
364 static void png_warning_callback (png_structp png_read_ptr,
365                                   png_const_charp warning_msg);
366
367 /* Called at the start of the progressive load */
368 static void png_info_callback   (png_structp png_read_ptr,
369                                  png_infop   png_info_ptr);
370
371 /* Called for each row; note that you will get duplicate row numbers
372    for interlaced PNGs */
373 static void png_row_callback   (png_structp png_read_ptr,
374                                 png_bytep   new_row,
375                                 png_uint_32 row_num,
376                                 int pass_num);
377
378 /* Called after reading the entire image */
379 static void png_end_callback   (png_structp png_read_ptr,
380                                 png_infop   png_info_ptr);
381
382 typedef struct _LoadContext LoadContext;
383
384 struct _LoadContext {
385         png_structp png_read_ptr;
386         png_infop   png_info_ptr;
387
388         GdkPixbufModuleSizeFunc size_func;
389         GdkPixbufModulePreparedFunc prepare_func;
390         GdkPixbufModuleUpdatedFunc update_func;
391         gpointer notify_user_data;
392
393         GdkPixbuf* pixbuf;
394
395         /* row number of first row seen, or -1 if none yet seen */
396
397         gint first_row_seen_in_chunk;
398
399         /* pass number for the first row seen */
400
401         gint first_pass_seen_in_chunk;
402         
403         /* row number of last row seen */
404         gint last_row_seen_in_chunk;
405
406         gint last_pass_seen_in_chunk;
407
408         /* highest row number seen */
409         gint max_row_seen_in_chunk;
410         
411         guint fatal_error_occurred : 1;
412
413         GError **error;
414 };
415
416 static gpointer
417 gdk_pixbuf__png_image_begin_load (GdkPixbufModuleSizeFunc size_func,
418                                   GdkPixbufModulePreparedFunc prepare_func,
419                                   GdkPixbufModuleUpdatedFunc update_func,
420                                   gpointer user_data,
421                                   GError **error)
422 {
423         LoadContext* lc;
424         
425         lc = g_new0(LoadContext, 1);
426         
427         lc->fatal_error_occurred = FALSE;
428
429         lc->size_func = size_func;
430         lc->prepare_func = prepare_func;
431         lc->update_func = update_func;
432         lc->notify_user_data = user_data;
433
434         lc->first_row_seen_in_chunk = -1;
435         lc->last_row_seen_in_chunk = -1;
436         lc->first_pass_seen_in_chunk = -1;
437         lc->last_pass_seen_in_chunk = -1;
438         lc->max_row_seen_in_chunk = -1;
439         lc->error = error;
440         
441         /* Create the main PNG context struct */
442
443 #ifdef PNG_USER_MEM_SUPPORTED
444         lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
445                                                      lc, /* error/warning callback data */
446                                                      png_error_callback,
447                                                      png_warning_callback,
448                                                      NULL,
449                                                      png_malloc_callback,
450                                                      png_free_callback);
451 #else
452         lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
453                                                   lc, /* error/warning callback data */
454                                                   png_error_callback,
455                                                   png_warning_callback);
456 #endif
457         if (lc->png_read_ptr == NULL) {
458                 g_free(lc);
459                 /* error callback should have set the error */
460                 return NULL;
461         }
462         
463         if (setjmp (lc->png_read_ptr->jmpbuf)) {
464                 if (lc->png_info_ptr)
465                         png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
466                 g_free(lc);
467                 /* error callback should have set the error */
468                 return NULL;
469         }
470
471         /* Create the auxiliary context struct */
472
473         lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
474
475         if (lc->png_info_ptr == NULL) {
476                 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
477                 g_free(lc);
478                 /* error callback should have set the error */
479                 return NULL;
480         }
481
482         png_set_progressive_read_fn(lc->png_read_ptr,
483                                     lc, /* callback data */
484                                     png_info_callback,
485                                     png_row_callback,
486                                     png_end_callback);
487         
488
489         /* We don't want to keep modifying error after returning here,
490          * it may no longer be valid.
491          */
492         lc->error = NULL;
493         
494         return lc;
495 }
496
497 static gboolean
498 gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
499 {
500         LoadContext* lc = context;
501
502         g_return_val_if_fail(lc != NULL, TRUE);
503
504         /* FIXME this thing needs to report errors if
505          * we have unused image data
506          */
507         
508         if (lc->pixbuf)
509                 g_object_unref (lc->pixbuf);
510         
511         png_destroy_read_struct(&lc->png_read_ptr, &lc->png_info_ptr, NULL);
512         g_free(lc);
513
514         return TRUE;
515 }
516
517 static gboolean
518 gdk_pixbuf__png_image_load_increment(gpointer context,
519                                      const guchar *buf, guint size,
520                                      GError **error)
521 {
522         LoadContext* lc = context;
523
524         g_return_val_if_fail(lc != NULL, FALSE);
525
526         /* reset */
527         lc->first_row_seen_in_chunk = -1;
528         lc->last_row_seen_in_chunk = -1;
529         lc->first_pass_seen_in_chunk = -1;
530         lc->last_pass_seen_in_chunk = -1;
531         lc->max_row_seen_in_chunk = -1;
532         lc->error = error;
533         
534         /* Invokes our callbacks as needed */
535         if (setjmp (lc->png_read_ptr->jmpbuf)) {
536                 lc->error = NULL;
537                 return FALSE;
538         } else {
539                 png_process_data(lc->png_read_ptr, lc->png_info_ptr,
540                                  (guchar*) buf, size);
541         }
542
543         if (lc->fatal_error_occurred) {
544                 lc->error = NULL;
545                 return FALSE;
546         } else {
547                 if (lc->first_row_seen_in_chunk >= 0 && lc->update_func) {
548                         /* We saw at least one row */
549                         gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
550                         
551                         g_assert(pass_diff >= 0);
552                         
553                         if (pass_diff == 0) {
554                                 /* start and end row were in the same pass */
555                                 (lc->update_func)(lc->pixbuf, 0,
556                                                   lc->first_row_seen_in_chunk,
557                                                   lc->pixbuf->width,
558                                                   (lc->last_row_seen_in_chunk -
559                                                    lc->first_row_seen_in_chunk) + 1,
560                                                   lc->notify_user_data);
561                         } else if (pass_diff == 1) {
562                                 /* We have from the first row seen to
563                                    the end of the image (max row
564                                    seen), then from the top of the
565                                    image to the last row seen */
566                                 /* first row to end */
567                                 (lc->update_func)(lc->pixbuf, 0,
568                                                   lc->first_row_seen_in_chunk,
569                                                   lc->pixbuf->width,
570                                                   (lc->max_row_seen_in_chunk -
571                                                    lc->first_row_seen_in_chunk) + 1,
572                                                   lc->notify_user_data);
573                                 /* top to last row */
574                                 (lc->update_func)(lc->pixbuf,
575                                                   0, 0, 
576                                                   lc->pixbuf->width,
577                                                   lc->last_row_seen_in_chunk + 1,
578                                                   lc->notify_user_data);
579                         } else {
580                                 /* We made at least one entire pass, so update the
581                                    whole image */
582                                 (lc->update_func)(lc->pixbuf,
583                                                   0, 0, 
584                                                   lc->pixbuf->width,
585                                                   lc->max_row_seen_in_chunk + 1,
586                                                   lc->notify_user_data);
587                         }
588                 }
589
590                 lc->error = NULL;
591                 
592                 return TRUE;
593         }
594 }
595
596 /* Called at the start of the progressive load, once we have image info */
597 static void
598 png_info_callback   (png_structp png_read_ptr,
599                      png_infop   png_info_ptr)
600 {
601         LoadContext* lc;
602         png_uint_32 width, height;
603         png_textp png_text_ptr;
604         int i, num_texts;
605         int color_type;
606         gboolean have_alpha = FALSE;
607         gchar *icc_profile_base64;
608         const gchar *icc_profile_title;
609         const gchar *icc_profile;
610         png_uint_32 icc_profile_size;
611         guint32 retval;
612         gint compression_type;
613
614         lc = png_get_progressive_ptr(png_read_ptr);
615
616         if (lc->fatal_error_occurred)
617                 return;
618
619         if (!setup_png_transformations(lc->png_read_ptr,
620                                        lc->png_info_ptr,
621                                        lc->error,
622                                        &width, &height, &color_type)) {
623                 lc->fatal_error_occurred = TRUE;
624                 return;
625         }
626
627         /* If we have alpha, set a flag */
628         if (color_type & PNG_COLOR_MASK_ALPHA)
629                 have_alpha = TRUE;
630         
631         if (lc->size_func) {
632                 gint w = width;
633                 gint h = height;
634                 (* lc->size_func) (&w, &h, lc->notify_user_data);
635                 
636                 if (w == 0 || h == 0) {
637                         lc->fatal_error_occurred = TRUE;
638                         if (lc->error && *lc->error == NULL) {
639                                 g_set_error_literal (lc->error,
640                                                      GDK_PIXBUF_ERROR,
641                                                      GDK_PIXBUF_ERROR_FAILED,
642                                                      _("Transformed PNG has zero width or height."));
643                         }
644                         return;
645                 }
646         }
647
648         lc->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
649
650         if (lc->pixbuf == NULL) {
651                 /* Failed to allocate memory */
652                 lc->fatal_error_occurred = TRUE;
653                 if (lc->error && *lc->error == NULL) {
654                         g_set_error (lc->error,
655                                      GDK_PIXBUF_ERROR,
656                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
657                                      _("Insufficient memory to store a %ld by %ld image; try exiting some applications to reduce memory usage"),
658                                      width, height);
659                 }
660                 return;
661         }
662
663         /* Extract text chunks and attach them as pixbuf options */
664         
665         if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) {
666                 for (i = 0; i < num_texts; i++) {
667                         gchar *key, *value;
668
669                         if (png_text_to_pixbuf_option (png_text_ptr[i],
670                                                        &key, &value)) {
671                                 gdk_pixbuf_set_option (lc->pixbuf, key, value);
672                                 g_free (key);
673                                 g_free (value);
674                         }
675                 }
676         }
677
678 #if defined(PNG_cHRM_SUPPORTED)
679         /* Extract embedded ICC profile */
680         retval = png_get_iCCP (png_read_ptr, png_info_ptr,
681                                (png_charpp) &icc_profile_title, &compression_type,
682                                (png_charpp) &icc_profile, &icc_profile_size);
683         if (retval != 0) {
684                 icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, (gsize)icc_profile_size);
685                 gdk_pixbuf_set_option (lc->pixbuf, "icc-profile", icc_profile_base64);
686                 g_free (icc_profile_base64);
687         }
688 #endif
689
690         /* Notify the client that we are ready to go */
691
692         if (lc->prepare_func)
693                 (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
694
695         return;
696 }
697
698 /* Called for each row; note that you will get duplicate row numbers
699    for interlaced PNGs */
700 static void
701 png_row_callback   (png_structp png_read_ptr,
702                     png_bytep   new_row,
703                     png_uint_32 row_num,
704                     int pass_num)
705 {
706         LoadContext* lc;
707         guchar* old_row = NULL;
708
709         lc = png_get_progressive_ptr(png_read_ptr);
710
711         if (lc->fatal_error_occurred)
712                 return;
713
714         if (row_num >= lc->pixbuf->height) {
715                 lc->fatal_error_occurred = TRUE;
716                 if (lc->error && *lc->error == NULL) {
717                         g_set_error_literal (lc->error,
718                                              GDK_PIXBUF_ERROR,
719                                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
720                                              _("Fatal error reading PNG image file"));
721                 }
722                 return;
723         }
724
725         if (lc->first_row_seen_in_chunk < 0) {
726                 lc->first_row_seen_in_chunk = row_num;
727                 lc->first_pass_seen_in_chunk = pass_num;
728         }
729
730         lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
731         lc->last_row_seen_in_chunk = row_num;
732         lc->last_pass_seen_in_chunk = pass_num;
733         
734         old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
735
736         png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
737 }
738
739 /* Called after reading the entire image */
740 static void
741 png_end_callback   (png_structp png_read_ptr,
742                     png_infop   png_info_ptr)
743 {
744         LoadContext* lc;
745
746         lc = png_get_progressive_ptr(png_read_ptr);
747
748         if (lc->fatal_error_occurred)
749                 return;
750 }
751
752 static void
753 png_error_callback(png_structp png_read_ptr,
754                    png_const_charp error_msg)
755 {
756         LoadContext* lc;
757         
758         lc = png_get_error_ptr(png_read_ptr);
759         
760         lc->fatal_error_occurred = TRUE;
761
762         /* I don't trust libpng to call the error callback only once,
763          * so check for already-set error
764          */
765         if (lc->error && *lc->error == NULL) {
766                 g_set_error (lc->error,
767                              GDK_PIXBUF_ERROR,
768                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
769                              _("Fatal error reading PNG image file: %s"),
770                              error_msg);
771         }
772
773         longjmp (png_read_ptr->jmpbuf, 1);
774 }
775
776 static void
777 png_warning_callback (png_structp png_read_ptr,
778                       png_const_charp warning_msg)
779 {
780         LoadContext* lc;
781         
782         lc = png_get_error_ptr(png_read_ptr);
783
784         /* Don't print anything; we should not be dumping junk to
785          * stderr, since that may be bad for some apps. If it's
786          * important enough to display, we need to add a GError
787          * **warning return location wherever we have an error return
788          * location.
789          */
790 }
791
792
793 /* Save */
794
795 typedef struct {
796         GdkPixbufSaveFunc save_func;
797         gpointer user_data;
798         GError **error;
799 } SaveToFunctionIoPtr;
800
801 static void
802 png_save_to_callback_write_func (png_structp png_ptr,
803                                  png_bytep   data,
804                                  png_size_t  length)
805 {
806         SaveToFunctionIoPtr *ioptr = png_get_io_ptr (png_ptr);
807
808         if (!ioptr->save_func ((gchar *)data, length, ioptr->error, ioptr->user_data)) {
809                 /* If save_func has already set an error, which it
810                    should have done, this won't overwrite it. */
811                 png_error (png_ptr, "write function failed");
812         }
813 }
814
815 static void
816 png_save_to_callback_flush_func (png_structp png_ptr)
817 {
818         ;
819 }
820
821 static gboolean real_save_png (GdkPixbuf        *pixbuf, 
822                                gchar           **keys,
823                                gchar           **values,
824                                GError          **error,
825                                gboolean          to_callback,
826                                FILE             *f,
827                                GdkPixbufSaveFunc save_func,
828                                gpointer          user_data)
829 {
830        png_structp png_ptr = NULL;
831        png_infop info_ptr;
832        png_textp text_ptr = NULL;
833        guchar *ptr;
834        guchar *pixels;
835        int y;
836        int i;
837        png_bytep row_ptr;
838        png_color_8 sig_bit;
839        int w, h, rowstride;
840        int has_alpha;
841        int bpc;
842        int num_keys;
843        int compression = -1;
844        gboolean success = TRUE;
845        guchar *icc_profile = NULL;
846        gsize icc_profile_size = 0;
847        SaveToFunctionIoPtr to_callback_ioptr;
848
849        num_keys = 0;
850
851        if (keys && *keys) {
852                gchar **kiter = keys;
853                gchar **viter = values;
854
855                while (*kiter) {
856                        if (strncmp (*kiter, "tEXt::", 6) == 0) {
857                                gchar  *key = *kiter + 6;
858                                int     len = strlen (key);
859                                if (len <= 1 || len > 79) {
860                                        g_set_error_literal (error,
861                                                             GDK_PIXBUF_ERROR,
862                                                             GDK_PIXBUF_ERROR_BAD_OPTION,
863                                                             _("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
864                                        success = FALSE;
865                                        goto cleanup;
866                                }
867                                for (i = 0; i < len; i++) {
868                                        if ((guchar) key[i] > 127) {
869                                                g_set_error_literal (error,
870                                                                     GDK_PIXBUF_ERROR,
871                                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
872                                                                     _("Keys for PNG text chunks must be ASCII characters."));
873                                                success = FALSE;
874                                                goto cleanup;
875                                        }
876                                }
877                                num_keys++;
878                        } else if (strcmp (*kiter, "icc-profile") == 0) {
879                                /* decode from base64 */
880                                icc_profile = g_base64_decode (*viter, &icc_profile_size);
881                                if (icc_profile_size < 127) {
882                                        /* This is a user-visible error */
883                                        g_set_error (error,
884                                                     GDK_PIXBUF_ERROR,
885                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
886                                                     _("Color profile has invalid length %d."),
887                                                     (gint)icc_profile_size);
888                                        success = FALSE;
889                                        goto cleanup;
890                                }
891                        } else if (strcmp (*kiter, "compression") == 0) {
892                                char *endptr = NULL;
893                                compression = strtol (*viter, &endptr, 10);
894
895                                if (endptr == *viter) {
896                                        g_set_error (error,
897                                                     GDK_PIXBUF_ERROR,
898                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
899                                                     _("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
900                                                     *viter);
901                                        success = FALSE;
902                                        goto cleanup;
903                                }
904                                if (compression < 0 || compression > 9) {
905                                        /* This is a user-visible error;
906                                         * lets people skip the range-checking
907                                         * in their app.
908                                         */
909                                        g_set_error (error,
910                                                     GDK_PIXBUF_ERROR,
911                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
912                                                     _("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
913                                                     compression);
914                                        success = FALSE;
915                                        goto cleanup;
916                                }
917                        } else {
918                                g_warning ("Unrecognized parameter (%s) passed to PNG saver.", *kiter);
919                        }
920
921                        ++kiter;
922                        ++viter;
923                }
924        }
925
926        if (num_keys > 0) {
927                text_ptr = g_new0 (png_text, num_keys);
928                for (i = 0; i < num_keys; i++) {
929                        text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
930                        text_ptr[i].key  = keys[i] + 6;
931                        text_ptr[i].text = g_convert (values[i], -1, 
932                                                      "ISO-8859-1", "UTF-8", 
933                                                      NULL, &text_ptr[i].text_length, 
934                                                      NULL);
935
936 #ifdef PNG_iTXt_SUPPORTED 
937                        if (!text_ptr[i].text) {
938                                text_ptr[i].compression = PNG_ITXT_COMPRESSION_NONE;
939                                text_ptr[i].text = g_strdup (values[i]);
940                                text_ptr[i].text_length = 0;
941                                text_ptr[i].itxt_length = strlen (text_ptr[i].text);
942                                text_ptr[i].lang = NULL;
943                                text_ptr[i].lang_key = NULL;
944                        }
945 #endif
946
947                        if (!text_ptr[i].text) {
948                                g_set_error (error,
949                                             GDK_PIXBUF_ERROR,
950                                             GDK_PIXBUF_ERROR_BAD_OPTION,
951                                             _("Value for PNG text chunk %s cannot be converted to ISO-8859-1 encoding."), keys[i] + 6);
952                                num_keys = i;
953                                for (i = 0; i < num_keys; i++)
954                                        g_free (text_ptr[i].text);
955                                g_free (text_ptr);
956                                return FALSE;
957                        }
958                }
959        }
960
961        bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
962        w = gdk_pixbuf_get_width (pixbuf);
963        h = gdk_pixbuf_get_height (pixbuf);
964        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
965        has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
966        pixels = gdk_pixbuf_get_pixels (pixbuf);
967
968        png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
969                                           error,
970                                           png_simple_error_callback,
971                                           png_simple_warning_callback);
972        if (png_ptr == NULL) {
973                success = FALSE;
974                goto cleanup;
975        }
976
977        info_ptr = png_create_info_struct (png_ptr);
978        if (info_ptr == NULL) {
979                success = FALSE;
980                goto cleanup;
981        }
982        if (setjmp (png_ptr->jmpbuf)) {
983                success = FALSE;
984                goto cleanup;
985        }
986
987        if (num_keys > 0) {
988                png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
989        }
990
991        if (to_callback) {
992                to_callback_ioptr.save_func = save_func;
993                to_callback_ioptr.user_data = user_data;
994                to_callback_ioptr.error = error;
995                png_set_write_fn (png_ptr, &to_callback_ioptr,
996                                  png_save_to_callback_write_func,
997                                  png_save_to_callback_flush_func);
998        } else {
999                png_init_io (png_ptr, f);
1000        }
1001
1002        if (compression >= 0)
1003                png_set_compression_level (png_ptr, compression);
1004
1005 #if defined(PNG_iCCP_SUPPORTED)
1006         /* the proper ICC profile title is encoded in the profile */
1007         if (icc_profile != NULL) {
1008                 png_set_iCCP (png_ptr, info_ptr,
1009                               "ICC profile", PNG_COMPRESSION_TYPE_BASE,
1010                               (gchar*) icc_profile, icc_profile_size);
1011         }
1012 #endif
1013
1014        if (has_alpha) {
1015                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
1016                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
1017                              PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
1018        } else {
1019                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
1020                              PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
1021                              PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
1022        }
1023        sig_bit.red = bpc;
1024        sig_bit.green = bpc;
1025        sig_bit.blue = bpc;
1026        sig_bit.alpha = bpc;
1027        png_set_sBIT (png_ptr, info_ptr, &sig_bit);
1028        png_write_info (png_ptr, info_ptr);
1029        png_set_shift (png_ptr, &sig_bit);
1030        png_set_packing (png_ptr);
1031
1032        ptr = pixels;
1033        for (y = 0; y < h; y++) {
1034                row_ptr = (png_bytep)ptr;
1035                png_write_rows (png_ptr, &row_ptr, 1);
1036                ptr += rowstride;
1037        }
1038
1039        png_write_end (png_ptr, info_ptr);
1040
1041 cleanup:
1042         if (png_ptr != NULL)
1043                 png_destroy_write_struct (&png_ptr, &info_ptr);
1044
1045         g_free (icc_profile);
1046
1047         if (text_ptr != NULL) {
1048                 for (i = 0; i < num_keys; i++)
1049                         g_free (text_ptr[i].text);
1050                 g_free (text_ptr);
1051         }
1052
1053        return success;
1054 }
1055
1056 static gboolean
1057 gdk_pixbuf__png_image_save (FILE          *f, 
1058                             GdkPixbuf     *pixbuf, 
1059                             gchar        **keys,
1060                             gchar        **values,
1061                             GError       **error)
1062 {
1063         return real_save_png (pixbuf, keys, values, error,
1064                               FALSE, f, NULL, NULL);
1065 }
1066
1067 static gboolean
1068 gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc   save_func,
1069                                         gpointer            user_data,
1070                                         GdkPixbuf          *pixbuf, 
1071                                         gchar             **keys,
1072                                         gchar             **values,
1073                                         GError            **error)
1074 {
1075         return real_save_png (pixbuf, keys, values, error,
1076                               TRUE, NULL, save_func, user_data);
1077 }
1078
1079 #ifndef INCLUDE_png
1080 #define MODULE_ENTRY(function) G_MODULE_EXPORT void function
1081 #else
1082 #define MODULE_ENTRY(function) void _gdk_pixbuf__png_ ## function
1083 #endif
1084
1085 MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
1086 {
1087         module->load = gdk_pixbuf__png_image_load;
1088         module->begin_load = gdk_pixbuf__png_image_begin_load;
1089         module->stop_load = gdk_pixbuf__png_image_stop_load;
1090         module->load_increment = gdk_pixbuf__png_image_load_increment;
1091         module->save = gdk_pixbuf__png_image_save;
1092         module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
1093 }
1094
1095 MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
1096 {
1097         static GdkPixbufModulePattern signature[] = {
1098                 { "\x89PNG\r\n\x1a\x0a", NULL, 100 },
1099                 { NULL, NULL, 0 }
1100         };
1101         static gchar * mime_types[] = {
1102                 "image/png",
1103                 NULL
1104         };
1105         static gchar * extensions[] = {
1106                 "png",
1107                 NULL
1108         };
1109
1110         info->name = "png";
1111         info->signature = signature;
1112         info->description = N_("The PNG image format");
1113         info->mime_types = mime_types;
1114         info->extensions = extensions;
1115         info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
1116         info->license = "LGPL";
1117 }