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