477a49199ae6400de42823b4907e66545eeb8fbe
[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,
320           "png_get_IHDR results: width %d, height %d, bit_depth %d, color_type %d, interlace_type %d\n",
321           width,height,bit_depth,color_type,interlace_type));
322   
323   CC2C[PNG_COLOR_TYPE_GRAY]=1;
324   CC2C[PNG_COLOR_TYPE_PALETTE]=3;
325   CC2C[PNG_COLOR_TYPE_RGB]=3;
326   CC2C[PNG_COLOR_TYPE_RGB_ALPHA]=4;
327   CC2C[PNG_COLOR_TYPE_GRAY_ALPHA]=2;
328   channels = CC2C[color_type];
329
330   mm_log((1,"i_readpng_wiol: channels %d\n",channels));
331
332   if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
333     mm_log((1, "i_readpnm: image size exceeds limits\n"));
334     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
335     return NULL;
336   }
337
338   if (color_type == PNG_COLOR_TYPE_PALETTE) {
339     im = read_paletted(png_ptr, info_ptr, channels, width, height);
340   }
341   else if (color_type == PNG_COLOR_TYPE_GRAY
342            && bit_depth == 1
343            && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
344     im = read_bilevel(png_ptr, info_ptr, width, height);
345   }
346   else if (bit_depth == 16) {
347     im = read_direct16(png_ptr, info_ptr, channels, width, height);
348   }
349   else {
350     im = read_direct8(png_ptr, info_ptr, channels, width, height);
351   }
352
353   if (im)
354     get_png_tags(im, png_ptr, info_ptr, bit_depth, color_type);
355
356   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
357
358   if (im) {
359     if (rs.warnings) {
360       i_tags_set(&im->tags, "png_warnings", rs.warnings, -1);
361     }
362   }
363   cleanup_read_state(&rs);
364   
365   mm_log((1,"(%p) <- i_readpng_wiol\n", im));  
366   
367   return im;
368 }
369
370 static i_img *
371 read_direct8(png_structp png_ptr, png_infop info_ptr, int channels,
372              i_img_dim width, i_img_dim height) {
373   i_img * volatile vim = NULL;
374   int color_type = png_get_color_type(png_ptr, info_ptr);
375   int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
376   i_img_dim y;
377   int number_passes, pass;
378   i_img *im;
379   unsigned char *line;
380   unsigned char * volatile vline = NULL;
381
382   if (setjmp(png_jmpbuf(png_ptr))) {
383     if (vim) i_img_destroy(vim);
384     if (vline) myfree(vline);
385
386     return NULL;
387   }
388
389   number_passes = png_set_interlace_handling(png_ptr);
390   mm_log((1,"number of passes=%d\n",number_passes));
391
392   png_set_strip_16(png_ptr);
393   png_set_packing(png_ptr);
394
395   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
396     png_set_expand(png_ptr);
397     
398   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
399     channels++;
400     mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
401     png_set_expand(png_ptr);
402   }
403   
404   png_read_update_info(png_ptr, info_ptr);
405   
406   im = vim = i_img_8_new(width,height,channels);
407   if (!im) {
408     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
409     return NULL;
410   }
411   
412   line = vline = mymalloc(channels * width);
413   for (pass = 0; pass < number_passes; pass++) {
414     for (y = 0; y < height; y++) {
415       if (pass > 0)
416         i_gsamp(im, 0, width, y, line, NULL, channels);
417       png_read_row(png_ptr,(png_bytep)line, NULL);
418       i_psamp(im, 0, width, y, line, NULL, channels);
419     }
420   }
421   myfree(line);
422   vline = NULL;
423   
424   png_read_end(png_ptr, info_ptr); 
425
426   return im;
427 }
428
429 static i_img *
430 read_direct16(png_structp png_ptr, png_infop info_ptr, int channels,
431              i_img_dim width, i_img_dim height) {
432   i_img * volatile vim = NULL;
433   i_img_dim x, y;
434   int number_passes, pass;
435   i_img *im;
436   unsigned char *line;
437   unsigned char * volatile vline = NULL;
438   unsigned *bits_line;
439   unsigned * volatile vbits_line = NULL;
440   size_t row_bytes;
441
442   if (setjmp(png_jmpbuf(png_ptr))) {
443     if (vim) i_img_destroy(vim);
444     if (vline) myfree(vline);
445     if (vbits_line) myfree(vbits_line);
446
447     return NULL;
448   }
449
450   number_passes = png_set_interlace_handling(png_ptr);
451   mm_log((1,"number of passes=%d\n",number_passes));
452
453   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
454     channels++;
455     mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
456     png_set_expand(png_ptr);
457   }
458   
459   png_read_update_info(png_ptr, info_ptr);
460   
461   im = vim = i_img_16_new(width,height,channels);
462   if (!im) {
463     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
464     return NULL;
465   }
466   
467   row_bytes = png_get_rowbytes(png_ptr, info_ptr);
468   line = vline = mymalloc(row_bytes);
469   memset(line, 0, row_bytes);
470   bits_line = vbits_line = mymalloc(sizeof(unsigned) * width * channels);
471   for (pass = 0; pass < number_passes; pass++) {
472     for (y = 0; y < height; y++) {
473       if (pass > 0) {
474         i_gsamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
475         for (x = 0; x < width * channels; ++x) {
476           line[x*2] = bits_line[x] >> 8;
477           line[x*2+1] = bits_line[x] & 0xff;
478         }
479       }
480       png_read_row(png_ptr,(png_bytep)line, NULL);
481       for (x = 0; x < width * channels; ++x)
482         bits_line[x] = (line[x*2] << 8) + line[x*2+1];
483       i_psamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
484     }
485   }
486   myfree(line);
487   myfree(bits_line);
488   vline = NULL;
489   vbits_line = NULL;
490   
491   png_read_end(png_ptr, info_ptr); 
492
493   return im;
494 }
495
496 static i_img *
497 read_bilevel(png_structp png_ptr, png_infop info_ptr,
498              i_img_dim width, i_img_dim height) {
499   i_img * volatile vim = NULL;
500   i_img_dim x, y;
501   int number_passes, pass;
502   i_img *im;
503   unsigned char *line;
504   unsigned char * volatile vline = NULL;
505   i_color palette[2];
506
507   if (setjmp(png_jmpbuf(png_ptr))) {
508     if (vim) i_img_destroy(vim);
509     if (vline) myfree(vline);
510
511     return NULL;
512   }
513
514   number_passes = png_set_interlace_handling(png_ptr);
515   mm_log((1,"number of passes=%d\n",number_passes));
516
517   png_set_packing(png_ptr);
518
519   png_set_expand(png_ptr);  
520   
521   png_read_update_info(png_ptr, info_ptr);
522   
523   im = vim = i_img_pal_new(width, height, 1, 256);
524   if (!im) {
525     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
526     return NULL;
527   }
528
529   palette[0].channel[0] = palette[0].channel[1] = palette[0].channel[2] = 
530     palette[0].channel[3] = 0;
531   palette[1].channel[0] = palette[1].channel[1] = palette[1].channel[2] = 
532     palette[1].channel[3] = 255;
533   i_addcolors(im, palette, 2);
534   
535   line = vline = mymalloc(width);
536   memset(line, 0, width);
537   for (pass = 0; pass < number_passes; pass++) {
538     for (y = 0; y < height; y++) {
539       if (pass > 0) {
540         i_gpal(im, 0, width, y, line);
541         /* expand indexes back to 0/255 */
542         for (x = 0; x < width; ++x)
543           line[x] = line[x] ? 255 : 0;
544       }
545       png_read_row(png_ptr,(png_bytep)line, NULL);
546
547       /* back to palette indexes */
548       for (x = 0; x < width; ++x)
549         line[x] = line[x] ? 1 : 0;
550       i_ppal(im, 0, width, y, line);
551     }
552   }
553   myfree(line);
554   vline = NULL;
555   
556   png_read_end(png_ptr, info_ptr); 
557
558   return im;
559 }
560
561 /* FIXME: do we need to unscale palette color values from the 
562    supplied alphas? */
563 static i_img *
564 read_paletted(png_structp png_ptr, png_infop info_ptr, int channels,
565               i_img_dim width, i_img_dim height) {
566   i_img * volatile vim = NULL;
567   int color_type = png_get_color_type(png_ptr, info_ptr);
568   int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
569   i_img_dim y;
570   int number_passes, pass;
571   i_img *im;
572   unsigned char *line;
573   unsigned char * volatile vline = NULL;
574   int num_palette, i;
575   png_colorp png_palette;
576   png_bytep png_pal_trans;
577   png_color_16p png_color_trans;
578   int num_pal_trans;
579
580   if (setjmp(png_jmpbuf(png_ptr))) {
581     if (vim) i_img_destroy(vim);
582     if (vline) myfree(vline);
583
584     return NULL;
585   }
586
587   number_passes = png_set_interlace_handling(png_ptr);
588   mm_log((1,"number of passes=%d\n",number_passes));
589
590   png_set_strip_16(png_ptr);
591   png_set_packing(png_ptr);
592
593   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
594     png_set_expand(png_ptr);
595     
596   if (!png_get_PLTE(png_ptr, info_ptr, &png_palette, &num_palette)) {
597     i_push_error(0, "Paletted image with no PLTE chunk");
598     return NULL;
599   }
600
601   if (png_get_tRNS(png_ptr, info_ptr, &png_pal_trans, &num_pal_trans,
602                    &png_color_trans)) {
603     channels++;
604   }
605   else {
606     num_pal_trans = 0;
607   }
608   
609   png_read_update_info(png_ptr, info_ptr);
610   
611   im = vim = i_img_pal_new(width, height, channels, 256);
612   if (!im) {
613     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
614     return NULL;
615   }
616
617   for (i = 0; i < num_palette; ++i) {
618     i_color c;
619
620     c.rgba.r = png_palette[i].red;
621     c.rgba.g = png_palette[i].green;
622     c.rgba.b = png_palette[i].blue;
623     if (i < num_pal_trans)
624       c.rgba.a = png_pal_trans[i];
625     else
626       c.rgba.a = 255;
627     i_addcolors(im, &c, 1);
628   }
629
630   line = vline = mymalloc(width);
631   for (pass = 0; pass < number_passes; pass++) {
632     for (y = 0; y < height; y++) {
633       if (pass > 0)
634         i_gpal(im, 0, width, y, line);
635       png_read_row(png_ptr,(png_bytep)line, NULL);
636       i_ppal(im, 0, width, y, line);
637     }
638   }
639   myfree(line);
640   vline = NULL;
641   
642   png_read_end(png_ptr, info_ptr); 
643
644   return im;
645 }
646
647 struct png_text_name {
648   const char *keyword;
649   const char *tagname;
650 };
651
652 static const struct png_text_name
653 text_tags[] = {
654   { "Author", "png_author" },
655   { "Comment", "i_comment" },
656   { "Copyright", "png_copyright" },
657   { "Creation Time", "png_creation_time" },
658   { "Description", "png_description" },
659   { "Disclaimer", "png_disclaimer" },
660   { "Software", "png_software" },
661   { "Source", "png_source" },
662   { "Title", "png_title" },
663   { "Warning", "png_warning" }
664 };
665
666 static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags);
667
668 static const char * const
669 chroma_tags[] = {
670   "png_chroma_white_x",
671   "png_chroma_white_y",
672   "png_chroma_red_x",
673   "png_chroma_red_y",
674   "png_chroma_green_x",
675   "png_chroma_green_y",
676   "png_chroma_blue_x",
677   "png_chroma_blue_y"
678 };
679
680 static const int chroma_tag_count = sizeof(chroma_tags) / sizeof(*chroma_tags);
681
682 static void
683 get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr,
684              int bit_depth, int color_type) {
685   png_uint_32 xres, yres;
686   int unit_type;
687
688   i_tags_set(&im->tags, "i_format", "png", -1);
689   if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) {
690     mm_log((1,"pHYs (%d, %d) %d\n", xres, yres, unit_type));
691     if (unit_type == PNG_RESOLUTION_METER) {
692       i_tags_set_float2(&im->tags, "i_xres", 0, xres * 0.0254, 5);
693       i_tags_set_float2(&im->tags, "i_yres", 0, yres * 0.0254, 5);
694     }
695     else {
696       i_tags_setn(&im->tags, "i_xres", xres);
697       i_tags_setn(&im->tags, "i_yres", yres);
698       i_tags_setn(&im->tags, "i_aspect_only", 1);
699     }
700   }
701   {
702     int interlace = png_get_interlace_type(png_ptr, info_ptr);
703
704     i_tags_setn(&im->tags, "png_interlace", interlace != PNG_INTERLACE_NONE);
705     switch (interlace) {
706     case PNG_INTERLACE_NONE:
707       i_tags_set(&im->tags, "png_interlace_name", "none", -1);
708       break;
709       
710     case PNG_INTERLACE_ADAM7:
711       i_tags_set(&im->tags, "png_interlace_name", "adam7", -1);
712       break;
713       
714     default:
715       i_tags_set(&im->tags, "png_interlace_name", "unknown", -1);
716       break;
717     }
718   }
719
720   /* the various readers can call png_set_expand(), libpng will make
721      it's internal record of bit_depth at least 8 in that case */
722   i_tags_setn(&im->tags, "png_bits", bit_depth);
723   
724   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
725     int intent;
726     if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
727       i_tags_setn(&im->tags, "png_srgb_intent", intent);
728     }
729   }
730   else {
731     /* Ignore these if there's an sRGB chunk, libpng simulates
732        their existence if there's an sRGB chunk, and the PNG spec says
733        that these are ignored if the sRGB is present, so ignore them.
734     */
735     double gamma;
736     double chroma[8];
737
738     if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
739       i_tags_set_float2(&im->tags, "png_gamma", 0, gamma, 4);
740     }
741
742     if (png_get_cHRM(png_ptr, info_ptr, chroma+0, chroma+1,
743                      chroma+2, chroma+3, chroma+4, chroma+5,
744                      chroma+6, chroma+7)) {
745       int i;
746
747       for (i = 0; i < chroma_tag_count; ++i)
748         i_tags_set_float2(&im->tags, chroma_tags[i], 0, chroma[i], 4);
749     }
750   }
751
752   {
753     int num_text;
754     png_text *text;
755
756     if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
757       int i;
758       int custom_index = 0;
759       for (i = 0; i < num_text; ++i) {
760         int j;
761         int found = 0;
762         int compressed = text[i].compression == PNG_ITXT_COMPRESSION_zTXt
763           || text[i].compression == PNG_TEXT_COMPRESSION_zTXt;
764
765         for (j = 0; j < text_tags_count; ++j) {
766           if (strcmp(text_tags[j].keyword, text[i].key) == 0) {
767             char tag_name[50];
768             i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1);
769             if (compressed) {
770               sprintf(tag_name, "%s_compressed", text_tags[j].tagname);
771               i_tags_setn(&im->tags, tag_name, 1);
772             }
773             found = 1;
774             break;
775           }
776         }
777
778         if (!found) {
779           char tag_name[50];
780           sprintf(tag_name, "png_text%d_key", custom_index);
781           i_tags_set(&im->tags, tag_name, text[i].key, -1);
782           sprintf(tag_name, "png_text%d_text", custom_index);
783           i_tags_set(&im->tags, tag_name, text[i].text, -1);
784           sprintf(tag_name, "png_text%d_type", custom_index);
785           i_tags_set(&im->tags, tag_name, 
786                      (text[i].compression == PNG_TEXT_COMPRESSION_NONE
787                       || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ?
788                      "text" : "itxt", -1);
789           if (compressed) {
790             sprintf(tag_name, "png_text%d_compressed", custom_index);
791             i_tags_setn(&im->tags, tag_name, 1);
792           }
793           ++custom_index;
794         }
795       }
796     }
797   }
798
799   {
800     png_time *mod_time;
801
802     if (png_get_tIME(png_ptr, info_ptr, &mod_time)) {
803       char time_formatted[80];
804
805       sprintf(time_formatted, "%d-%02d-%02dT%02d:%02d:%02d",
806               mod_time->year, mod_time->month, mod_time->day,
807               mod_time->hour, mod_time->minute, mod_time->second);
808       i_tags_set(&im->tags, "png_time", time_formatted, -1);
809     }
810   }
811
812   {
813     png_color_16 *back;
814     i_color c;
815
816     if (png_get_bKGD(png_ptr, info_ptr, &back)) {
817       switch (color_type) {
818       case PNG_COLOR_TYPE_GRAY:
819       case PNG_COLOR_TYPE_GRAY_ALPHA:
820         {
821           /* lib png stores the raw gray value rather than scaling it
822              to 16-bit (or 8), we use 8-bit color for i_background */
823
824           int gray;
825           switch (bit_depth) {
826           case 16:
827             gray = back->gray >> 8;
828             break;
829           case 8:
830             gray = back->gray;
831             break;
832           case 4:
833             gray = 0x11 * back->gray;
834             break;
835           case 2:
836             gray = 0x55 * back->gray;
837             break;
838           case 1:
839             gray = back->gray ? 0xFF : 0;
840             break;
841           default:
842             gray = 0;
843           }
844           c.rgb.r = c.rgb.g = c.rgb.b = gray;
845           break;
846         }
847
848       case PNG_COLOR_TYPE_RGB:
849       case PNG_COLOR_TYPE_RGB_ALPHA:
850         {
851           c.rgb.r = bit_depth == 16 ? (back->red   >> 8) : back->red;
852           c.rgb.g = bit_depth == 16 ? (back->green >> 8) : back->green;
853           c.rgb.b = bit_depth == 16 ? (back->blue  >> 8) : back->blue;
854           break;
855         }
856
857       case PNG_COLOR_TYPE_PALETTE:
858         c.rgb.r = back->red;
859         c.rgb.g = back->green;
860         c.rgb.b = back->blue;
861         break;
862       }
863
864       c.rgba.a = 255;
865       i_tags_set_color(&im->tags, "i_background", 0, &c);
866     }
867   }
868 }
869
870 #define GET_STR_BUF_SIZE 40
871
872 static int
873 set_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr) {
874   double xres, yres;
875   int aspect_only, have_res = 1;
876
877   if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
878     if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
879       ; /* nothing to do */
880     else
881       yres = xres;
882   }
883   else {
884     if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
885       xres = yres;
886     else
887       have_res = 0;
888   }
889   if (have_res) {
890     aspect_only = 0;
891     i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only);
892     xres /= 0.0254;
893     yres /= 0.0254;
894     png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, 
895                  aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
896   }
897
898   {
899     int intent;
900     if (i_tags_get_int(&im->tags, "png_srgb_intent", 0, &intent)) {
901       if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) {
902         i_push_error(0, "tag png_srgb_intent out of range");
903         return 0;
904       }
905       png_set_sRGB(png_ptr, info_ptr, intent);
906     }
907     else {
908       double chroma[8], gamma;
909       int i;
910       int found_chroma_count = 0;
911
912       for (i = 0; i < chroma_tag_count; ++i) {
913         if (i_tags_get_float(&im->tags, chroma_tags[i], 0, chroma+i))
914           ++found_chroma_count;
915       }
916
917       if (found_chroma_count) {
918         if (found_chroma_count != chroma_tag_count) {
919           i_push_error(0, "all png_chroma_* tags must be supplied or none");
920           return 0;
921         }
922
923         png_set_cHRM(png_ptr, info_ptr, chroma[0], chroma[1], chroma[2],
924                      chroma[3], chroma[4], chroma[5], chroma[6], chroma[7]);
925       }
926
927       if (i_tags_get_float(&im->tags, "png_gamma", 0, &gamma)) {
928         png_set_gAMA(png_ptr, info_ptr, gamma);
929       }
930     }
931   }
932
933   {
934     /* png_set_text() is sparsely documented, it isn't indicated whether
935        multiple calls add to or replace the lists of texts, and
936        whether the text/keyword data is copied or not.
937
938        Examining the linpng code reveals that png_set_text() adds to
939        the list and that the text is copied.
940     */
941     int i;
942
943     /* do our standard tags */
944     for (i = 0; i < text_tags_count; ++i) {
945       char buf[GET_STR_BUF_SIZE];
946       size_t size;
947       const char *data;
948       
949       data = get_string2(&im->tags, text_tags[i].tagname, buf, &size);
950       if (data) {
951         png_text text;
952         int compression = size > 1000;
953         char compress_tag[40];
954
955         if (memchr(data, '\0',  size)) {
956           i_push_errorf(0, "tag %s may not contain NUL characters", text_tags[i].tagname);
957           return 0;
958         }
959       
960         sprintf(compress_tag, "%s_compressed", text_tags[i].tagname);
961         i_tags_get_int(&im->tags, compress_tag, 0, &compression);
962         
963         text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt
964           : PNG_TEXT_COMPRESSION_NONE;
965         text.key = (char *)text_tags[i].keyword;
966         text.text_length = size;
967         text.text = (char *)data;
968 #ifdef PNG_iTXt_SUPPORTED
969         text.itxt_length = 0;
970         text.lang = NULL;
971         text.lang_key = NULL;
972 #endif
973
974         png_set_text(png_ptr, info_ptr, &text, 1);
975       }
976     }
977
978     /* for non-standard tags ensure keywords are limited to 1 to 79
979        characters */
980     i = 0;
981     while (1) {
982       char tag_name[50];
983       char key_buf[GET_STR_BUF_SIZE], value_buf[GET_STR_BUF_SIZE];
984       const char *key, *value;
985       size_t key_size, value_size;
986
987       sprintf(tag_name, "png_text%d_key", i);
988       key = get_string2(&im->tags, tag_name, key_buf, &key_size);
989       
990       if (key) {
991         size_t k;
992         if (key_size < 1 || key_size > 79) {
993           i_push_errorf(0, "tag %s must be between 1 and 79 characters in length", tag_name);
994           return 0;
995         }
996
997         if (key[0] == ' ' || key[key_size-1] == ' ') {
998           i_push_errorf(0, "tag %s may not contain leading or trailing spaces", tag_name);
999           return 0;
1000         }
1001
1002         if (strstr(key, "  ")) {
1003           i_push_errorf(0, "tag %s may not contain consecutive spaces", tag_name);
1004           return 0;
1005         }
1006
1007         for (k = 0; k < key_size; ++k) {
1008           if (key[k] < 32 || (key[k] > 126 && key[k] < 161)) {
1009             i_push_errorf(0, "tag %s may only contain Latin1 characters 32-126, 161-255", tag_name);
1010             return 0;
1011           }
1012         }
1013       }
1014
1015       sprintf(tag_name, "png_text%d_text", i);
1016       value = get_string2(&im->tags, tag_name, value_buf, &value_size);
1017
1018       if (value) {
1019         if (memchr(value, '\0', value_size)) {
1020           i_push_errorf(0, "tag %s may not contain NUL characters", tag_name);
1021           return 0;
1022         }
1023       }
1024
1025       if (key && value) {
1026         png_text text;
1027         int compression = value_size > 1000;
1028
1029         sprintf(tag_name, "png_text%d_compressed", i);
1030         i_tags_get_int(&im->tags, tag_name, 0, &compression);
1031
1032         text.compression = compression ? PNG_TEXT_COMPRESSION_zTXt
1033           : PNG_TEXT_COMPRESSION_NONE;
1034         text.key = (char *)key;
1035         text.text_length = value_size;
1036         text.text = (char *)value;
1037 #ifdef PNG_iTXt_SUPPORTED
1038         text.itxt_length = 0;
1039         text.lang = NULL;
1040         text.lang_key = NULL;
1041 #endif
1042
1043         png_set_text(png_ptr, info_ptr, &text, 1);
1044       }
1045       else if (key) {
1046         i_push_errorf(0, "tag png_text%d_key found but not png_text%d_text", i, i);
1047         return 0;
1048       }
1049       else if (value) {
1050         i_push_errorf(0, "tag png_text%d_text found but not png_text%d_key", i, i);
1051         return 0;
1052       }
1053       else {
1054         break;
1055       }
1056       ++i;
1057     }
1058   }
1059
1060   {
1061     char buf[GET_STR_BUF_SIZE];
1062     size_t time_size;
1063     const char *timestr = get_string2(&im->tags, "png_time", buf, &time_size);
1064
1065     if (timestr) {
1066       int year, month, day, hour, minute, second;
1067       png_time mod_time;
1068
1069       if (sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) {
1070         /* rough validation */
1071         if (month < 1 || month > 12
1072             || day < 1 || day > 31
1073             || hour < 0 || hour > 23
1074             || minute < 0 || minute > 59
1075             || second < 0 || second > 60) {
1076           i_push_error(0, "invalid date/time for png_time");
1077           return 0;
1078         }
1079         mod_time.year = year;
1080         mod_time.month = month;
1081         mod_time.day = day;
1082         mod_time.hour = hour;
1083         mod_time.minute = minute;
1084         mod_time.second = second;
1085
1086         png_set_tIME(png_ptr, info_ptr, &mod_time);
1087       }
1088       else {
1089         i_push_error(0, "png_time must be formatted 'y-m-dTh:m:s'");
1090         return 0;
1091       }
1092     }
1093   }
1094
1095   {
1096     /* no bKGD support yet, maybe later
1097        it may be simpler to do it in the individual writers
1098      */
1099   }
1100
1101   return 1;
1102 }
1103
1104 static const char *
1105 get_string2(i_img_tags *tags, const char *name, char *buf, size_t *size) {
1106   int index;
1107
1108   if (i_tags_find(tags, name, 0, &index)) {
1109     const i_img_tag *entry = tags->tags + index;
1110     
1111     if (entry->data) {
1112       *size = entry->size;
1113
1114       return entry->data;
1115     }
1116     else {
1117       *size = sprintf(buf, "%d", entry->idata);
1118
1119       return buf;
1120     }
1121   }
1122   return NULL;
1123 }
1124
1125 static int
1126 write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1127   unsigned char *data, *volatile vdata = NULL;
1128   i_img_dim y;
1129
1130   if (setjmp(png_jmpbuf(png_ptr))) {
1131     if (vdata)
1132       myfree(vdata);
1133
1134     return 0;
1135   }
1136
1137   png_write_info(png_ptr, info_ptr);
1138
1139   vdata = data = mymalloc(im->xsize * im->channels);
1140   for (y = 0; y < im->ysize; y++) {
1141     i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
1142     png_write_row(png_ptr, (png_bytep)data);
1143   }
1144   myfree(data);
1145
1146   return 1;
1147 }
1148
1149 static int
1150 write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1151   unsigned *data, *volatile vdata = NULL;
1152   unsigned char *tran_data, * volatile vtran_data = NULL;
1153   i_img_dim samples_per_row = im->xsize * im->channels;
1154   
1155   i_img_dim y;
1156
1157   if (setjmp(png_jmpbuf(png_ptr))) {
1158     if (vdata)
1159       myfree(vdata);
1160     if (vtran_data)
1161       myfree(vtran_data);
1162
1163     return 0;
1164   }
1165
1166   png_write_info(png_ptr, info_ptr);
1167
1168   vdata = data = mymalloc(samples_per_row * sizeof(unsigned));
1169   vtran_data = tran_data = mymalloc(samples_per_row * 2);
1170   for (y = 0; y < im->ysize; y++) {
1171     i_img_dim i;
1172     unsigned char *p = tran_data;
1173     i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16);
1174     for (i = 0; i < samples_per_row; ++i) {
1175       p[0] = data[i] >> 8;
1176       p[1] = data[i] & 0xff;
1177       p += 2;
1178     }
1179     png_write_row(png_ptr, (png_bytep)tran_data);
1180   }
1181   myfree(tran_data);
1182   myfree(data);
1183
1184   return 1;
1185 }
1186
1187 static int
1188 write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) {
1189   unsigned char *data, *volatile vdata = NULL;
1190   i_img_dim y;
1191   unsigned char pal_map[256];
1192   png_color pcolors[256];
1193   i_color colors[256];
1194   int count = i_colorcount(im);
1195   int i;
1196
1197   if (setjmp(png_jmpbuf(png_ptr))) {
1198     if (vdata)
1199       myfree(vdata);
1200
1201     return 0;
1202   }
1203
1204   i_getcolors(im, 0, colors, count);
1205   if (im->channels < 3) {
1206     /* convert the greyscale palette to color */
1207     int i;
1208     for (i = 0; i < count; ++i) {
1209       i_color *c = colors + i;
1210       c->channel[3] = c->channel[1];
1211       c->channel[2] = c->channel[1] = c->channel[0];
1212     }
1213   }
1214
1215   if (i_img_has_alpha(im)) {
1216     int i;
1217     int bottom_index = 0, top_index = count-1;
1218
1219     /* fill out the palette map */
1220     for (i = 0; i < count; ++i)
1221       pal_map[i] = i;
1222
1223     /* the PNG spec suggests sorting the palette by alpha, but that's
1224        unnecessary - all we want to do is move the opaque entries to
1225        the end */
1226     while (bottom_index < top_index) {
1227       if (colors[bottom_index].rgba.a == 255) {
1228         pal_map[bottom_index] = top_index;
1229         pal_map[top_index--] = bottom_index;
1230       }
1231       ++bottom_index;
1232     }
1233   }
1234
1235   for (i = 0; i < count; ++i) {
1236     int srci = i_img_has_alpha(im) ? pal_map[i] : i;
1237
1238     pcolors[i].red = colors[srci].rgb.r;
1239     pcolors[i].green = colors[srci].rgb.g;
1240     pcolors[i].blue = colors[srci].rgb.b;
1241   }
1242
1243   png_set_PLTE(png_ptr, info_ptr, pcolors, count);
1244
1245   if (i_img_has_alpha(im)) {
1246     unsigned char trans[256];
1247     int i;
1248
1249     for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) {
1250       trans[i] = colors[pal_map[i]].rgba.a;
1251     }
1252     png_set_tRNS(png_ptr, info_ptr, trans, i, NULL);
1253   }
1254
1255   png_write_info(png_ptr, info_ptr);
1256
1257   png_set_packing(png_ptr);
1258
1259   vdata = data = mymalloc(im->xsize);
1260   for (y = 0; y < im->ysize; y++) {
1261     i_gpal(im, 0, im->xsize, y, data);
1262     if (i_img_has_alpha(im)) {
1263       i_img_dim x;
1264       for (x = 0; x < im->xsize; ++x)
1265         data[x] = pal_map[data[x]];
1266     }
1267     png_write_row(png_ptr, (png_bytep)data);
1268   }
1269   myfree(data);
1270
1271   return 1;
1272 }
1273
1274 static int
1275 write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) {
1276   unsigned char *data, *volatile vdata = NULL;
1277   i_img_dim y;
1278
1279   if (setjmp(png_jmpbuf(png_ptr))) {
1280     if (vdata)
1281       myfree(vdata);
1282
1283     return 0;
1284   }
1285
1286   png_write_info(png_ptr, info_ptr);
1287
1288   png_set_packing(png_ptr);
1289
1290   vdata = data = mymalloc(im->xsize);
1291   for (y = 0; y < im->ysize; y++) {
1292     i_gsamp(im, 0, im->xsize, y, data, NULL, 1);
1293     png_write_row(png_ptr, (png_bytep)data);
1294   }
1295   myfree(data);
1296
1297   return 1;
1298 }
1299
1300 static void
1301 read_warn_handler(png_structp png_ptr, png_const_charp msg) {
1302   i_png_read_statep rs = (i_png_read_statep)png_get_error_ptr(png_ptr);
1303   char *workp;
1304   size_t new_size;
1305
1306   mm_log((1, "PNG read warning '%s'\n", msg));
1307
1308   /* in case this is part of an error report */
1309   i_push_error(0, msg);
1310   
1311   /* and save in the warnings so if we do manage to succeed, we 
1312    * can save it as a tag
1313    */
1314   new_size = (rs->warnings ? strlen(rs->warnings) : 0)
1315     + 1 /* NUL */
1316     + strlen(msg) /* new text */
1317     + 1; /* newline */
1318   workp = myrealloc(rs->warnings, new_size);
1319   if (!rs->warnings)
1320     *workp = '\0';
1321   strcat(workp, msg);
1322   strcat(workp, "\n");
1323   rs->warnings = workp;
1324 }
1325
1326 static void
1327 cleanup_read_state(i_png_read_statep rs) {
1328   if (rs->warnings)
1329     myfree(rs->warnings);
1330 }