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