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