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
43static gboolean
44setup_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_ptr: png_read_ptr, info_ptr: png_info_ptr);
59 if (bit_depth < 1 || bit_depth > 16) {
60 g_set_error_literal (err: error,
61 GDK_PIXBUF_ERROR,
62 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
63 _("Bits per channel of PNG image is invalid."));
64 return FALSE;
65 }
66 png_get_IHDR (png_ptr: png_read_ptr, info_ptr: png_info_ptr,
67 width: &width, height: &height,
68 bit_depth: &bit_depth,
69 color_type: &color_type,
70 interlace_method: &interlace_type,
71 compression_method: &compression_type,
72 filter_method: &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_ptr: 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_ptr: png_read_ptr);
88
89 } else if (png_get_valid (png_ptr: png_read_ptr,
90 info_ptr: png_info_ptr, PNG_INFO_tRNS)) {
91
92 /* If we have transparency header, convert it to alpha
93 channel */
94 png_set_expand(png_ptr: 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_ptr: 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_ptr: 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_ptr: png_read_ptr);
116 }
117
118 /* If interlaced, handle that */
119 if (interlace_type != PNG_INTERLACE_NONE) {
120 png_set_interlace_handling(png_ptr: png_read_ptr);
121 }
122
123 /* Update the info the reflect our transformations */
124 png_read_update_info(png_ptr: png_read_ptr, info_ptr: png_info_ptr);
125
126 png_get_IHDR (png_ptr: png_read_ptr, info_ptr: png_info_ptr,
127 width: &width, height: &height,
128 bit_depth: &bit_depth,
129 color_type: &color_type,
130 interlace_method: &interlace_type,
131 compression_method: &compression_type,
132 filter_method: &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 (err: error,
142 GDK_PIXBUF_ERROR,
143 code: 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 (err: error,
150 GDK_PIXBUF_ERROR,
151 code: 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 (err: error,
159 GDK_PIXBUF_ERROR,
160 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
161 _("Transformed PNG not RGB or RGBA."));
162 return FALSE;
163 }
164
165 channels = png_get_channels(png_ptr: png_read_ptr, info_ptr: png_info_ptr);
166 if ( ! (channels == 3 || channels == 4) ) {
167 g_set_error_literal (err: error,
168 GDK_PIXBUF_ERROR,
169 code: 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
176static void
177png_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_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 (err: error,
189 GDK_PIXBUF_ERROR,
190 code: GDK_PIXBUF_ERROR_FAILED,
191 _("Fatal error in PNG image file: %s"),
192 error_msg);
193 }
194
195 longjmp (png_jmpbuf(png_save_ptr), val: 1);
196}
197
198static void
199png_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
210static gboolean
211png_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 (str: text_ptr.text);
227 } else {
228 *value = g_convert (str: text_ptr.text, len: -1,
229 to_codeset: "UTF-8", from_codeset: "ISO-8859-1",
230 NULL, NULL, NULL);
231 }
232
233 if (*value) {
234 *key = g_strconcat (string1: "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
243static png_voidp
244png_malloc_callback (png_structp o, png_size_t size)
245{
246 return g_try_malloc (n_bytes: size);
247}
248
249static void
250png_free_callback (png_structp o, png_voidp x)
251{
252 g_free (mem: x);
253}
254
255/* Shared library entry point */
256static GdkPixbuf *
257gdk_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_ptr: error,
285 error_fn: png_simple_error_callback,
286 warn_fn: png_simple_warning_callback,
287 NULL,
288 malloc_fn: png_malloc_callback,
289 free_fn: 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_ptr: &png_ptr, NULL, NULL);
302 return NULL;
303 }
304
305 if (setjmp (png_jmpbuf(png_ptr))) {
306 g_free (mem: rows);
307
308 if (pixbuf)
309 g_object_unref (object: pixbuf);
310
311 png_destroy_read_struct (png_ptr_ptr: &png_ptr, info_ptr_ptr: &info_ptr, NULL);
312 return NULL;
313 }
314
315 png_init_io (png_ptr, fp: f);
316 png_read_info (png_ptr, info_ptr);
317
318 if (!setup_png_transformations(png_read_ptr: png_ptr, png_info_ptr: info_ptr, error, width_p: &w, height_p: &h, color_type_p: &ctype)) {
319 png_destroy_read_struct (png_ptr_ptr: &png_ptr, info_ptr_ptr: &info_ptr, NULL);
320 return NULL;
321 }
322
323 pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: ctype & PNG_COLOR_MASK_ALPHA, bits_per_sample: 8, width: w, height: h);
324
325 if (!pixbuf) {
326 g_set_error_literal (err: error,
327 GDK_PIXBUF_ERROR,
328 code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
329 _("Insufficient memory to load PNG file"));
330
331 png_destroy_read_struct (png_ptr_ptr: &png_ptr, info_ptr_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, image: rows);
345 png_read_end (png_ptr, info_ptr);
346
347 if (png_get_text (png_ptr, info_ptr, text_ptr: &text_ptr, num_text: &num_texts)) {
348 for (i = 0; i < num_texts; i++) {
349 png_text_to_pixbuf_option (text_ptr: text_ptr[i], key: &key, value: &value);
350 gdk_pixbuf_set_option (pixbuf, key, value);
351 g_free (mem: key);
352 g_free (mem: 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 name: (png_charpp) &icc_profile_title, compression_type: &compression_type,
360 profile: (png_bytepp) &icc_profile, proflen: (png_uint_32*) &icc_profile_size);
361 if (retval != 0) {
362 icc_profile_base64 = g_base64_encode (data: (const guchar *) icc_profile, len: (gsize)icc_profile_size);
363 gdk_pixbuf_set_option (pixbuf, key: "icc-profile", value: icc_profile_base64);
364 g_free (mem: icc_profile_base64);
365 }
366#endif
367
368#ifdef PNG_pHYs_SUPPORTED
369 retval = png_get_pHYs (png_ptr, info_ptr, res_x: &x_resolution, res_y: &y_resolution, unit_type: &unit_type);
370 if (retval != 0 && unit_type == PNG_RESOLUTION_METER) {
371 density_str = g_strdup_printf (format: "%d", DPM_TO_DPI (x_resolution));
372 gdk_pixbuf_set_option (pixbuf, key: "x-dpi", value: density_str);
373 g_free (mem: density_str);
374 density_str = g_strdup_printf (format: "%d", DPM_TO_DPI (y_resolution));
375 gdk_pixbuf_set_option (pixbuf, key: "y-dpi", value: density_str);
376 g_free (mem: density_str);
377 }
378#endif
379
380 g_free (mem: rows);
381 png_destroy_read_struct (png_ptr_ptr: &png_ptr, info_ptr_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. */
388static void png_error_callback (png_structp png_read_ptr,
389 png_const_charp error_msg);
390
391static 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 */
395static 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 */
400static 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 */
406static void png_end_callback (png_structp png_read_ptr,
407 png_infop png_info_ptr);
408
409typedef struct _LoadContext LoadContext;
410
411struct _LoadContext {
412 png_structp png_read_ptr;
413 png_infop png_info_ptr;
414
415 GdkPixbufModuleSizeFunc size_func;
416 GdkPixbufModulePreparedFunc prepared_func;
417 GdkPixbufModuleUpdatedFunc updated_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
443static gpointer
444gdk_pixbuf__png_image_begin_load (GdkPixbufModuleSizeFunc size_func,
445 GdkPixbufModulePreparedFunc prepared_func,
446 GdkPixbufModuleUpdatedFunc updated_func,
447 gpointer user_data,
448 GError **error)
449{
450 LoadContext* lc;
451
452 g_assert (size_func != NULL);
453 g_assert (prepared_func != NULL);
454 g_assert (updated_func != NULL);
455
456 lc = g_new0(LoadContext, 1);
457
458 lc->fatal_error_occurred = FALSE;
459
460 lc->size_func = size_func;
461 lc->prepared_func = prepared_func;
462 lc->updated_func = updated_func;
463 lc->notify_user_data = user_data;
464
465 lc->first_row_seen_in_chunk = -1;
466 lc->last_row_seen_in_chunk = -1;
467 lc->first_pass_seen_in_chunk = -1;
468 lc->last_pass_seen_in_chunk = -1;
469 lc->max_row_seen_in_chunk = -1;
470 lc->error = error;
471
472 /* Create the main PNG context struct */
473
474#ifdef PNG_USER_MEM_SUPPORTED
475 lc->png_read_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
476 error_ptr: lc, /* error/warning callback data */
477 error_fn: png_error_callback,
478 warn_fn: png_warning_callback,
479 NULL,
480 malloc_fn: png_malloc_callback,
481 free_fn: png_free_callback);
482#else
483 lc->png_read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
484 lc, /* error/warning callback data */
485 png_error_callback,
486 png_warning_callback);
487#endif
488 if (lc->png_read_ptr == NULL) {
489 g_free(mem: lc);
490
491 /* A failure here isn't supposed to call the error
492 * callback, but it doesn't hurt to be careful.
493 */
494 if (error && *error == NULL) {
495 g_set_error_literal (err: error,
496 GDK_PIXBUF_ERROR,
497 code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
498 _("Couldn’t allocate memory for loading PNG"));
499 }
500
501 return NULL;
502 }
503
504 /* Create the auxiliary context struct */
505
506 lc->png_info_ptr = png_create_info_struct(png_ptr: lc->png_read_ptr);
507
508 if (lc->png_info_ptr == NULL) {
509 png_destroy_read_struct(png_ptr_ptr: &lc->png_read_ptr, NULL, NULL);
510 g_free(mem: lc);
511
512 /* A failure here isn't supposed to call the error
513 * callback, but it doesn't hurt to be careful.
514 */
515 if (error && *error == NULL) {
516 g_set_error_literal (err: error,
517 GDK_PIXBUF_ERROR,
518 code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
519 _("Couldn’t allocate memory for loading PNG"));
520 }
521
522 return NULL;
523 }
524
525 if (setjmp (png_jmpbuf(lc->png_read_ptr))) {
526 png_destroy_read_struct(png_ptr_ptr: &lc->png_read_ptr, info_ptr_ptr: &lc->png_info_ptr, NULL);
527 g_free(mem: lc);
528 /* error callback should have set the error */
529 return NULL;
530 }
531
532 png_set_progressive_read_fn(png_ptr: lc->png_read_ptr,
533 progressive_ptr: lc, /* callback data */
534 info_fn: png_info_callback,
535 row_fn: png_row_callback,
536 end_fn: png_end_callback);
537
538
539 /* We don't want to keep modifying error after returning here,
540 * it may no longer be valid.
541 */
542 lc->error = NULL;
543
544 return lc;
545}
546
547static gboolean
548gdk_pixbuf__png_image_stop_load (gpointer context, GError **error)
549{
550 LoadContext* lc = context;
551 gboolean retval = TRUE;
552
553 g_return_val_if_fail(lc != NULL, TRUE);
554
555 /* FIXME this thing needs to report errors if
556 * we have unused image data
557 */
558
559 if (lc->pixbuf)
560 g_object_unref (object: lc->pixbuf);
561 else {
562 g_set_error_literal (err: error, GDK_PIXBUF_ERROR,
563 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
564 _("Premature end-of-file encountered"));
565 retval = FALSE;
566 }
567
568 png_destroy_read_struct(png_ptr_ptr: &lc->png_read_ptr, info_ptr_ptr: &lc->png_info_ptr, NULL);
569 g_free(mem: lc);
570
571 return retval;
572}
573
574static gboolean
575gdk_pixbuf__png_image_load_increment(gpointer context,
576 const guchar *buf, guint size,
577 GError **error)
578{
579 LoadContext* lc = context;
580
581 g_return_val_if_fail(lc != NULL, FALSE);
582
583 /* reset */
584 lc->first_row_seen_in_chunk = -1;
585 lc->last_row_seen_in_chunk = -1;
586 lc->first_pass_seen_in_chunk = -1;
587 lc->last_pass_seen_in_chunk = -1;
588 lc->max_row_seen_in_chunk = -1;
589 lc->error = error;
590
591 /* Invokes our callbacks as needed */
592 if (setjmp (png_jmpbuf(lc->png_read_ptr))) {
593 lc->error = NULL;
594 return FALSE;
595 } else {
596 png_process_data(png_ptr: lc->png_read_ptr, info_ptr: lc->png_info_ptr,
597 buffer: (guchar*) buf, buffer_size: size);
598 }
599
600 if (lc->fatal_error_occurred) {
601 lc->error = NULL;
602 return FALSE;
603 } else {
604 if (lc->first_row_seen_in_chunk >= 0) {
605 gint width = gdk_pixbuf_get_width (pixbuf: lc->pixbuf);
606 /* We saw at least one row */
607 gint pass_diff = lc->last_pass_seen_in_chunk - lc->first_pass_seen_in_chunk;
608
609 g_assert(pass_diff >= 0);
610
611 if (pass_diff == 0) {
612 /* start and end row were in the same pass */
613 (lc->updated_func)(lc->pixbuf, 0,
614 lc->first_row_seen_in_chunk,
615 width,
616 (lc->last_row_seen_in_chunk -
617 lc->first_row_seen_in_chunk) + 1,
618 lc->notify_user_data);
619 } else if (pass_diff == 1) {
620 /* We have from the first row seen to
621 the end of the image (max row
622 seen), then from the top of the
623 image to the last row seen */
624 /* first row to end */
625 (lc->updated_func)(lc->pixbuf, 0,
626 lc->first_row_seen_in_chunk,
627 width,
628 (lc->max_row_seen_in_chunk -
629 lc->first_row_seen_in_chunk) + 1,
630 lc->notify_user_data);
631 /* top to last row */
632 (lc->updated_func)(lc->pixbuf,
633 0, 0,
634 width,
635 lc->last_row_seen_in_chunk + 1,
636 lc->notify_user_data);
637 } else {
638 /* We made at least one entire pass, so update the
639 whole image */
640 (lc->updated_func)(lc->pixbuf,
641 0, 0,
642 width,
643 lc->max_row_seen_in_chunk + 1,
644 lc->notify_user_data);
645 }
646 }
647
648 lc->error = NULL;
649
650 return TRUE;
651 }
652}
653
654/* Called at the start of the progressive load, once we have image info */
655static void
656png_info_callback (png_structp png_read_ptr,
657 png_infop png_info_ptr)
658{
659 LoadContext* lc;
660 png_uint_32 width, height;
661 png_textp png_text_ptr;
662 int i, num_texts;
663 int color_type;
664 gboolean have_alpha = FALSE;
665 gchar *icc_profile_base64;
666 const gchar *icc_profile_title;
667 const gchar *icc_profile;
668 png_uint_32 icc_profile_size;
669 png_uint_32 x_resolution;
670 png_uint_32 y_resolution;
671 int unit_type;
672 gchar *density_str;
673 guint32 retval;
674 gint compression_type;
675
676 lc = png_get_progressive_ptr(png_ptr: png_read_ptr);
677
678 if (lc->fatal_error_occurred)
679 return;
680
681 if (!setup_png_transformations(png_read_ptr: lc->png_read_ptr,
682 png_info_ptr: lc->png_info_ptr,
683 error: lc->error,
684 width_p: &width, height_p: &height, color_type_p: &color_type)) {
685 lc->fatal_error_occurred = TRUE;
686 return;
687 }
688
689 /* If we have alpha, set a flag */
690 if (color_type & PNG_COLOR_MASK_ALPHA)
691 have_alpha = TRUE;
692
693 {
694 gint w = width;
695 gint h = height;
696 (* lc->size_func) (&w, &h, lc->notify_user_data);
697
698 if (w == 0 || h == 0) {
699 lc->fatal_error_occurred = TRUE;
700 g_set_error_literal (err: lc->error,
701 GDK_PIXBUF_ERROR,
702 code: GDK_PIXBUF_ERROR_FAILED,
703 _("Transformed PNG has zero width or height."));
704 return;
705 }
706 }
707
708 lc->pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: have_alpha, bits_per_sample: 8, width, height);
709
710 if (lc->pixbuf == NULL) {
711 /* Failed to allocate memory */
712 lc->fatal_error_occurred = TRUE;
713 g_set_error (err: lc->error,
714 GDK_PIXBUF_ERROR,
715 code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
716 _("Insufficient memory to store a %lu by %lu image; try exiting some applications to reduce memory usage"),
717 (gulong) width, (gulong) height);
718 return;
719 }
720
721 gdk_pixbuf_fill (pixbuf: lc->pixbuf, DEFAULT_FILL_COLOR);
722
723 /* Extract text chunks and attach them as pixbuf options */
724
725 if (png_get_text (png_ptr: png_read_ptr, info_ptr: png_info_ptr, text_ptr: &png_text_ptr, num_text: &num_texts)) {
726 for (i = 0; i < num_texts; i++) {
727 gchar *key, *value;
728
729 if (png_text_to_pixbuf_option (text_ptr: png_text_ptr[i],
730 key: &key, value: &value)) {
731 gdk_pixbuf_set_option (pixbuf: lc->pixbuf, key, value);
732 g_free (mem: key);
733 g_free (mem: value);
734 }
735 }
736 }
737
738#if defined(PNG_cHRM_SUPPORTED)
739 /* Extract embedded ICC profile */
740 retval = png_get_iCCP (png_ptr: png_read_ptr, info_ptr: png_info_ptr,
741 name: (png_charpp) &icc_profile_title, compression_type: &compression_type,
742 profile: (png_bytepp) &icc_profile, proflen: &icc_profile_size);
743 if (retval != 0) {
744 icc_profile_base64 = g_base64_encode (data: (const guchar *) icc_profile, len: (gsize)icc_profile_size);
745 gdk_pixbuf_set_option (pixbuf: lc->pixbuf, key: "icc-profile", value: icc_profile_base64);
746 g_free (mem: icc_profile_base64);
747 }
748#endif
749
750#ifdef PNG_pHYs_SUPPORTED
751 retval = png_get_pHYs (png_ptr: png_read_ptr, info_ptr: png_info_ptr, res_x: &x_resolution, res_y: &y_resolution, unit_type: &unit_type);
752 if (retval != 0 && unit_type == PNG_RESOLUTION_METER) {
753 density_str = g_strdup_printf (format: "%d", DPM_TO_DPI (x_resolution));
754 gdk_pixbuf_set_option (pixbuf: lc->pixbuf, key: "x-dpi", value: density_str);
755 g_free (mem: density_str);
756 density_str = g_strdup_printf (format: "%d", DPM_TO_DPI (y_resolution));
757 gdk_pixbuf_set_option (pixbuf: lc->pixbuf, key: "y-dpi", value: density_str);
758 g_free (mem: density_str);
759 }
760#endif
761
762 /* Notify the client that we are ready to go */
763
764 (* lc->prepared_func) (lc->pixbuf, NULL, lc->notify_user_data);
765
766 return;
767}
768
769/* Called for each row; note that you will get duplicate row numbers
770 for interlaced PNGs */
771static void
772png_row_callback (png_structp png_read_ptr,
773 png_bytep new_row,
774 png_uint_32 row_num,
775 int pass_num)
776{
777 LoadContext* lc;
778 guchar* old_row = NULL;
779 gsize rowstride;
780
781 lc = png_get_progressive_ptr(png_ptr: png_read_ptr);
782
783 if (lc->fatal_error_occurred)
784 return;
785
786 if (row_num >= gdk_pixbuf_get_height (pixbuf: lc->pixbuf)) {
787 lc->fatal_error_occurred = TRUE;
788 g_set_error_literal (err: lc->error,
789 GDK_PIXBUF_ERROR,
790 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
791 _("Fatal error reading PNG image file"));
792 return;
793 }
794
795 if (lc->first_row_seen_in_chunk < 0) {
796 lc->first_row_seen_in_chunk = row_num;
797 lc->first_pass_seen_in_chunk = pass_num;
798 }
799
800 lc->max_row_seen_in_chunk = MAX(lc->max_row_seen_in_chunk, ((gint)row_num));
801 lc->last_row_seen_in_chunk = row_num;
802 lc->last_pass_seen_in_chunk = pass_num;
803
804 rowstride = gdk_pixbuf_get_rowstride (pixbuf: lc->pixbuf);
805 old_row = gdk_pixbuf_get_pixels (pixbuf: lc->pixbuf) + (row_num * rowstride);
806
807 png_progressive_combine_row(png_ptr: lc->png_read_ptr, old_row, new_row);
808}
809
810/* Called after reading the entire image */
811static void
812png_end_callback (png_structp png_read_ptr,
813 png_infop png_info_ptr)
814{
815 LoadContext* lc;
816
817 lc = png_get_progressive_ptr(png_ptr: png_read_ptr);
818
819 if (lc->fatal_error_occurred)
820 return;
821}
822
823static void
824png_error_callback(png_structp png_read_ptr,
825 png_const_charp error_msg)
826{
827 LoadContext* lc;
828
829 lc = png_get_error_ptr(png_ptr: png_read_ptr);
830
831 lc->fatal_error_occurred = TRUE;
832
833 /* I don't trust libpng to call the error callback only once,
834 * so check for already-set error
835 */
836 if (lc->error && *lc->error == NULL) {
837 g_set_error (err: lc->error,
838 GDK_PIXBUF_ERROR,
839 code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
840 _("Fatal error reading PNG image file: %s"),
841 error_msg);
842 }
843
844 longjmp (png_jmpbuf(png_read_ptr), val: 1);
845}
846
847static void
848png_warning_callback (png_structp png_read_ptr,
849 png_const_charp warning_msg)
850{
851 /* Don't print anything; we should not be dumping junk to
852 * stderr, since that may be bad for some apps. If it's
853 * important enough to display, we need to add a GError
854 * **warning return location wherever we have an error return
855 * location.
856 */
857}
858
859
860/* Save */
861
862typedef struct {
863 GdkPixbufSaveFunc save_func;
864 gpointer user_data;
865 GError **error;
866} SaveToFunctionIoPtr;
867
868static void
869png_save_to_callback_write_func (png_structp png_ptr,
870 png_bytep data,
871 png_size_t length)
872{
873 SaveToFunctionIoPtr *ioptr = png_get_io_ptr (png_ptr);
874
875 if (!ioptr->save_func ((gchar *)data, length, ioptr->error, ioptr->user_data)) {
876 /* If save_func has already set an error, which it
877 should have done, this won't overwrite it. */
878 png_error (png_ptr, error_message: "write function failed");
879 }
880}
881
882static void
883png_save_to_callback_flush_func (png_structp png_ptr)
884{
885 ;
886}
887
888static gboolean
889real_save_png (GdkPixbuf *pixbuf,
890 int n_keys,
891 gchar **keys,
892 gchar **values,
893 GError **error,
894 gboolean to_callback,
895 FILE *f,
896 GdkPixbufSaveFunc save_func,
897 gpointer user_data)
898{
899 png_structp png_ptr = NULL;
900 png_infop info_ptr;
901 guchar *ptr;
902 guchar *pixels;
903 int y;
904 png_bytep row_ptr;
905 png_color_8 sig_bit;
906 int w, h, rowstride;
907 int has_alpha;
908 int bpc;
909 int compression = -1;
910 int x_density = 0;
911 int y_density = 0;
912 gboolean success = TRUE;
913 guchar *icc_profile = NULL;
914 gsize icc_profile_size = 0;
915 SaveToFunctionIoPtr to_callback_ioptr;
916 int num_keys = 0;
917 png_textp text_ptr = NULL;
918 GArray *text_data = NULL;
919
920 text_data = g_array_sized_new (FALSE, TRUE, element_size: sizeof (png_text), reserved_size: n_keys);
921
922 for (int i = 0; i < n_keys; i++) {
923 const char *key = keys[i];
924 const char *value = values[i];
925
926 if (strncmp (s1: key, s2: "tEXt::", n: 6) == 0) {
927 const char *unprefixed_key = key + 6;
928 int len = strlen (s: unprefixed_key);
929 png_text text;
930
931 if (len < 1 || len > 79) {
932 /* Translators notice: '%s' is the name of the
933 * PNG text key
934 */
935 g_set_error (err: error, GDK_PIXBUF_ERROR,
936 code: GDK_PIXBUF_ERROR_BAD_OPTION,
937 _("Invalid key “%s”. Keys for PNG text chunks must have at least 1 and at most 79 characters."),
938 unprefixed_key);
939 success = FALSE;
940 goto cleanup;
941 }
942
943 for (int i = 0; i < len; i++) {
944 if ((guchar) unprefixed_key[i] > 127) {
945 /* Translators notice: '%s' is the name of
946 * the PNG text key
947 */
948 g_set_error (err: error, GDK_PIXBUF_ERROR,
949 code: GDK_PIXBUF_ERROR_BAD_OPTION,
950 _("Invalid key “%s”. Keys for PNG text chunks must be ASCII characters."),
951 unprefixed_key);
952 success = FALSE;
953 goto cleanup;
954 }
955 }
956
957 text.compression = PNG_TEXT_COMPRESSION_NONE;
958 text.key = unprefixed_key;
959 text.text = g_convert (str: value, len: -1,
960 to_codeset: "ISO-8859-1", from_codeset: "UTF-8",
961 NULL,
962 bytes_written: &text.text_length,
963 NULL);
964
965#ifdef PNG_iTXt_SUPPORTED
966 if (text.text == NULL) {
967 text.compression = PNG_ITXT_COMPRESSION_NONE;
968 text.text = g_strdup (str: value);
969 text.text_length = 0;
970 text.itxt_length = strlen (s: value);
971 text.lang = NULL;
972 text.lang_key = NULL;
973 }
974#endif
975
976 if (text.text == NULL) {
977 g_set_error (err: error,
978 GDK_PIXBUF_ERROR,
979 code: GDK_PIXBUF_ERROR_BAD_OPTION,
980 _("Value for PNG text chunk '%s' cannot be converted to ISO-8859-1 encoding."), unprefixed_key);
981 success = FALSE;
982 goto cleanup;
983 }
984
985 g_array_append_val (text_data, text);
986 } else if (strcmp (s1: key, s2: "icc-profile") == 0) {
987 icc_profile = g_base64_decode (text: value, out_len: &icc_profile_size);
988
989 if (icc_profile_size < 127) {
990 g_set_error (err: error, GDK_PIXBUF_ERROR,
991 code: GDK_PIXBUF_ERROR_BAD_OPTION,
992 _("Color profile has invalid length %d"),
993 (int) icc_profile_size);
994 success = FALSE;
995 goto cleanup;
996 }
997 } else if (strcmp (s1: key, s2: "compression") == 0) {
998 char *endptr = NULL;
999
1000 compression = strtol (nptr: value, endptr: &endptr, base: 10);
1001 if (endptr == value || (compression < 0 || compression > 9)) {
1002 g_set_error (err: error, GDK_PIXBUF_ERROR,
1003 code: GDK_PIXBUF_ERROR_BAD_OPTION,
1004 _("PNG compression level must be a value between 0 and 9; value “%s” is invalid"),
1005 value);
1006 success = FALSE;
1007 goto cleanup;
1008 }
1009 } else if (strcmp (s1: key, s2: "x-dpi") == 0 || strcmp (s1: key, s2: "y-dpi") == 0) {
1010 gboolean is_horizontal = strcmp (s1: key, s2: "x-dpi") == 0;
1011 char *endptr = NULL;
1012
1013 int dpi = strtol (nptr: value, endptr: &endptr, base: 10);
1014
1015 if (endptr == value || dpi <= 0) {
1016 g_set_error (err: error, GDK_PIXBUF_ERROR,
1017 code: GDK_PIXBUF_ERROR_BAD_OPTION,
1018 _("PNG %s must be greater than zero; value “%s” is not allowed"),
1019 is_horizontal ? "x-dpi" : "y-dpi",
1020 value);
1021 success = FALSE;
1022 goto cleanup;
1023 }
1024
1025 if (is_horizontal) {
1026 x_density = dpi;
1027 } else {
1028 y_density = dpi;
1029 }
1030 } else {
1031 g_warning ("Unrecognized parameter “%s” passed to the PNG saver", key);
1032 }
1033 }
1034
1035 bpc = gdk_pixbuf_get_bits_per_sample (pixbuf);
1036 w = gdk_pixbuf_get_width (pixbuf);
1037 h = gdk_pixbuf_get_height (pixbuf);
1038 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
1039 has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
1040 pixels = gdk_pixbuf_get_pixels (pixbuf);
1041
1042 if (text_data->len > 0) {
1043 num_keys = text_data->len;
1044 text_ptr = (png_textp) g_array_free (array: text_data, FALSE);
1045 text_data = NULL;
1046 } else {
1047 g_clear_pointer (&text_data, g_array_unref);
1048 num_keys = 0;
1049 text_ptr = NULL;
1050 }
1051
1052 /* Guaranteed by the caller. */
1053 g_assert (w >= 0);
1054 g_assert (h >= 0);
1055 g_assert (rowstride >= 0);
1056
1057 png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
1058 error_ptr: error,
1059 error_fn: png_simple_error_callback,
1060 warn_fn: png_simple_warning_callback);
1061 if (png_ptr == NULL) {
1062 success = FALSE;
1063 goto cleanup;
1064 }
1065
1066 info_ptr = png_create_info_struct (png_ptr);
1067 if (info_ptr == NULL) {
1068 success = FALSE;
1069 goto cleanup;
1070 }
1071
1072 if (setjmp (png_jmpbuf (png_ptr))) {
1073 success = FALSE;
1074 goto cleanup;
1075 }
1076
1077 if (num_keys > 0) {
1078 png_set_text (png_ptr, info_ptr, text_ptr, num_text: num_keys);
1079 }
1080
1081 if (to_callback) {
1082 to_callback_ioptr.save_func = save_func;
1083 to_callback_ioptr.user_data = user_data;
1084 to_callback_ioptr.error = error;
1085 png_set_write_fn (png_ptr, io_ptr: &to_callback_ioptr,
1086 write_data_fn: png_save_to_callback_write_func,
1087 output_flush_fn: png_save_to_callback_flush_func);
1088 } else {
1089 png_init_io (png_ptr, fp: f);
1090 }
1091
1092 if (compression >= 0) {
1093 png_set_compression_level (png_ptr, level: compression);
1094 }
1095
1096#ifdef PNG_pHYs_SUPPORTED
1097 if (x_density > 0 && y_density > 0) {
1098 png_set_pHYs (png_ptr, info_ptr,
1099 DPI_TO_DPM (x_density),
1100 DPI_TO_DPM (y_density),
1101 PNG_RESOLUTION_METER);
1102 }
1103#endif
1104
1105#if defined(PNG_iCCP_SUPPORTED)
1106 /* the proper ICC profile title is encoded in the profile */
1107 if (icc_profile != NULL) {
1108 png_set_iCCP (png_ptr, info_ptr,
1109 name: "ICC profile",
1110 PNG_COMPRESSION_TYPE_BASE,
1111 profile: (png_bytep) icc_profile,
1112 proflen: icc_profile_size);
1113 }
1114#endif
1115
1116 if (has_alpha) {
1117 png_set_IHDR (png_ptr, info_ptr, width: w, height: h, bit_depth: bpc,
1118 PNG_COLOR_TYPE_RGB_ALPHA,
1119 PNG_INTERLACE_NONE,
1120 PNG_COMPRESSION_TYPE_BASE,
1121 PNG_FILTER_TYPE_BASE);
1122 } else {
1123 png_set_IHDR (png_ptr, info_ptr, width: w, height: h, bit_depth: bpc,
1124 PNG_COLOR_TYPE_RGB,
1125 PNG_INTERLACE_NONE,
1126 PNG_COMPRESSION_TYPE_BASE,
1127 PNG_FILTER_TYPE_BASE);
1128 }
1129
1130 /* Note bpc is always 8 */
1131 sig_bit.red = bpc;
1132 sig_bit.green = bpc;
1133 sig_bit.blue = bpc;
1134 sig_bit.alpha = bpc;
1135 png_set_sBIT (png_ptr, info_ptr, sig_bit: &sig_bit);
1136 png_write_info (png_ptr, info_ptr);
1137 png_set_packing (png_ptr);
1138
1139 for (y = 0, ptr = pixels; y < h; y++, ptr += rowstride) {
1140 row_ptr = (png_bytep)ptr;
1141 png_write_rows (png_ptr, row: &row_ptr, num_rows: 1);
1142 }
1143
1144 png_write_end (png_ptr, info_ptr);
1145
1146 for (int i = 0; i < num_keys; i++) {
1147 g_free (mem: text_ptr[i].text);
1148 }
1149
1150 g_free (mem: text_ptr);
1151
1152cleanup:
1153 if (png_ptr != NULL) {
1154 png_destroy_write_struct (png_ptr_ptr: &png_ptr, info_ptr_ptr: &info_ptr);
1155 }
1156
1157 if (text_data != NULL) {
1158 for (guint i = 0; i < text_data->len; i++) {
1159 png_textp text = &g_array_index (text_data, png_text, i);
1160
1161 g_free (mem: text->text);
1162 }
1163
1164 g_array_unref (array: text_data);
1165 }
1166
1167 g_free (mem: icc_profile);
1168
1169 return success;
1170}
1171
1172static gboolean
1173gdk_pixbuf__png_image_save (FILE *f,
1174 GdkPixbuf *pixbuf,
1175 gchar **keys,
1176 gchar **values,
1177 GError **error)
1178{
1179 int n_keys = keys != NULL ? g_strv_length (str_array: keys) : 0;
1180
1181 return real_save_png (pixbuf, n_keys, keys, values, error,
1182 FALSE, f, NULL, NULL);
1183}
1184
1185static gboolean
1186gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc save_func,
1187 gpointer user_data,
1188 GdkPixbuf *pixbuf,
1189 gchar **keys,
1190 gchar **values,
1191 GError **error)
1192{
1193 int n_keys = keys != NULL ? g_strv_length (str_array: keys) : 0;
1194
1195 return real_save_png (pixbuf, n_keys, keys, values, error,
1196 TRUE, NULL, save_func, user_data);
1197}
1198
1199static gboolean
1200gdk_pixbuf__png_is_save_option_supported (const gchar *option_key)
1201{
1202 if (g_strcmp0 (str1: option_key, str2: "compression") == 0 ||
1203 g_strcmp0 (str1: option_key, str2: "icc-profile") == 0 ||
1204 g_strcmp0 (str1: option_key, str2: "x-dpi") == 0 ||
1205 g_strcmp0 (str1: option_key, str2: "y-dpi") == 0 ||
1206 strncmp (s1: option_key, s2: "tEXt::", n: 6) == 0)
1207 return TRUE;
1208
1209 return FALSE;
1210}
1211
1212#ifndef INCLUDE_png
1213#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
1214#else
1215#define MODULE_ENTRY(function) void _gdk_pixbuf__png_ ## function
1216#endif
1217
1218MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
1219{
1220 module->load = gdk_pixbuf__png_image_load;
1221 module->begin_load = gdk_pixbuf__png_image_begin_load;
1222 module->stop_load = gdk_pixbuf__png_image_stop_load;
1223 module->load_increment = gdk_pixbuf__png_image_load_increment;
1224 module->save = gdk_pixbuf__png_image_save;
1225 module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
1226 module->is_save_option_supported = gdk_pixbuf__png_is_save_option_supported;
1227}
1228
1229MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
1230{
1231 static const GdkPixbufModulePattern signature[] = {
1232 { "\x89PNG\r\n\x1a\x0a", NULL, 100 },
1233 { NULL, NULL, 0 }
1234 };
1235 static const gchar *mime_types[] = {
1236 "image/png",
1237 NULL
1238 };
1239 static const gchar *extensions[] = {
1240 "png",
1241 NULL
1242 };
1243
1244 info->name = "png";
1245 info->signature = (GdkPixbufModulePattern *) signature;
1246 info->description = NC_("image format", "PNG");
1247 info->mime_types = (gchar **) mime_types;
1248 info->extensions = (gchar **) extensions;
1249 info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
1250 info->license = "LGPL";
1251}
1252

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-png.c