]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-png.c
Add a cast to avoid compiler warnings.
[~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 (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 (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 (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 (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 (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         if (text_ptr.text_length > 0) {
208                 *value = g_convert (text_ptr.text, -1, 
209                                     "UTF-8", "ISO-8859-1", 
210                                     NULL, NULL, NULL);
211         }
212         else {
213                 *value = g_strdup (text_ptr.text);
214         }
215         if (*value) {
216                 *key = g_strconcat ("tEXt::", text_ptr.key, NULL);
217                 return TRUE;
218         } else {
219                 g_warning ("Couldn't convert text chunk value to UTF-8.");
220                 *key = NULL;
221                 return FALSE;
222         }
223 }
224
225 static png_voidp
226 png_malloc_callback (png_structp o, png_size_t size)
227 {
228         return g_try_malloc (size);
229 }
230
231 static void
232 png_free_callback (png_structp o, png_voidp x)
233 {
234         g_free (x);
235 }
236
237 /* Shared library entry point */
238 static GdkPixbuf *
239 gdk_pixbuf__png_image_load (FILE *f, GError **error)
240 {
241         GdkPixbuf * volatile pixbuf = NULL;
242         png_structp png_ptr;
243         png_infop info_ptr;
244         png_textp text_ptr;
245         gint i, ctype;
246         png_uint_32 w, h;
247         png_bytepp volatile rows = NULL;
248         gint    num_texts;
249         gchar *key;
250         gchar *value;
251
252 #ifdef PNG_USER_MEM_SUPPORTED
253         png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
254                                             error,
255                                             png_simple_error_callback,
256                                             png_simple_warning_callback,
257                                             NULL, 
258                                             png_malloc_callback, 
259                                             png_free_callback);
260 #else
261         png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
262                                           error,
263                                           png_simple_error_callback,
264                                           png_simple_warning_callback);
265 #endif
266         if (!png_ptr)
267                 return NULL;
268
269         info_ptr = png_create_info_struct (png_ptr);
270         if (!info_ptr) {
271                 png_destroy_read_struct (&png_ptr, NULL, NULL);
272                 return NULL;
273         }
274
275         if (setjmp (png_ptr->jmpbuf)) {
276                 if (rows)
277                         g_free (rows);
278
279                 if (pixbuf)
280                         g_object_unref (pixbuf);
281
282                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
283                 return NULL;
284         }
285
286         png_init_io (png_ptr, f);
287         png_read_info (png_ptr, info_ptr);
288
289         if (!setup_png_transformations(png_ptr, info_ptr, error, &w, &h, &ctype)) {
290                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
291                 return NULL;
292         }
293         
294         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ctype & PNG_COLOR_MASK_ALPHA, 8, w, h);
295
296         if (!pixbuf) {
297                 if (error && *error == NULL) {
298                         g_set_error (error,
299                                      GDK_PIXBUF_ERROR,
300                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
301                                      _("Insufficient memory to load PNG file"));
302                 }
303                 
304
305                 png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
306                 return NULL;
307         }
308
309         rows = g_new (png_bytep, h);
310
311         for (i = 0; i < h; i++)
312                 rows[i] = pixbuf->pixels + i * pixbuf->rowstride;
313
314         png_read_image (png_ptr, rows);
315         png_read_end (png_ptr, info_ptr);
316
317         if (png_get_text (png_ptr, info_ptr, &text_ptr, &num_texts)) {
318                 for (i = 0; i < num_texts; i++) {
319                         png_text_to_pixbuf_option (text_ptr[i], &key, &value);
320                         gdk_pixbuf_set_option (pixbuf, key, value);
321                         g_free (key);
322                         g_free (value);
323                 }
324         }
325
326         g_free (rows);
327         png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
328
329         return pixbuf;
330 }
331
332 /* I wish these avoided the setjmp()/longjmp() crap in libpng instead
333    just allow you to change the error reporting. */
334 static void png_error_callback  (png_structp png_read_ptr,
335                                  png_const_charp error_msg);
336
337 static void png_warning_callback(png_structp png_read_ptr,
338                                  png_const_charp warning_msg);
339
340 /* Called at the start of the progressive load */
341 static void png_info_callback   (png_structp png_read_ptr,
342                                  png_infop   png_info_ptr);
343
344 /* Called for each row; note that you will get duplicate row numbers
345    for interlaced PNGs */
346 static void png_row_callback   (png_structp png_read_ptr,
347                                 png_bytep   new_row,
348                                 png_uint_32 row_num,
349                                 int pass_num);
350
351 /* Called after reading the entire image */
352 static void png_end_callback   (png_structp png_read_ptr,
353                                 png_infop   png_info_ptr);
354
355 typedef struct _LoadContext LoadContext;
356
357 struct _LoadContext {
358         png_structp png_read_ptr;
359         png_infop   png_info_ptr;
360
361         GdkPixbufModuleSizeFunc size_func;
362         GdkPixbufModulePreparedFunc prepare_func;
363         GdkPixbufModuleUpdatedFunc update_func;
364         gpointer notify_user_data;
365
366         GdkPixbuf* pixbuf;
367
368         /* row number of first row seen, or -1 if none yet seen */
369
370         gint first_row_seen_in_chunk;
371
372         /* pass number for the first row seen */
373
374         gint first_pass_seen_in_chunk;
375         
376         /* row number of last row seen */
377         gint last_row_seen_in_chunk;
378
379         gint last_pass_seen_in_chunk;
380
381         /* highest row number seen */
382         gint max_row_seen_in_chunk;
383         
384         guint fatal_error_occurred : 1;
385
386         GError **error;
387 };
388
389 static gpointer
390 gdk_pixbuf__png_image_begin_load (GdkPixbufModuleSizeFunc size_func,
391                                   GdkPixbufModulePreparedFunc prepare_func,
392                                   GdkPixbufModuleUpdatedFunc update_func,
393                                   gpointer user_data,
394                                   GError **error)
395 {
396         LoadContext* lc;
397         
398         lc = g_new0(LoadContext, 1);
399         
400         lc->fatal_error_occurred = FALSE;
401
402         lc->size_func = size_func;
403         lc->prepare_func = prepare_func;
404         lc->update_func = update_func;
405         lc->notify_user_data = user_data;
406
407         lc->first_row_seen_in_chunk = -1;
408         lc->last_row_seen_in_chunk = -1;
409         lc->first_pass_seen_in_chunk = -1;
410         lc->last_pass_seen_in_chunk = -1;
411         lc->max_row_seen_in_chunk = -1;
412         lc->error = error;
413         
414         /* Create the main PNG context struct */
415
416 #ifdef PNG_USER_MEM_SUPPORTED
417         lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
418                                                      lc, /* error/warning callback data */
419                                                      png_error_callback,
420                                                      png_warning_callback,
421                                                      NULL,
422                                                      png_malloc_callback,
423                                                      png_free_callback);
424 #else
425         lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
426                                                   lc, /* error/warning callback data */
427                                                   png_error_callback,
428                                                   png_warning_callback);
429 #endif
430         if (lc->png_read_ptr == NULL) {
431                 g_free(lc);
432                 /* error callback should have set the error */
433                 return NULL;
434         }
435         
436         if (setjmp (lc->png_read_ptr->jmpbuf)) {
437                 if (lc->png_info_ptr)
438                         png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
439                 g_free(lc);
440                 /* error callback should have set the error */
441                 return NULL;
442         }
443
444         /* Create the auxiliary context struct */
445
446         lc->png_info_ptr = png_create_info_struct(lc->png_read_ptr);
447
448         if (lc->png_info_ptr == NULL) {
449                 png_destroy_read_struct(&lc->png_read_ptr, NULL, NULL);
450                 g_free(lc);
451                 /* error callback should have set the error */
452                 return NULL;
453         }
454
455         png_set_progressive_read_fn(lc->png_read_ptr,
456                                     lc, /* callback data */
457                                     png_info_callback,
458                                     png_row_callback,
459                                     png_end_callback);
460         
461
462         /* We don't want to keep modifying error after returning here,
463          * it may no longer be valid.
464          */
465         lc->error = NULL;
466         
467         return lc;
468 }
469
470 static gboolean
471 gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
472 {
473         LoadContext* lc = context;
474
475         g_return_val_if_fail(lc != NULL, TRUE);
476
477         /* FIXME this thing needs to report errors if
478          * we have unused image data
479          */
480         
481         if (lc->pixbuf)
482                 g_object_unref (lc->pixbuf);
483         
484         png_destroy_read_struct(&lc->png_read_ptr, &lc->png_info_ptr, NULL);
485         g_free(lc);
486
487         return TRUE;
488 }
489
490 static gboolean
491 gdk_pixbuf__png_image_load_increment(gpointer context,
492                                      const guchar *buf, guint size,
493                                      GError **error)
494 {
495         LoadContext* lc = context;
496
497         g_return_val_if_fail(lc != NULL, FALSE);
498
499         /* reset */
500         lc->first_row_seen_in_chunk = -1;
501         lc->last_row_seen_in_chunk = -1;
502         lc->first_pass_seen_in_chunk = -1;
503         lc->last_pass_seen_in_chunk = -1;
504         lc->max_row_seen_in_chunk = -1;
505         lc->error = error;
506         
507         /* Invokes our callbacks as needed */
508         if (setjmp (lc->png_read_ptr->jmpbuf)) {
509                 lc->error = NULL;
510                 return FALSE;
511         } else {
512                 png_process_data(lc->png_read_ptr, lc->png_info_ptr,
513                                  (guchar*) buf, size);
514         }
515
516         if (lc->fatal_error_occurred) {
517                 lc->error = NULL;
518                 return FALSE;
519         } else {
520                 if (lc->first_row_seen_in_chunk >= 0) {
521                         /* We saw at least one row */
522                         gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
523                         
524                         g_assert(pass_diff >= 0);
525                         
526                         if (pass_diff == 0) {
527                                 /* start and end row were in the same pass */
528                                 (lc->update_func)(lc->pixbuf, 0,
529                                                   lc->first_row_seen_in_chunk,
530                                                   lc->pixbuf->width,
531                                                   (lc->last_row_seen_in_chunk -
532                                                    lc->first_row_seen_in_chunk) + 1,
533                                                   lc->notify_user_data);
534                         } else if (pass_diff == 1) {
535                                 /* We have from the first row seen to
536                                    the end of the image (max row
537                                    seen), then from the top of the
538                                    image to the last row seen */
539                                 /* first row to end */
540                                 (lc->update_func)(lc->pixbuf, 0,
541                                                   lc->first_row_seen_in_chunk,
542                                                   lc->pixbuf->width,
543                                                   (lc->max_row_seen_in_chunk -
544                                                    lc->first_row_seen_in_chunk) + 1,
545                                                   lc->notify_user_data);
546                                 /* top to last row */
547                                 (lc->update_func)(lc->pixbuf,
548                                                   0, 0, 
549                                                   lc->pixbuf->width,
550                                                   lc->last_row_seen_in_chunk + 1,
551                                                   lc->notify_user_data);
552                         } else {
553                                 /* We made at least one entire pass, so update the
554                                    whole image */
555                                 (lc->update_func)(lc->pixbuf,
556                                                   0, 0, 
557                                                   lc->pixbuf->width,
558                                                   lc->max_row_seen_in_chunk + 1,
559                                                   lc->notify_user_data);
560                         }
561                 }
562
563                 lc->error = NULL;
564                 
565                 return TRUE;
566         }
567 }
568
569 /* Called at the start of the progressive load, once we have image info */
570 static void
571 png_info_callback   (png_structp png_read_ptr,
572                      png_infop   png_info_ptr)
573 {
574         LoadContext* lc;
575         png_uint_32 width, height;
576         png_textp png_text_ptr;
577         int i, num_texts;
578         int color_type;
579         gboolean have_alpha = FALSE;
580         
581         lc = png_get_progressive_ptr(png_read_ptr);
582
583         if (lc->fatal_error_occurred)
584                 return;
585
586         if (!setup_png_transformations(lc->png_read_ptr,
587                                        lc->png_info_ptr,
588                                        lc->error,
589                                        &width, &height, &color_type)) {
590                 lc->fatal_error_occurred = TRUE;
591                 return;
592         }
593
594         /* If we have alpha, set a flag */
595         if (color_type & PNG_COLOR_MASK_ALPHA)
596                 have_alpha = TRUE;
597         
598         if (lc->size_func) {
599                 gint w = width;
600                 gint h = height;
601                 (* lc->size_func) (&w, &h, lc->notify_user_data);
602                 
603                 if (w == 0 || h == 0) {
604                         lc->fatal_error_occurred = TRUE;
605                         return;
606                 }
607         }
608
609         lc->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, have_alpha, 8, width, height);
610
611         if (lc->pixbuf == NULL) {
612                 /* Failed to allocate memory */
613                 lc->fatal_error_occurred = TRUE;
614                 if (lc->error && *lc->error == NULL) {
615                         g_set_error (lc->error,
616                                      GDK_PIXBUF_ERROR,
617                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
618                                      _("Insufficient memory to store a %ld by %ld image; try exiting some applications to reduce memory usage"),
619                                      width, height);
620                 }
621                 return;
622         }
623
624         /* Extract text chunks and attach them as pixbuf options */
625         
626         if (png_get_text (png_read_ptr, png_info_ptr, &png_text_ptr, &num_texts)) {
627                 for (i = 0; i < num_texts; i++) {
628                         gchar *key, *value;
629
630                         if (png_text_to_pixbuf_option (png_text_ptr[i],
631                                                        &key, &value)) {
632                                 gdk_pixbuf_set_option (lc->pixbuf, key, value);
633                                 g_free (key);
634                                 g_free (value);
635                         }
636                 }
637         }
638
639         /* Notify the client that we are ready to go */
640
641         if (lc->prepare_func)
642                 (* lc->prepare_func) (lc->pixbuf, NULL, lc->notify_user_data);
643         
644         return;
645 }
646
647 /* Called for each row; note that you will get duplicate row numbers
648    for interlaced PNGs */
649 static void
650 png_row_callback   (png_structp png_read_ptr,
651                     png_bytep   new_row,
652                     png_uint_32 row_num,
653                     int pass_num)
654 {
655         LoadContext* lc;
656         guchar* old_row = NULL;
657
658         lc = png_get_progressive_ptr(png_read_ptr);
659
660         if (lc->fatal_error_occurred)
661                 return;
662
663         if (row_num < 0 || row_num >= lc->pixbuf->height) {
664                 lc->fatal_error_occurred = TRUE;
665                 if (lc->error && *lc->error == NULL) {
666                         g_set_error (lc->error,
667                                      GDK_PIXBUF_ERROR,
668                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
669                                      _("Fatal error reading PNG image file"));
670                 }
671                 return;
672         }
673
674         if (lc->first_row_seen_in_chunk < 0) {
675                 lc->first_row_seen_in_chunk = row_num;
676                 lc->first_pass_seen_in_chunk = pass_num;
677         }
678
679         lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
680         lc->last_row_seen_in_chunk = row_num;
681         lc->last_pass_seen_in_chunk = pass_num;
682         
683         old_row = lc->pixbuf->pixels + (row_num * lc->pixbuf->rowstride);
684
685         png_progressive_combine_row(lc->png_read_ptr, old_row, new_row);
686 }
687
688 /* Called after reading the entire image */
689 static void
690 png_end_callback   (png_structp png_read_ptr,
691                     png_infop   png_info_ptr)
692 {
693         LoadContext* lc;
694
695         lc = png_get_progressive_ptr(png_read_ptr);
696
697         if (lc->fatal_error_occurred)
698                 return;
699 }
700
701 static void
702 png_error_callback(png_structp png_read_ptr,
703                    png_const_charp error_msg)
704 {
705         LoadContext* lc;
706         
707         lc = png_get_error_ptr(png_read_ptr);
708         
709         lc->fatal_error_occurred = TRUE;
710
711         /* I don't trust libpng to call the error callback only once,
712          * so check for already-set error
713          */
714         if (lc->error && *lc->error == NULL) {
715                 g_set_error (lc->error,
716                              GDK_PIXBUF_ERROR,
717                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
718                              _("Fatal error reading PNG image file: %s"),
719                              error_msg);
720         }
721
722         longjmp (png_read_ptr->jmpbuf, 1);
723 }
724
725 static void
726 png_warning_callback(png_structp png_read_ptr,
727                      png_const_charp warning_msg)
728 {
729         LoadContext* lc;
730         
731         lc = png_get_error_ptr(png_read_ptr);
732
733         /* Don't print anything; we should not be dumping junk to
734          * stderr, since that may be bad for some apps. If it's
735          * important enough to display, we need to add a GError
736          * **warning return location wherever we have an error return
737          * location.
738          */
739 }
740
741
742 /* Save */
743
744 typedef struct {
745         GdkPixbufSaveFunc save_func;
746         gpointer user_data;
747         GError **error;
748 } SaveToFunctionIoPtr;
749
750 static void
751 png_save_to_callback_write_func (png_structp png_ptr,
752                                  png_bytep   data,
753                                  png_size_t  length)
754 {
755         SaveToFunctionIoPtr *ioptr = png_get_io_ptr (png_ptr);
756
757         if (!ioptr->save_func ((gchar *)data, length, ioptr->error, ioptr->user_data)) {
758                 /* If save_func has already set an error, which it
759                    should have done, this won't overwrite it. */
760                 png_error (png_ptr, "write function failed");
761         }
762 }
763
764 static void
765 png_save_to_callback_flush_func (png_structp png_ptr)
766 {
767         ;
768 }
769
770 static gboolean real_save_png (GdkPixbuf        *pixbuf, 
771                                gchar           **keys,
772                                gchar           **values,
773                                GError          **error,
774                                gboolean          to_callback,
775                                FILE             *f,
776                                GdkPixbufSaveFunc save_func,
777                                gpointer          user_data)
778 {
779        png_structp png_ptr;
780        png_infop info_ptr;
781        png_textp text_ptr = NULL;
782        guchar *ptr;
783        guchar *pixels;
784        int y;
785        int i;
786        png_bytep row_ptr;
787        png_color_8 sig_bit;
788        int w, h, rowstride;
789        int has_alpha;
790        int bpc;
791        int num_keys;
792        int compression = -1;
793        gboolean success = TRUE;
794        SaveToFunctionIoPtr to_callback_ioptr;
795
796        num_keys = 0;
797
798        if (keys && *keys) {
799                gchar **kiter = keys;
800                gchar **viter = values;
801
802                while (*kiter) {
803                        if (strncmp (*kiter, "tEXt::", 6) == 0) {
804                                gchar  *key = *kiter + 6;
805                                int     len = strlen (key);
806                                if (len <= 1 || len > 79) {
807                                        g_set_error (error,
808                                                     GDK_PIXBUF_ERROR,
809                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
810                                                     _("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
811                                        return FALSE;
812                                }
813                                for (i = 0; i < len; i++) {
814                                        if ((guchar) key[i] > 127) {
815                                                g_set_error (error,
816                                                             GDK_PIXBUF_ERROR,
817                                                             GDK_PIXBUF_ERROR_BAD_OPTION,
818                                                             _("Keys for PNG text chunks must be ASCII characters."));
819                                                return FALSE;
820                                        }
821                                }
822                                num_keys++;
823                        } else if (strcmp (*kiter, "compression") == 0) {
824                                char *endptr = NULL;
825                                compression = strtol (*viter, &endptr, 10);
826
827                                if (endptr == *viter) {
828                                        g_set_error (error,
829                                                     GDK_PIXBUF_ERROR,
830                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
831                                                     _("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
832                                                     *viter);
833                                        return FALSE;
834                                }
835                                if (compression < 0 || compression > 9) {
836                                        /* This is a user-visible error;
837                                         * lets people skip the range-checking
838                                         * in their app.
839                                         */
840                                        g_set_error (error,
841                                                     GDK_PIXBUF_ERROR,
842                                                     GDK_PIXBUF_ERROR_BAD_OPTION,
843                                                     _("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
844                                                     compression);
845                                        return FALSE;
846                                }
847                        } else {
848                                g_warning ("Bad option name '%s' passed to PNG saver",
849                                           *kiter);
850                                return FALSE;
851                        }
852
853                        ++kiter;
854                        ++viter;
855                }
856        }
857
858        if (num_keys > 0) {
859                text_ptr = g_new0 (png_text, num_keys);
860                for (i = 0; i < num_keys; i++) {
861                        text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
862                        text_ptr[i].key  = keys[i] + 6;
863                        text_ptr[i].text = g_convert (values[i], -1, 
864                                                      "ISO-8859-1", "UTF-8", 
865                                                      NULL, &text_ptr[i].text_length, 
866                                                      NULL);
867
868 #ifdef PNG_iTXt_SUPPORTED 
869                        if (!text_ptr[i].text) {
870                                text_ptr[i].compression = PNG_ITXT_COMPRESSION_NONE;
871                                text_ptr[i].text = g_strdup (values[i]);
872                                text_ptr[i].text_length = 0;
873                                text_ptr[i].itxt_length = strlen (text_ptr[i].text);
874                                text_ptr[i].lang = NULL;
875                                text_ptr[i].lang_key = NULL;
876                        }
877 #endif
878
879                        if (!text_ptr[i].text) {
880                                g_set_error (error,
881                                             GDK_PIXBUF_ERROR,
882                                             GDK_PIXBUF_ERROR_BAD_OPTION,
883                                             _("Value for PNG text chunk %s cannot be converted to ISO-8859-1 encoding."), keys[i] + 6);
884                                num_keys = i;
885                                for (i = 0; i < num_keys; i++)
886                                        g_free (text_ptr[i].text);
887                                g_free (text_ptr);
888                                return FALSE;
889                        }
890                }
891        }
892
893        bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
894        w = gdk_pixbuf_get_width (pixbuf);
895        h = gdk_pixbuf_get_height (pixbuf);
896        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
897        has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
898        pixels = gdk_pixbuf_get_pixels (pixbuf);
899
900        png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
901                                           error,
902                                           png_simple_error_callback,
903                                           png_simple_warning_callback);
904
905        g_return_val_if_fail (png_ptr != NULL, FALSE);
906
907        info_ptr = png_create_info_struct (png_ptr);
908        if (info_ptr == NULL) {
909                success = FALSE;
910                goto cleanup;
911        }
912        if (setjmp (png_ptr->jmpbuf)) {
913                success = FALSE;
914                goto cleanup;
915        }
916
917        if (num_keys > 0) {
918                png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
919        }
920
921        if (to_callback) {
922                to_callback_ioptr.save_func = save_func;
923                to_callback_ioptr.user_data = user_data;
924                to_callback_ioptr.error = error;
925                png_set_write_fn (png_ptr, &to_callback_ioptr,
926                                  png_save_to_callback_write_func,
927                                  png_save_to_callback_flush_func);
928        } else {
929                png_init_io (png_ptr, f);
930        }
931
932        if (compression >= 0)
933                png_set_compression_level (png_ptr, compression);
934
935        if (has_alpha) {
936                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
937                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
938                              PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
939        } else {
940                png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
941                              PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
942                              PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
943        }
944        sig_bit.red = bpc;
945        sig_bit.green = bpc;
946        sig_bit.blue = bpc;
947        sig_bit.alpha = bpc;
948        png_set_sBIT (png_ptr, info_ptr, &sig_bit);
949        png_write_info (png_ptr, info_ptr);
950        png_set_shift (png_ptr, &sig_bit);
951        png_set_packing (png_ptr);
952
953        ptr = pixels;
954        for (y = 0; y < h; y++) {
955                row_ptr = (png_bytep)ptr;
956                png_write_rows (png_ptr, &row_ptr, 1);
957                ptr += rowstride;
958        }
959
960        png_write_end (png_ptr, info_ptr);
961
962 cleanup:
963        png_destroy_write_struct (&png_ptr, &info_ptr);
964
965        if (num_keys > 0) {
966                for (i = 0; i < num_keys; i++)
967                        g_free (text_ptr[i].text);
968                g_free (text_ptr);
969        }
970
971        return success;
972 }
973
974 static gboolean
975 gdk_pixbuf__png_image_save (FILE          *f, 
976                             GdkPixbuf     *pixbuf, 
977                             gchar        **keys,
978                             gchar        **values,
979                             GError       **error)
980 {
981         return real_save_png (pixbuf, keys, values, error,
982                               FALSE, f, NULL, NULL);
983 }
984
985 static gboolean
986 gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc   save_func,
987                                         gpointer            user_data,
988                                         GdkPixbuf          *pixbuf, 
989                                         gchar             **keys,
990                                         gchar             **values,
991                                         GError            **error)
992 {
993         return real_save_png (pixbuf, keys, values, error,
994                               TRUE, NULL, save_func, user_data);
995 }
996
997 void
998 MODULE_ENTRY (png, fill_vtable) (GdkPixbufModule *module)
999 {
1000         module->load = gdk_pixbuf__png_image_load;
1001         module->begin_load = gdk_pixbuf__png_image_begin_load;
1002         module->stop_load = gdk_pixbuf__png_image_stop_load;
1003         module->load_increment = gdk_pixbuf__png_image_load_increment;
1004         module->save = gdk_pixbuf__png_image_save;
1005         module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
1006 }
1007
1008 void
1009 MODULE_ENTRY (png, fill_info) (GdkPixbufFormat *info)
1010 {
1011         static GdkPixbufModulePattern signature[] = {
1012                 { "\x89PNG\r\n\x1a\x0a", NULL, 100 },
1013                 { NULL, NULL, 0 }
1014         };
1015         static gchar * mime_types[] = {
1016                 "image/png",
1017                 NULL
1018         };
1019         static gchar * extensions[] = {
1020                 "png",
1021                 NULL
1022         };
1023
1024         info->name = "png";
1025         info->signature = signature;
1026         info->description = N_("The PNG image format");
1027         info->mime_types = mime_types;
1028         info->extensions = extensions;
1029         info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
1030         info->license = "LGPL";
1031 }