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