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 |
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_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 | |
176 | static void |
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_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 | |
198 | static void |
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 |
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 (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 | |
243 | static png_voidp |
244 | png_malloc_callback (png_structp o, png_size_t size) |
245 | { |
246 | return g_try_malloc (n_bytes: size); |
247 | } |
248 | |
249 | static void |
250 | png_free_callback (png_structp o, png_voidp x) |
251 | { |
252 | g_free (mem: x); |
253 | } |
254 | |
255 | /* Shared library entry point */ |
256 | static GdkPixbuf * |
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_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. */ |
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 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 | |
443 | static gpointer |
444 | gdk_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 | |
547 | static gboolean |
548 | gdk_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 | |
574 | static gboolean |
575 | gdk_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 */ |
655 | static void |
656 | png_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 */ |
771 | static void |
772 | png_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 */ |
811 | static void |
812 | png_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 | |
823 | static void |
824 | png_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 | |
847 | static void |
848 | png_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 | |
862 | typedef struct { |
863 | GdkPixbufSaveFunc save_func; |
864 | gpointer user_data; |
865 | GError **error; |
866 | } SaveToFunctionIoPtr; |
867 | |
868 | static void |
869 | png_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 | |
882 | static void |
883 | png_save_to_callback_flush_func (png_structp png_ptr) |
884 | { |
885 | ; |
886 | } |
887 | |
888 | static gboolean |
889 | real_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 | |
1152 | cleanup: |
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 | |
1172 | static gboolean |
1173 | gdk_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 | |
1185 | static gboolean |
1186 | gdk_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 | |
1199 | static gboolean |
1200 | gdk_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 | |
1218 | MODULE_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 | |
1229 | MODULE_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 | |