handle failure to clone the log filehandle when cloning the Imager context
[imager.git] / PNG / impng.c
CommitLineData
1d7e3124 1#include "impng.h"
02d1d628 2#include "png.h"
21c6936b 3#include <stdlib.h>
57520a19 4#include <string.h>
28d08bb1 5#include <zlib.h>
02d1d628 6
02d1d628
AMH
7/* this is a way to get number of channels from color space
8 * Color code to channel number */
9
b33c08f8 10static int CC2C[PNG_COLOR_MASK_PALETTE|PNG_COLOR_MASK_COLOR|PNG_COLOR_MASK_ALPHA];
02d1d628
AMH
11
12#define PNG_BYTES_TO_CHECK 4
cfb628e2
TC
13
14static i_img *
15read_direct8(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height);
16
c631b2d5
TC
17static i_img *
18read_direct16(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height);
19
963d3602
TC
20static i_img *
21read_paletted(png_structp png_ptr, png_infop info_ptr, int channels, i_img_dim width, i_img_dim height);
22
79b4c849
TC
23static i_img *
24read_bilevel(png_structp png_ptr, png_infop info_ptr, i_img_dim width, i_img_dim height);
25
81089826
TC
26static int
27write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im);
28
d9610331
TC
29static int
30write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im);
31
21c6936b
TC
32static int
33write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits);
34
b8961d05
TC
35static int
36write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im);
21c6936b 37
d0f15206
TC
38static void
39get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth, int color_type);
40
41static int
42set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr);
43
44static const char *
45get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size);
46
647508aa
TC
47unsigned
48i_png_lib_version(void) {
49 return png_access_version_number();
50}
02d1d628 51
79f95bf1
TC
52static char const * const
53features[] =
54 {
55#ifdef PNG_BENIGN_ERRORS_SUPPORTED
56 "benign-errors",
57#endif
58#ifdef PNG_READ_SUPPORTED
59 "read",
60#endif
61#ifdef PNG_WRITE_SUPPORTED
62 "write",
63#endif
64#ifdef PNG_MNG_FEATURES_SUPPORTED
65 "mng-features",
66#endif
67#ifdef PNG_CHECK_cHRM_SUPPORTED
68 "check-cHRM",
69#endif
70#ifdef PNG_SET_USER_LIMITS_SUPPORTED
71 "user-limits",
72#endif
73 NULL
74 };
75
76const char * const *
77i_png_features(void) {
78 return features;
79}
80
02d1d628 81static void
790923a4 82wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
f5b4354e 83 io_glue *ig = png_get_io_ptr(png_ptr);
38eab175 84 ssize_t rc = i_io_read(ig, data, length);
790923a4 85 if (rc != length) png_error(png_ptr, "Read overflow error on an iolayer source.");
02d1d628
AMH
86}
87
02d1d628 88static void
790923a4 89wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
38eab175 90 ssize_t rc;
f5b4354e 91 io_glue *ig = png_get_io_ptr(png_ptr);
6d5c85a2 92 rc = i_io_write(ig, data, length);
790923a4 93 if (rc != length) png_error(png_ptr, "Write error on an iolayer source.");
02d1d628
AMH
94}
95
96static void
790923a4 97wiol_flush_data(png_structp png_ptr) {
6352c169
TC
98 io_glue *ig = png_get_io_ptr(png_ptr);
99 if (!i_io_flush(ig))
100 png_error(png_ptr, "Error flushing output");
02d1d628 101}
02d1d628 102
38eab175
TC
103static void
104error_handler(png_structp png_ptr, png_const_charp msg) {
105 mm_log((1, "PNG error: '%s'\n", msg));
106
107 i_push_error(0, msg);
108 longjmp(png_jmpbuf(png_ptr), 1);
109}
110
111/*
112
963d3602
TC
113 For writing a warning might have information about an error, so send
114 it to the error stack.
38eab175
TC
115
116*/
117static void
118write_warn_handler(png_structp png_ptr, png_const_charp msg) {
119 mm_log((1, "PNG write warning '%s'\n", msg));
120
121 i_push_error(0, msg);
122}
123
124#define PNG_DIM_MAX 0x7fffffffL
125
02d1d628 126undef_int
790923a4 127i_writepng_wiol(i_img *im, io_glue *ig) {
02d1d628 128 png_structp png_ptr;
a807aae6 129 png_infop info_ptr = NULL;
0a53238e 130 i_img_dim width,height;
02d1d628 131 volatile int cspace,channels;
21c6936b 132 int bits;
b8961d05 133 int is_bilevel = 0, zero_is_white;
02d1d628 134
790923a4 135 mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig));
8d14daab
TC
136
137 i_clear_error();
138
139 if (im->xsize > PNG_UINT_31_MAX || im->ysize > PNG_UINT_31_MAX) {
140 i_push_error(0, "image too large for PNG");
141 return 0;
142 }
143
790923a4
AMH
144 height = im->ysize;
145 width = im->xsize;
02d1d628 146
38eab175
TC
147 /* if we ever have 64-bit i_img_dim
148 * the libpng docs state that png_set_user_limits() can be used to
149 * override the PNG_USER_*_MAX limits, but as implemented they
150 * don't. We check against the theoretical limit of PNG here, and
151 * try to override the limits below, in case the libpng
152 * implementation ever matches the documentation.
153 *
154 * https://sourceforge.net/tracker/?func=detail&atid=105624&aid=3314943&group_id=5624
647508aa 155 * fixed in libpng 1.5.3
34e56776 156 */
38eab175
TC
157 if (width > PNG_DIM_MAX || height > PNG_DIM_MAX) {
158 i_push_error(0, "Image too large for PNG");
159 return 0;
160 }
161
02d1d628
AMH
162 channels=im->channels;
163
b8961d05
TC
164 if (i_img_is_monochrome(im, &zero_is_white)) {
165 is_bilevel = 1;
166 bits = 1;
167 cspace = PNG_COLOR_TYPE_GRAY;
168 mm_log((1, "i_writepng: bilevel output\n"));
169 }
170 else if (im->type == i_palette_type) {
21c6936b
TC
171 int colors = i_colorcount(im);
172
173 cspace = PNG_COLOR_TYPE_PALETTE;
174 bits = 1;
175 while ((1 << bits) < colors) {
176 bits += bits;
177 }
b8961d05 178 mm_log((1, "i_writepng: paletted output\n"));
21c6936b
TC
179 }
180 else {
181 switch (channels) {
182 case 1:
183 cspace = PNG_COLOR_TYPE_GRAY;
184 break;
185 case 2:
186 cspace = PNG_COLOR_TYPE_GRAY_ALPHA;
187 break;
188 case 3:
189 cspace = PNG_COLOR_TYPE_RGB;
190 break;
191 case 4:
192 cspace = PNG_COLOR_TYPE_RGB_ALPHA;
193 break;
194 default:
195 fprintf(stderr, "Internal error, channels = %d\n", channels);
196 abort();
197 }
d9610331 198 bits = im->bits > 8 ? 16 : 8;
b8961d05 199 mm_log((1, "i_writepng: direct output\n"));
21c6936b 200 }
02d1d628 201
b8961d05 202 mm_log((1,"i_writepng: cspace=%d, bits=%d\n",cspace, bits));
02d1d628
AMH
203
204 /* Create and initialize the png_struct with the desired error handler
205 * functions. If you want to use the default stderr and longjump method,
206 * you can supply NULL for the last three parameters. We also check that
207 * the library version is compatible with the one used at compile time,
208 * in case we are using dynamically linked libraries. REQUIRED.
209 */
210
38eab175
TC
211 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
212 error_handler, write_warn_handler);
02d1d628 213
790923a4
AMH
214 if (png_ptr == NULL) return 0;
215
02d1d628
AMH
216
217 /* Allocate/initialize the image information data. REQUIRED */
218 info_ptr = png_create_info_struct(png_ptr);
219
220 if (info_ptr == NULL) {
a807aae6 221 png_destroy_write_struct(&png_ptr, &info_ptr);
790923a4 222 return 0;
02d1d628
AMH
223 }
224
225 /* Set error handling. REQUIRED if you aren't supplying your own
226 * error hadnling functions in the png_create_write_struct() call.
227 */
f5b4354e 228 if (setjmp(png_jmpbuf(png_ptr))) {
a807aae6 229 png_destroy_write_struct(&png_ptr, &info_ptr);
02d1d628
AMH
230 return(0);
231 }
232
790923a4 233 png_set_write_fn(png_ptr, (png_voidp) (ig), wiol_write_data, wiol_flush_data);
02d1d628
AMH
234
235 /* Set the image information here. Width and height are up to 2^31,
236 * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
237 * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
238 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
239 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
240 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
241 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
242 */
243
38eab175
TC
244 /* by default, libpng (not PNG) limits the image size to a maximum
245 * 1000000 pixels in each direction, but Imager doesn't.
246 * Configure libpng to avoid that limit.
247 */
248 png_set_user_limits(png_ptr, width, height);
249
21c6936b 250 png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace,
02d1d628
AMH
251 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
252
d0f15206
TC
253 if (!set_png_tags(im, png_ptr, info_ptr)) {
254 png_destroy_write_struct(&png_ptr, &info_ptr);
255 return 0;
faa9b3e7
TC
256 }
257
b8961d05
TC
258 if (is_bilevel) {
259 if (!write_bilevel(png_ptr, info_ptr, im)) {
260 png_destroy_write_struct(&png_ptr, &info_ptr);
261 return 0;
262 }
263 }
264 else if (im->type == i_palette_type) {
21c6936b
TC
265 if (!write_paletted(png_ptr, info_ptr, im, bits)) {
266 png_destroy_write_struct(&png_ptr, &info_ptr);
267 return 0;
268 }
269 }
d9610331
TC
270 else if (bits == 16) {
271 if (!write_direct16(png_ptr, info_ptr, im)) {
272 png_destroy_write_struct(&png_ptr, &info_ptr);
273 return 0;
274 }
275 }
276 else {
21c6936b
TC
277 if (!write_direct8(png_ptr, info_ptr, im)) {
278 png_destroy_write_struct(&png_ptr, &info_ptr);
279 return 0;
280 }
faa9b3e7 281 }
790923a4 282
02d1d628 283 png_write_end(png_ptr, info_ptr);
790923a4 284
a807aae6 285 png_destroy_write_struct(&png_ptr, &info_ptr);
02d1d628 286
6d5c85a2
TC
287 if (i_io_close(ig))
288 return 0;
10461f9a 289
02d1d628
AMH
290 return(1);
291}
292
38eab175
TC
293typedef struct {
294 char *warnings;
295} i_png_read_state, *i_png_read_statep;
02d1d628 296
38eab175
TC
297static void
298read_warn_handler(png_structp, png_const_charp);
02d1d628 299
38eab175
TC
300static void
301cleanup_read_state(i_png_read_statep);
02d1d628
AMH
302
303i_img*
57520a19 304i_readpng_wiol(io_glue *ig, int flags) {
02576e8d 305 i_img *im = NULL;
02d1d628
AMH
306 png_structp png_ptr;
307 png_infop info_ptr;
790923a4 308 png_uint_32 width, height;
02d1d628 309 int bit_depth, color_type, interlace_type;
432d0ff0 310 int channels;
02d1d628 311 unsigned int sig_read;
38eab175 312 i_png_read_state rs;
02d1d628 313
38eab175 314 rs.warnings = NULL;
790923a4 315 sig_read = 0;
02d1d628 316
1d7e3124 317 mm_log((1,"i_readpng_wiol(ig %p)\n", ig));
38eab175 318 i_clear_error();
02d1d628 319
38eab175
TC
320 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &rs,
321 error_handler, read_warn_handler);
322 if (!png_ptr) {
323 i_push_error(0, "Cannot create PNG read structure");
324 return NULL;
325 }
790923a4 326 png_set_read_fn(png_ptr, (png_voidp) (ig), wiol_read_data);
57520a19 327
79f95bf1 328#if defined(PNG_BENIGN_ERRORS_SUPPORTED)
57520a19 329 png_set_benign_errors(png_ptr, (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) ? 1 : 0);
79f95bf1
TC
330#elif PNG_LIBPNG_VER >= 10400
331 if (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) {
332 i_push_error(0, "libpng not configured to ignore benign errors");
333 png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
334 return NULL;
335 }
57520a19
TC
336#else
337 if (flags & IMPNG_READ_IGNORE_BENIGN_ERRORS) {
338 i_push_error(0, "libpng too old to ignore benign errors");
339 png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
340 return NULL;
341 }
342#endif
343
02d1d628
AMH
344 info_ptr = png_create_info_struct(png_ptr);
345 if (info_ptr == NULL) {
346 png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
38eab175 347 i_push_error(0, "Cannot create PNG info structure");
02d1d628
AMH
348 return NULL;
349 }
350
f5b4354e 351 if (setjmp(png_jmpbuf(png_ptr))) {
02576e8d 352 if (im) i_img_destroy(im);
790923a4 353 mm_log((1,"i_readpng_wiol: error.\n"));
02d1d628 354 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
38eab175 355 cleanup_read_state(&rs);
02d1d628
AMH
356 return NULL;
357 }
57520a19 358
647508aa
TC
359 /* we do our own limit checks */
360 png_set_user_limits(png_ptr, PNG_DIM_MAX, PNG_DIM_MAX);
790923a4 361
02d1d628
AMH
362 png_set_sig_bytes(png_ptr, sig_read);
363 png_read_info(png_ptr, info_ptr);
364 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL);
365
5e84d110
TC
366 mm_log((1, "png_get_IHDR results: width %u, height %u, bit_depth %d, color_type %d, interlace_type %d\n",
367 (unsigned)width, (unsigned)height, bit_depth,color_type,interlace_type));
02d1d628
AMH
368
369 CC2C[PNG_COLOR_TYPE_GRAY]=1;
370 CC2C[PNG_COLOR_TYPE_PALETTE]=3;
371 CC2C[PNG_COLOR_TYPE_RGB]=3;
372 CC2C[PNG_COLOR_TYPE_RGB_ALPHA]=4;
373 CC2C[PNG_COLOR_TYPE_GRAY_ALPHA]=2;
790923a4
AMH
374 channels = CC2C[color_type];
375
376 mm_log((1,"i_readpng_wiol: channels %d\n",channels));
377
77157728
TC
378 if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
379 mm_log((1, "i_readpnm: image size exceeds limits\n"));
380 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
381 return NULL;
382 }
383
963d3602
TC
384 if (color_type == PNG_COLOR_TYPE_PALETTE) {
385 im = read_paletted(png_ptr, info_ptr, channels, width, height);
386 }
79b4c849
TC
387 else if (color_type == PNG_COLOR_TYPE_GRAY
388 && bit_depth == 1
389 && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
390 im = read_bilevel(png_ptr, info_ptr, width, height);
391 }
c631b2d5
TC
392 else if (bit_depth == 16) {
393 im = read_direct16(png_ptr, info_ptr, channels, width, height);
394 }
963d3602
TC
395 else {
396 im = read_direct8(png_ptr, info_ptr, channels, width, height);
397 }
cfb628e2
TC
398
399 if (im)
6fa6c8ee 400 get_png_tags(im, png_ptr, info_ptr, bit_depth, color_type);
cfb628e2
TC
401
402 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
403
404 if (im) {
405 if (rs.warnings) {
406 i_tags_set(&im->tags, "png_warnings", rs.warnings, -1);
407 }
408 }
409 cleanup_read_state(&rs);
410
411 mm_log((1,"(%p) <- i_readpng_wiol\n", im));
412
413 return im;
414}
415
416static i_img *
417read_direct8(png_structp png_ptr, png_infop info_ptr, int channels,
418 i_img_dim width, i_img_dim height) {
419 i_img * volatile vim = NULL;
420 int color_type = png_get_color_type(png_ptr, info_ptr);
421 int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
422 i_img_dim y;
423 int number_passes, pass;
424 i_img *im;
34e56776
TC
425 unsigned char *line;
426 unsigned char * volatile vline = NULL;
cfb628e2
TC
427
428 if (setjmp(png_jmpbuf(png_ptr))) {
429 if (vim) i_img_destroy(vim);
34e56776 430 if (vline) myfree(vline);
cfb628e2
TC
431
432 return NULL;
433 }
434
435 number_passes = png_set_interlace_handling(png_ptr);
436 mm_log((1,"number of passes=%d\n",number_passes));
437
02d1d628
AMH
438 png_set_strip_16(png_ptr);
439 png_set_packing(png_ptr);
6829f5f6 440
cfb628e2
TC
441 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
442 png_set_expand(png_ptr);
443
6829f5f6
AMH
444 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
445 channels++;
446 mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
447 png_set_expand(png_ptr);
448 }
790923a4 449
02d1d628 450 png_read_update_info(png_ptr, info_ptr);
790923a4 451
cfb628e2 452 im = vim = i_img_8_new(width,height,channels);
352c64ed
TC
453 if (!im) {
454 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
455 return NULL;
456 }
02d1d628 457
34e56776 458 line = vline = mymalloc(channels * width);
cfb628e2
TC
459 for (pass = 0; pass < number_passes; pass++) {
460 for (y = 0; y < height; y++) {
34e56776
TC
461 if (pass > 0)
462 i_gsamp(im, 0, width, y, line, NULL, channels);
463 png_read_row(png_ptr,(png_bytep)line, NULL);
464 i_psamp(im, 0, width, y, line, NULL, channels);
cfb628e2 465 }
38eab175 466 }
34e56776
TC
467 myfree(line);
468 vline = NULL;
02d1d628 469
cfb628e2
TC
470 png_read_end(png_ptr, info_ptr);
471
790923a4
AMH
472 return im;
473}
faa9b3e7 474
c631b2d5
TC
475static i_img *
476read_direct16(png_structp png_ptr, png_infop info_ptr, int channels,
477 i_img_dim width, i_img_dim height) {
478 i_img * volatile vim = NULL;
c631b2d5
TC
479 i_img_dim x, y;
480 int number_passes, pass;
481 i_img *im;
482 unsigned char *line;
483 unsigned char * volatile vline = NULL;
484 unsigned *bits_line;
485 unsigned * volatile vbits_line = NULL;
486 size_t row_bytes;
487
488 if (setjmp(png_jmpbuf(png_ptr))) {
489 if (vim) i_img_destroy(vim);
490 if (vline) myfree(vline);
491 if (vbits_line) myfree(vbits_line);
492
493 return NULL;
494 }
495
496 number_passes = png_set_interlace_handling(png_ptr);
497 mm_log((1,"number of passes=%d\n",number_passes));
498
499 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
500 channels++;
501 mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
502 png_set_expand(png_ptr);
503 }
504
505 png_read_update_info(png_ptr, info_ptr);
506
507 im = vim = i_img_16_new(width,height,channels);
508 if (!im) {
509 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
510 return NULL;
511 }
512
513 row_bytes = png_get_rowbytes(png_ptr, info_ptr);
514 line = vline = mymalloc(row_bytes);
515 memset(line, 0, row_bytes);
516 bits_line = vbits_line = mymalloc(sizeof(unsigned) * width * channels);
517 for (pass = 0; pass < number_passes; pass++) {
518 for (y = 0; y < height; y++) {
519 if (pass > 0) {
520 i_gsamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
521 for (x = 0; x < width * channels; ++x) {
522 line[x*2] = bits_line[x] >> 8;
523 line[x*2+1] = bits_line[x] & 0xff;
524 }
525 }
526 png_read_row(png_ptr,(png_bytep)line, NULL);
527 for (x = 0; x < width * channels; ++x)
528 bits_line[x] = (line[x*2] << 8) + line[x*2+1];
529 i_psamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
530 }
531 }
532 myfree(line);
533 myfree(bits_line);
534 vline = NULL;
535 vbits_line = NULL;
536
537 png_read_end(png_ptr, info_ptr);
538
539 return im;
540}
541
79b4c849
TC
542static i_img *
543read_bilevel(png_structp png_ptr, png_infop info_ptr,
544 i_img_dim width, i_img_dim height) {
545 i_img * volatile vim = NULL;
546 i_img_dim x, y;
547 int number_passes, pass;
548 i_img *im;
549 unsigned char *line;
550 unsigned char * volatile vline = NULL;
551 i_color palette[2];
552
553 if (setjmp(png_jmpbuf(png_ptr))) {
554 if (vim) i_img_destroy(vim);
555 if (vline) myfree(vline);
556
557 return NULL;
558 }
559
560 number_passes = png_set_interlace_handling(png_ptr);
561 mm_log((1,"number of passes=%d\n",number_passes));
562
563 png_set_packing(png_ptr);
564
565 png_set_expand(png_ptr);
566
567 png_read_update_info(png_ptr, info_ptr);
568
569 im = vim = i_img_pal_new(width, height, 1, 256);
570 if (!im) {
571 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
572 return NULL;
573 }
574
575 palette[0].channel[0] = palette[0].channel[1] = palette[0].channel[2] =
576 palette[0].channel[3] = 0;
577 palette[1].channel[0] = palette[1].channel[1] = palette[1].channel[2] =
578 palette[1].channel[3] = 255;
579 i_addcolors(im, palette, 2);
580
581 line = vline = mymalloc(width);
582 memset(line, 0, width);
583 for (pass = 0; pass < number_passes; pass++) {
584 for (y = 0; y < height; y++) {
585 if (pass > 0) {
586 i_gpal(im, 0, width, y, line);
587 /* expand indexes back to 0/255 */
588 for (x = 0; x < width; ++x)
589 line[x] = line[x] ? 255 : 0;
590 }
591 png_read_row(png_ptr,(png_bytep)line, NULL);
592
593 /* back to palette indexes */
594 for (x = 0; x < width; ++x)
595 line[x] = line[x] ? 1 : 0;
596 i_ppal(im, 0, width, y, line);
597 }
598 }
599 myfree(line);
600 vline = NULL;
601
602 png_read_end(png_ptr, info_ptr);
603
604 return im;
605}
606
607/* FIXME: do we need to unscale palette color values from the
608 supplied alphas? */
963d3602
TC
609static i_img *
610read_paletted(png_structp png_ptr, png_infop info_ptr, int channels,
611 i_img_dim width, i_img_dim height) {
612 i_img * volatile vim = NULL;
613 int color_type = png_get_color_type(png_ptr, info_ptr);
614 int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
615 i_img_dim y;
616 int number_passes, pass;
617 i_img *im;
618 unsigned char *line;
619 unsigned char * volatile vline = NULL;
620 int num_palette, i;
621 png_colorp png_palette;
622 png_bytep png_pal_trans;
623 png_color_16p png_color_trans;
624 int num_pal_trans;
625
626 if (setjmp(png_jmpbuf(png_ptr))) {
627 if (vim) i_img_destroy(vim);
628 if (vline) myfree(vline);
629
630 return NULL;
631 }
632
633 number_passes = png_set_interlace_handling(png_ptr);
634 mm_log((1,"number of passes=%d\n",number_passes));
635
636 png_set_strip_16(png_ptr);
637 png_set_packing(png_ptr);
638
639 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
640 png_set_expand(png_ptr);
641
642 if (!png_get_PLTE(png_ptr, info_ptr, &png_palette, &num_palette)) {
643 i_push_error(0, "Paletted image with no PLTE chunk");
644 return NULL;
645 }
646
647 if (png_get_tRNS(png_ptr, info_ptr, &png_pal_trans, &num_pal_trans,
648 &png_color_trans)) {
649 channels++;
650 }
651 else {
652 num_pal_trans = 0;
653 }
654
655 png_read_update_info(png_ptr, info_ptr);
656
657 im = vim = i_img_pal_new(width, height, channels, 256);
658 if (!im) {
659 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
660 return NULL;
661 }
662
663 for (i = 0; i < num_palette; ++i) {
664 i_color c;
665
666 c.rgba.r = png_palette[i].red;
667 c.rgba.g = png_palette[i].green;
668 c.rgba.b = png_palette[i].blue;
669 if (i < num_pal_trans)
670 c.rgba.a = png_pal_trans[i];
671 else
672 c.rgba.a = 255;
673 i_addcolors(im, &c, 1);
674 }
675
676 line = vline = mymalloc(width);
677 for (pass = 0; pass < number_passes; pass++) {
678 for (y = 0; y < height; y++) {
679 if (pass > 0)
680 i_gpal(im, 0, width, y, line);
681 png_read_row(png_ptr,(png_bytep)line, NULL);
682 i_ppal(im, 0, width, y, line);
683 }
684 }
685 myfree(line);
686 vline = NULL;
687
688 png_read_end(png_ptr, info_ptr);
689
690 return im;
691}
692
b51bcea5
TC
693struct png_text_name {
694 const char *keyword;
695 const char *tagname;
696};
697
698static const struct png_text_name
699text_tags[] = {
700 { "Author", "png_author" },
701 { "Comment", "i_comment" },
702 { "Copyright", "png_copyright" },
703 { "Creation Time", "png_creation_time" },
704 { "Description", "png_description" },
705 { "Disclaimer", "png_disclaimer" },
706 { "Software", "png_software" },
707 { "Source", "png_source" },
708 { "Title", "png_title" },
709 { "Warning", "png_warning" }
710};
711
712static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags);
713
d0f15206
TC
714static const char * const
715chroma_tags[] = {
716 "png_chroma_white_x",
717 "png_chroma_white_y",
718 "png_chroma_red_x",
719 "png_chroma_red_y",
720 "png_chroma_green_x",
721 "png_chroma_green_y",
722 "png_chroma_blue_x",
723 "png_chroma_blue_y"
724};
725
726static const int chroma_tag_count = sizeof(chroma_tags) / sizeof(*chroma_tags);
727
38eab175 728static void
6fa6c8ee
TC
729get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr,
730 int bit_depth, int color_type) {
faa9b3e7
TC
731 png_uint_32 xres, yres;
732 int unit_type;
352c64ed 733
1d7e3124 734 i_tags_set(&im->tags, "i_format", "png", -1);
faa9b3e7 735 if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) {
5e84d110 736 mm_log((1,"pHYs (%u, %u) %d\n", (unsigned)xres, (unsigned)yres, unit_type));
faa9b3e7 737 if (unit_type == PNG_RESOLUTION_METER) {
2e41e30b
TC
738 i_tags_set_float2(&im->tags, "i_xres", 0, xres * 0.0254, 5);
739 i_tags_set_float2(&im->tags, "i_yres", 0, yres * 0.0254, 5);
faa9b3e7
TC
740 }
741 else {
1d7e3124
TC
742 i_tags_setn(&im->tags, "i_xres", xres);
743 i_tags_setn(&im->tags, "i_yres", yres);
744 i_tags_setn(&im->tags, "i_aspect_only", 1);
faa9b3e7
TC
745 }
746 }
a4fa5d5e
TC
747 {
748 int interlace = png_get_interlace_type(png_ptr, info_ptr);
749
750 i_tags_setn(&im->tags, "png_interlace", interlace != PNG_INTERLACE_NONE);
751 switch (interlace) {
752 case PNG_INTERLACE_NONE:
753 i_tags_set(&im->tags, "png_interlace_name", "none", -1);
754 break;
755
756 case PNG_INTERLACE_ADAM7:
757 i_tags_set(&im->tags, "png_interlace_name", "adam7", -1);
758 break;
759
760 default:
761 i_tags_set(&im->tags, "png_interlace_name", "unknown", -1);
762 break;
763 }
00cff942
TC
764 }
765
79b4c849
TC
766 /* the various readers can call png_set_expand(), libpng will make
767 it's internal record of bit_depth at least 8 in that case */
768 i_tags_setn(&im->tags, "png_bits", bit_depth);
b51bcea5 769
86464bbf 770 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
b51bcea5
TC
771 int intent;
772 if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
773 i_tags_setn(&im->tags, "png_srgb_intent", intent);
774 }
775 }
86464bbf
TC
776 else {
777 /* Ignore these if there's an sRGB chunk, libpng simulates
778 their existence if there's an sRGB chunk, and the PNG spec says
779 that these are ignored if the sRGB is present, so ignore them.
780 */
b51bcea5 781 double gamma;
d0f15206 782 double chroma[8];
86464bbf
TC
783
784 if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
785 i_tags_set_float2(&im->tags, "png_gamma", 0, gamma, 4);
786 }
787
d0f15206
TC
788 if (png_get_cHRM(png_ptr, info_ptr, chroma+0, chroma+1,
789 chroma+2, chroma+3, chroma+4, chroma+5,
790 chroma+6, chroma+7)) {
791 int i;
792
793 for (i = 0; i < chroma_tag_count; ++i)
794 i_tags_set_float2(&im->tags, chroma_tags[i], 0, chroma[i], 4);
b51bcea5
TC
795 }
796 }
797
798 {
799 int num_text;
800 png_text *text;
801
802 if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
803 int i;
d0f15206 804 int custom_index = 0;
b51bcea5
TC
805 for (i = 0; i < num_text; ++i) {
806 int j;
86464bbf 807 int found = 0;
d0f15206
TC
808 int compressed = text[i].compression == PNG_ITXT_COMPRESSION_zTXt
809 || text[i].compression == PNG_TEXT_COMPRESSION_zTXt;
b51bcea5
TC
810
811 for (j = 0; j < text_tags_count; ++j) {
812 if (strcmp(text_tags[j].keyword, text[i].key) == 0) {
d0f15206 813 char tag_name[50];
b51bcea5 814 i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1);
d0f15206
TC
815 if (compressed) {
816 sprintf(tag_name, "%s_compressed", text_tags[j].tagname);
817 i_tags_setn(&im->tags, tag_name, 1);
818 }
86464bbf 819 found = 1;
b51bcea5
TC
820 break;
821 }
822 }
86464bbf
TC
823
824 if (!found) {
825 char tag_name[50];
d0f15206 826 sprintf(tag_name, "png_text%d_key", custom_index);
86464bbf 827 i_tags_set(&im->tags, tag_name, text[i].key, -1);
d0f15206 828 sprintf(tag_name, "png_text%d_text", custom_index);
86464bbf 829 i_tags_set(&im->tags, tag_name, text[i].text, -1);
d0f15206 830 sprintf(tag_name, "png_text%d_type", custom_index);
86464bbf
TC
831 i_tags_set(&im->tags, tag_name,
832 (text[i].compression == PNG_TEXT_COMPRESSION_NONE
833 || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ?
834 "text" : "itxt", -1);
d0f15206
TC
835 if (compressed) {
836 sprintf(tag_name, "png_text%d_compressed", custom_index);
837 i_tags_setn(&im->tags, tag_name, 1);
838 }
839 ++custom_index;
86464bbf 840 }
b51bcea5
TC
841 }
842 }
843 }
86464bbf
TC
844
845 {
846 png_time *mod_time;
847
848 if (png_get_tIME(png_ptr, info_ptr, &mod_time)) {
849 char time_formatted[80];
850
851 sprintf(time_formatted, "%d-%02d-%02dT%02d:%02d:%02d",
852 mod_time->year, mod_time->month, mod_time->day,
853 mod_time->hour, mod_time->minute, mod_time->second);
854 i_tags_set(&im->tags, "png_time", time_formatted, -1);
855 }
856 }
6fa6c8ee
TC
857
858 {
859 png_color_16 *back;
860 i_color c;
861
862 if (png_get_bKGD(png_ptr, info_ptr, &back)) {
863 switch (color_type) {
864 case PNG_COLOR_TYPE_GRAY:
865 case PNG_COLOR_TYPE_GRAY_ALPHA:
866 {
867 /* lib png stores the raw gray value rather than scaling it
868 to 16-bit (or 8), we use 8-bit color for i_background */
869
870 int gray;
871 switch (bit_depth) {
872 case 16:
873 gray = back->gray >> 8;
874 break;
875 case 8:
876 gray = back->gray;
877 break;
878 case 4:
879 gray = 0x11 * back->gray;
880 break;
881 case 2:
882 gray = 0x55 * back->gray;
883 break;
884 case 1:
885 gray = back->gray ? 0xFF : 0;
886 break;
887 default:
888 gray = 0;
889 }
890 c.rgb.r = c.rgb.g = c.rgb.b = gray;
891 break;
892 }
893
894 case PNG_COLOR_TYPE_RGB:
895 case PNG_COLOR_TYPE_RGB_ALPHA:
896 {
897 c.rgb.r = bit_depth == 16 ? (back->red >> 8) : back->red;
898 c.rgb.g = bit_depth == 16 ? (back->green >> 8) : back->green;
899 c.rgb.b = bit_depth == 16 ? (back->blue >> 8) : back->blue;
900 break;
901 }
902
903 case PNG_COLOR_TYPE_PALETTE:
904 c.rgb.r = back->red;
905 c.rgb.g = back->green;
906 c.rgb.b = back->blue;
907 break;
908 }
909
910 c.rgba.a = 255;
911 i_tags_set_color(&im->tags, "i_background", 0, &c);
912 }
913 }
faa9b3e7 914}
38eab175 915
d0f15206
TC
916#define GET_STR_BUF_SIZE 40
917
918static int
919set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr) {
920 double xres, yres;
921 int aspect_only, have_res = 1;
922
923 if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
924 if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
925 ; /* nothing to do */
926 else
927 yres = xres;
928 }
929 else {
930 if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
931 xres = yres;
932 else
933 have_res = 0;
934 }
935 if (have_res) {
936 aspect_only = 0;
937 i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only);
938 xres /= 0.0254;
939 yres /= 0.0254;
940 png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5,
941 aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
942 }
943
944 {
945 int intent;
946 if (i_tags_get_int(&im->tags, "png_srgb_intent", 0, &intent)) {
947 if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) {
948 i_push_error(0, "tag png_srgb_intent out of range");
949 return 0;
950 }
951 png_set_sRGB(png_ptr, info_ptr, intent);
952 }
953 else {
954 double chroma[8], gamma;
955 int i;
956 int found_chroma_count = 0;
957
958 for (i = 0; i < chroma_tag_count; ++i) {
959 if (i_tags_get_float(&im->tags, chroma_tags[i], 0, chroma+i))
960 ++found_chroma_count;
961 }
962
963 if (found_chroma_count) {
964 if (found_chroma_count != chroma_tag_count) {
965 i_push_error(0, "all png_chroma_* tags must be supplied or none");
966 return 0;
967 }
968
969 png_set_cHRM(png_ptr, info_ptr, chroma[0], chroma[1], chroma[2],
970 chroma[3], chroma[4], chroma[5], chroma[6], chroma[7]);
971 }
972
973 if (i_tags_get_float(&im->tags, "png_gamma", 0, &gamma)) {
974 png_set_gAMA(png_ptr, info_ptr, gamma);
975 }
976 }
977 }
978
979 {
980 /* png_set_text() is sparsely documented, it isn't indicated whether
981 multiple calls add to or replace the lists of texts, and
982 whether the text/keyword data is copied or not.
983
984 Examining the linpng code reveals that png_set_text() adds to
985 the list and that the text is copied.
986 */
987 int i;
988
989 /* do our standard tags */
990 for (i = 0; i < text_tags_count; ++i) {
991 char buf[GET_STR_BUF_SIZE];
992 size_t size;
993 const char *data;
994
995 data = get_string2(&im->tags, text_tags[i].tagname, buf, &size);
996 if (data) {
997 png_text text;
998 int compression = size > 1000;
999 char compress_tag[40];
1000
1001 if (memchr(data, '\0', size)) {
1002 i_push_errorf(0, "tag %s may not contain NUL characters", text_tags[i].tagname);
1003 return 0;
1004 }
1005
1006 sprintf(compress_tag, "%s_compressed", text_tags[i].tagname);
1007 i_tags_get_int(&im->tags, compress_tag, 0, &compression);
1008
1009 text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt
1010 : PNG_TEXT_COMPRESSION_NONE;
1011 text.key = (char *)text_tags[i].keyword;
1012 text.text_length = size;
1013 text.text = (char *)data;
1014#ifdef PNG_iTXt_SUPPORTED
1015 text.itxt_length = 0;
1016 text.lang = NULL;
1017 text.lang_key = NULL;
1018#endif
1019
1020 png_set_text(png_ptr, info_ptr, &text, 1);
1021 }
1022 }
1023
1024 /* for non-standard tags ensure keywords are limited to 1 to 79
1025 characters */
1026 i = 0;
1027 while (1) {
1028 char tag_name[50];
1029 char key_buf[GET_STR_BUF_SIZE], value_buf[GET_STR_BUF_SIZE];
1030 const char *key, *value;
1031 size_t key_size, value_size;
1032
1033 sprintf(tag_name, "png_text%d_key", i);
1034 key = get_string2(&im->tags, tag_name, key_buf, &key_size);
1035
1036 if (key) {
1037 size_t k;
1038 if (key_size < 1 || key_size > 79) {
1039 i_push_errorf(0, "tag %s must be between 1 and 79 characters in length", tag_name);
1040 return 0;
1041 }
1042
1043 if (key[0] == ' ' || key[key_size-1] == ' ') {
1044 i_push_errorf(0, "tag %s may not contain leading or trailing spaces", tag_name);
1045 return 0;
1046 }
1047
1048 if (strstr(key, " ")) {
1049 i_push_errorf(0, "tag %s may not contain consecutive spaces", tag_name);
1050 return 0;
1051 }
1052
1053 for (k = 0; k < key_size; ++k) {
0a53238e 1054 if (key[k] < 32 || (key[k] > 126 && key[k] < 161)) {
d0f15206
TC
1055 i_push_errorf(0, "tag %s may only contain Latin1 characters 32-126, 161-255", tag_name);
1056 return 0;
1057 }
1058 }
1059 }
1060
1061 sprintf(tag_name, "png_text%d_text", i);
1062 value = get_string2(&im->tags, tag_name, value_buf, &value_size);
1063
1064 if (value) {
1065 if (memchr(value, '\0', value_size)) {
1066 i_push_errorf(0, "tag %s may not contain NUL characters", tag_name);
1067 return 0;
1068 }
1069 }
1070
1071 if (key && value) {
1072 png_text text;
1073 int compression = value_size > 1000;
1074
1075 sprintf(tag_name, "png_text%d_compressed", i);
1076 i_tags_get_int(&im->tags, tag_name, 0, &compression);
1077
1078 text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt
1079 : PNG_TEXT_COMPRESSION_NONE;
1080 text.key = (char *)key;
1081 text.text_length = value_size;
1082 text.text = (char *)value;
1083#ifdef PNG_iTXt_SUPPORTED
1084 text.itxt_length = 0;
1085 text.lang = NULL;
1086 text.lang_key = NULL;
1087#endif
1088
1089 png_set_text(png_ptr, info_ptr, &text, 1);
1090 }
1091 else if (key) {
1092 i_push_errorf(0, "tag png_text%d_key found but not png_text%d_text", i, i);
1093 return 0;
1094 }
1095 else if (value) {
1096 i_push_errorf(0, "tag png_text%d_text found but not png_text%d_key", i, i);
1097 return 0;
1098 }
1099 else {
1100 break;
1101 }
1102 ++i;
1103 }
1104 }
1105
1106 {
1107 char buf[GET_STR_BUF_SIZE];
1108 size_t time_size;
1109 const char *timestr = get_string2(&im->tags, "png_time", buf, &time_size);
1110
1111 if (timestr) {
1112 int year, month, day, hour, minute, second;
1113 png_time mod_time;
1114
1115 if (sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) {
1116 /* rough validation */
1117 if (month < 1 || month > 12
1118 || day < 1 || day > 31
1119 || hour < 0 || hour > 23
1120 || minute < 0 || minute > 59
1121 || second < 0 || second > 60) {
1122 i_push_error(0, "invalid date/time for png_time");
1123 return 0;
1124 }
1125 mod_time.year = year;
1126 mod_time.month = month;
1127 mod_time.day = day;
1128 mod_time.hour = hour;
1129 mod_time.minute = minute;
1130 mod_time.second = second;
1131
1132 png_set_tIME(png_ptr, info_ptr, &mod_time);
1133 }
1134 else {
1135 i_push_error(0, "png_time must be formatted 'y-m-dTh:m:s'");
1136 return 0;
1137 }
1138 }
1139 }
1140
28d08bb1
TC
1141 {
1142 int level;
1143 if (i_tags_get_int(&im->tags, "png_compression_level", 0, &level)) {
1144 if (level >= Z_NO_COMPRESSION && level <= Z_BEST_COMPRESSION)
1145 png_set_compression_level(png_ptr, level);
1146 else {
1147 i_push_errorf(0, "png_compression_level must be between %d and %d",
1148 Z_NO_COMPRESSION, Z_BEST_COMPRESSION);
1149 return 0;
1150 }
1151 }
1152 }
1153
d0f15206
TC
1154 {
1155 /* no bKGD support yet, maybe later
1156 it may be simpler to do it in the individual writers
1157 */
1158 }
1159
1160 return 1;
1161}
1162
1163static const char *
1164get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size) {
1165 int index;
1166
1167 if (i_tags_find(tags, name, 0, &index)) {
1168 const i_img_tag *entry = tags->tags + index;
1169
1170 if (entry->data) {
1171 *size = entry->size;
1172
1173 return entry->data;
1174 }
1175 else {
1176 *size = sprintf(buf, "%d", entry->idata);
1177
1178 return buf;
1179 }
1180 }
1181 return NULL;
1182}
1183
81089826
TC
1184static int
1185write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1186 unsigned char *data, *volatile vdata = NULL;
1187 i_img_dim y;
1188
1189 if (setjmp(png_jmpbuf(png_ptr))) {
1190 if (vdata)
1191 myfree(vdata);
1192
1193 return 0;
1194 }
1195
21c6936b
TC
1196 png_write_info(png_ptr, info_ptr);
1197
81089826
TC
1198 vdata = data = mymalloc(im->xsize * im->channels);
1199 for (y = 0; y < im->ysize; y++) {
1200 i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
1201 png_write_row(png_ptr, (png_bytep)data);
1202 }
1203 myfree(data);
1204
1205 return 1;
1206}
1207
d9610331
TC
1208static int
1209write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1210 unsigned *data, *volatile vdata = NULL;
1211 unsigned char *tran_data, * volatile vtran_data = NULL;
1212 i_img_dim samples_per_row = im->xsize * im->channels;
1213
1214 i_img_dim y;
1215
1216 if (setjmp(png_jmpbuf(png_ptr))) {
1217 if (vdata)
1218 myfree(vdata);
1219 if (vtran_data)
1220 myfree(vtran_data);
1221
1222 return 0;
1223 }
1224
1225 png_write_info(png_ptr, info_ptr);
1226
1227 vdata = data = mymalloc(samples_per_row * sizeof(unsigned));
1228 vtran_data = tran_data = mymalloc(samples_per_row * 2);
1229 for (y = 0; y < im->ysize; y++) {
1230 i_img_dim i;
1231 unsigned char *p = tran_data;
1232 i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16);
1233 for (i = 0; i < samples_per_row; ++i) {
1234 p[0] = data[i] >> 8;
1235 p[1] = data[i] & 0xff;
1236 p += 2;
1237 }
1238 png_write_row(png_ptr, (png_bytep)tran_data);
1239 }
1240 myfree(tran_data);
1241 myfree(data);
1242
1243 return 1;
1244}
1245
21c6936b
TC
1246static int
1247write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) {
1248 unsigned char *data, *volatile vdata = NULL;
1249 i_img_dim y;
1250 unsigned char pal_map[256];
1251 png_color pcolors[256];
1252 i_color colors[256];
1253 int count = i_colorcount(im);
1254 int i;
1255
1256 if (setjmp(png_jmpbuf(png_ptr))) {
1257 if (vdata)
1258 myfree(vdata);
1259
1260 return 0;
1261 }
1262
1263 i_getcolors(im, 0, colors, count);
1264 if (im->channels < 3) {
1265 /* convert the greyscale palette to color */
1266 int i;
1267 for (i = 0; i < count; ++i) {
1268 i_color *c = colors + i;
1269 c->channel[3] = c->channel[1];
1270 c->channel[2] = c->channel[1] = c->channel[0];
1271 }
1272 }
1273
1274 if (i_img_has_alpha(im)) {
1275 int i;
1276 int bottom_index = 0, top_index = count-1;
1277
1278 /* fill out the palette map */
1279 for (i = 0; i < count; ++i)
1280 pal_map[i] = i;
1281
1282 /* the PNG spec suggests sorting the palette by alpha, but that's
1283 unnecessary - all we want to do is move the opaque entries to
1284 the end */
1285 while (bottom_index < top_index) {
1286 if (colors[bottom_index].rgba.a == 255) {
1287 pal_map[bottom_index] = top_index;
1288 pal_map[top_index--] = bottom_index;
1289 }
1290 ++bottom_index;
1291 }
1292 }
1293
1294 for (i = 0; i < count; ++i) {
1295 int srci = i_img_has_alpha(im) ? pal_map[i] : i;
1296
1297 pcolors[i].red = colors[srci].rgb.r;
1298 pcolors[i].green = colors[srci].rgb.g;
1299 pcolors[i].blue = colors[srci].rgb.b;
1300 }
1301
1302 png_set_PLTE(png_ptr, info_ptr, pcolors, count);
1303
1304 if (i_img_has_alpha(im)) {
1305 unsigned char trans[256];
1306 int i;
1307
1308 for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) {
1309 trans[i] = colors[pal_map[i]].rgba.a;
1310 }
1311 png_set_tRNS(png_ptr, info_ptr, trans, i, NULL);
1312 }
1313
1314 png_write_info(png_ptr, info_ptr);
1315
1316 png_set_packing(png_ptr);
1317
1318 vdata = data = mymalloc(im->xsize);
1319 for (y = 0; y < im->ysize; y++) {
1320 i_gpal(im, 0, im->xsize, y, data);
1321 if (i_img_has_alpha(im)) {
1322 i_img_dim x;
1323 for (x = 0; x < im->xsize; ++x)
1324 data[x] = pal_map[data[x]];
1325 }
1326 png_write_row(png_ptr, (png_bytep)data);
1327 }
1328 myfree(data);
1329
1330 return 1;
1331}
1332
b8961d05
TC
1333static int
1334write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1335 unsigned char *data, *volatile vdata = NULL;
1336 i_img_dim y;
21c6936b 1337
b8961d05
TC
1338 if (setjmp(png_jmpbuf(png_ptr))) {
1339 if (vdata)
1340 myfree(vdata);
1341
1342 return 0;
21c6936b 1343 }
21c6936b 1344
b8961d05
TC
1345 png_write_info(png_ptr, info_ptr);
1346
1347 png_set_packing(png_ptr);
1348
1349 vdata = data = mymalloc(im->xsize);
1350 for (y = 0; y < im->ysize; y++) {
1351 i_gsamp(im, 0, im->xsize, y, data, NULL, 1);
1352 png_write_row(png_ptr, (png_bytep)data);
1353 }
1354 myfree(data);
1355
1356 return 1;
1357}
21c6936b 1358
38eab175
TC
1359static void
1360read_warn_handler(png_structp png_ptr, png_const_charp msg) {
1361 i_png_read_statep rs = (i_png_read_statep)png_get_error_ptr(png_ptr);
1362 char *workp;
1363 size_t new_size;
1364
1365 mm_log((1, "PNG read warning '%s'\n", msg));
1366
1367 /* in case this is part of an error report */
1368 i_push_error(0, msg);
1369
1370 /* and save in the warnings so if we do manage to succeed, we
1371 * can save it as a tag
1372 */
1373 new_size = (rs->warnings ? strlen(rs->warnings) : 0)
1374 + 1 /* NUL */
1375 + strlen(msg) /* new text */
1376 + 1; /* newline */
1377 workp = myrealloc(rs->warnings, new_size);
1378 if (!rs->warnings)
1379 *workp = '\0';
1380 strcat(workp, msg);
1381 strcat(workp, "\n");
1382 rs->warnings = workp;
1383}
1384
1385static void
1386cleanup_read_state(i_png_read_statep rs) {
1387 if (rs->warnings)
1388 myfree(rs->warnings);
1389}