PNG-rework: forward png flush output through to i_io_flush()
[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 unsigned
37 i_png_lib_version(void) {
38   return png_access_version_number();
39 }
40
41 static void
42 wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
43   io_glue *ig = png_get_io_ptr(png_ptr);
44   ssize_t rc = i_io_read(ig, data, length);
45   if (rc != length) png_error(png_ptr, "Read overflow error on an iolayer source.");
46 }
47
48 static void
49 wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
50   ssize_t rc;
51   io_glue *ig = png_get_io_ptr(png_ptr);
52   rc = i_io_write(ig, data, length);
53   if (rc != length) png_error(png_ptr, "Write error on an iolayer source.");
54 }
55
56 static void
57 wiol_flush_data(png_structp png_ptr) {
58   io_glue *ig = png_get_io_ptr(png_ptr);
59   if (!i_io_flush(ig))
60     png_error(png_ptr, "Error flushing output");
61 }
62
63 static void
64 error_handler(png_structp png_ptr, png_const_charp msg) {
65   mm_log((1, "PNG error: '%s'\n", msg));
66
67   i_push_error(0, msg);
68   longjmp(png_jmpbuf(png_ptr), 1);
69 }
70
71 /*
72
73   For writing a warning might have information about an error, so send
74   it to the error stack.
75
76 */
77 static void
78 write_warn_handler(png_structp png_ptr, png_const_charp msg) {
79   mm_log((1, "PNG write warning '%s'\n", msg));
80
81   i_push_error(0, msg);
82 }
83
84 #define PNG_DIM_MAX 0x7fffffffL
85
86 undef_int
87 i_writepng_wiol(i_img *im, io_glue *ig) {
88   png_structp png_ptr;
89   png_infop info_ptr = NULL;
90   i_img_dim width,height,y;
91   volatile int cspace,channels;
92   double xres, yres;
93   int aspect_only, have_res;
94   unsigned char *data;
95   unsigned char * volatile vdata = NULL;
96   int bits;
97   int is_bilevel = 0, zero_is_white;
98
99   mm_log((1,"i_writepng(im %p ,ig %p)\n", im, ig));
100
101   i_clear_error();
102
103   if (im->xsize > PNG_UINT_31_MAX || im->ysize > PNG_UINT_31_MAX) {
104     i_push_error(0, "image too large for PNG");
105     return 0;
106   }
107
108   height = im->ysize;
109   width  = im->xsize;
110
111   /* if we ever have 64-bit i_img_dim
112    * the libpng docs state that png_set_user_limits() can be used to
113    * override the PNG_USER_*_MAX limits, but as implemented they
114    * don't.  We check against the theoretical limit of PNG here, and
115    * try to override the limits below, in case the libpng
116    * implementation ever matches the documentation.
117    *
118    * https://sourceforge.net/tracker/?func=detail&atid=105624&aid=3314943&group_id=5624
119    * fixed in libpng 1.5.3
120    */
121   if (width > PNG_DIM_MAX || height > PNG_DIM_MAX) {
122     i_push_error(0, "Image too large for PNG");
123     return 0;
124   }
125
126   channels=im->channels;
127
128   if (i_img_is_monochrome(im, &zero_is_white)) {
129     is_bilevel = 1;
130     bits = 1;
131     cspace = PNG_COLOR_TYPE_GRAY;
132     mm_log((1, "i_writepng: bilevel output\n"));
133   }
134   else if (im->type == i_palette_type) {
135     int colors = i_colorcount(im);
136
137     cspace = PNG_COLOR_TYPE_PALETTE;
138     bits = 1;
139     while ((1 << bits) < colors) {
140       bits += bits;
141     }
142     mm_log((1, "i_writepng: paletted output\n"));
143   }
144   else {
145     switch (channels) {
146     case 1:
147       cspace = PNG_COLOR_TYPE_GRAY;
148       break;
149     case 2:
150       cspace = PNG_COLOR_TYPE_GRAY_ALPHA;
151       break;
152     case 3:
153       cspace = PNG_COLOR_TYPE_RGB;
154       break;
155     case 4:
156       cspace = PNG_COLOR_TYPE_RGB_ALPHA;
157       break;
158     default:
159       fprintf(stderr, "Internal error, channels = %d\n", channels);
160       abort();
161     }
162     bits = im->bits > 8 ? 16 : 8;
163     mm_log((1, "i_writepng: direct output\n"));
164   }
165
166   mm_log((1,"i_writepng: cspace=%d, bits=%d\n",cspace, bits));
167
168   /* Create and initialize the png_struct with the desired error handler
169    * functions.  If you want to use the default stderr and longjump method,
170    * you can supply NULL for the last three parameters.  We also check that
171    * the library version is compatible with the one used at compile time,
172    * in case we are using dynamically linked libraries.  REQUIRED.
173    */
174   
175   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, 
176                                     error_handler, write_warn_handler);
177   
178   if (png_ptr == NULL) return 0;
179
180   
181   /* Allocate/initialize the image information data.  REQUIRED */
182   info_ptr = png_create_info_struct(png_ptr);
183
184   if (info_ptr == NULL) {
185     png_destroy_write_struct(&png_ptr, &info_ptr);
186     return 0;
187   }
188   
189   /* Set error handling.  REQUIRED if you aren't supplying your own
190    * error hadnling functions in the png_create_write_struct() call.
191    */
192   if (setjmp(png_jmpbuf(png_ptr))) {
193     png_destroy_write_struct(&png_ptr, &info_ptr);
194     if (vdata)
195       myfree(vdata);
196     return(0);
197   }
198   
199   png_set_write_fn(png_ptr, (png_voidp) (ig), wiol_write_data, wiol_flush_data);
200
201   /* Set the image information here.  Width and height are up to 2^31,
202    * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
203    * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
204    * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
205    * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
206    * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
207    * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
208    */
209
210   /* by default, libpng (not PNG) limits the image size to a maximum
211    * 1000000 pixels in each direction, but Imager doesn't.
212    * Configure libpng to avoid that limit.
213    */
214   png_set_user_limits(png_ptr, width, height);
215
216   mm_log((1, ">png_set_IHDR\n"));
217   png_set_IHDR(png_ptr, info_ptr, width, height, bits, cspace,
218                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
219   mm_log((1, "<png_set_IHDR\n"));
220
221   have_res = 1;
222   if (i_tags_get_float(&im->tags, "i_xres", 0, &xres)) {
223     if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
224       ; /* nothing to do */
225     else
226       yres = xres;
227   }
228   else {
229     if (i_tags_get_float(&im->tags, "i_yres", 0, &yres))
230       xres = yres;
231     else
232       have_res = 0;
233   }
234   if (have_res) {
235     aspect_only = 0;
236     i_tags_get_int(&im->tags, "i_aspect_only", 0, &aspect_only);
237     xres /= 0.0254;
238     yres /= 0.0254;
239     png_set_pHYs(png_ptr, info_ptr, xres + 0.5, yres + 0.5, 
240                  aspect_only ? PNG_RESOLUTION_UNKNOWN : PNG_RESOLUTION_METER);
241   }
242
243   if (is_bilevel) {
244     if (!write_bilevel(png_ptr, info_ptr, im)) {
245       png_destroy_write_struct(&png_ptr, &info_ptr);
246       return 0;
247     }
248   }
249   else if (im->type == i_palette_type) {
250     if (!write_paletted(png_ptr, info_ptr, im, bits)) {
251       png_destroy_write_struct(&png_ptr, &info_ptr);
252       return 0;
253     }
254   }
255   else if (bits == 16) {
256     if (!write_direct16(png_ptr, info_ptr, im)) {
257       png_destroy_write_struct(&png_ptr, &info_ptr);
258       return 0;
259     }
260   }
261   else {
262     if (!write_direct8(png_ptr, info_ptr, im)) {
263       png_destroy_write_struct(&png_ptr, &info_ptr);
264       return 0;
265     }
266   }
267
268   png_write_end(png_ptr, info_ptr);
269
270   png_destroy_write_struct(&png_ptr, &info_ptr);
271
272   if (i_io_close(ig))
273     return 0;
274
275   return(1);
276 }
277
278 static void 
279 get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth);
280
281 typedef struct {
282   char *warnings;
283 } i_png_read_state, *i_png_read_statep;
284
285 static void
286 read_warn_handler(png_structp, png_const_charp);
287
288 static void
289 cleanup_read_state(i_png_read_statep);
290
291 i_img*
292 i_readpng_wiol(io_glue *ig) {
293   i_img *im = NULL;
294   png_structp png_ptr;
295   png_infop info_ptr;
296   png_uint_32 width, height;
297   int bit_depth, color_type, interlace_type;
298   int channels;
299   unsigned int sig_read;
300   i_png_read_state rs;
301
302   rs.warnings = NULL;
303   sig_read  = 0;
304
305   mm_log((1,"i_readpng_wiol(ig %p)\n", ig));
306   i_clear_error();
307
308   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &rs, 
309                                    error_handler, read_warn_handler);
310   if (!png_ptr) {
311     i_push_error(0, "Cannot create PNG read structure");
312     return NULL;
313   }
314   png_set_read_fn(png_ptr, (png_voidp) (ig), wiol_read_data);
315   
316   info_ptr = png_create_info_struct(png_ptr);
317   if (info_ptr == NULL) {
318     png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
319     i_push_error(0, "Cannot create PNG info structure");
320     return NULL;
321   }
322   
323   if (setjmp(png_jmpbuf(png_ptr))) {
324     if (im) i_img_destroy(im);
325     mm_log((1,"i_readpng_wiol: error.\n"));
326     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
327     cleanup_read_state(&rs);
328     return NULL;
329   }
330   
331   /* we do our own limit checks */
332   png_set_user_limits(png_ptr, PNG_DIM_MAX, PNG_DIM_MAX);
333
334   png_set_sig_bytes(png_ptr, sig_read);
335   png_read_info(png_ptr, info_ptr);
336   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL);
337   
338   mm_log((1,
339           "png_get_IHDR results: width %d, height %d, bit_depth %d, color_type %d, interlace_type %d\n",
340           width,height,bit_depth,color_type,interlace_type));
341   
342   CC2C[PNG_COLOR_TYPE_GRAY]=1;
343   CC2C[PNG_COLOR_TYPE_PALETTE]=3;
344   CC2C[PNG_COLOR_TYPE_RGB]=3;
345   CC2C[PNG_COLOR_TYPE_RGB_ALPHA]=4;
346   CC2C[PNG_COLOR_TYPE_GRAY_ALPHA]=2;
347   channels = CC2C[color_type];
348
349   mm_log((1,"i_readpng_wiol: channels %d\n",channels));
350
351   if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
352     mm_log((1, "i_readpnm: image size exceeds limits\n"));
353     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
354     return NULL;
355   }
356
357   if (color_type == PNG_COLOR_TYPE_PALETTE) {
358     im = read_paletted(png_ptr, info_ptr, channels, width, height);
359   }
360   else if (color_type == PNG_COLOR_TYPE_GRAY
361            && bit_depth == 1
362            && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
363     im = read_bilevel(png_ptr, info_ptr, width, height);
364   }
365   else if (bit_depth == 16) {
366     im = read_direct16(png_ptr, info_ptr, channels, width, height);
367   }
368   else {
369     im = read_direct8(png_ptr, info_ptr, channels, width, height);
370   }
371
372   if (im)
373     get_png_tags(im, png_ptr, info_ptr, bit_depth);
374
375   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
376
377   if (im) {
378     if (rs.warnings) {
379       i_tags_set(&im->tags, "png_warnings", rs.warnings, -1);
380     }
381   }
382   cleanup_read_state(&rs);
383   
384   mm_log((1,"(%p) <- i_readpng_wiol\n", im));  
385   
386   return im;
387 }
388
389 static i_img *
390 read_direct8(png_structp png_ptr, png_infop info_ptr, int channels,
391              i_img_dim width, i_img_dim height) {
392   i_img * volatile vim = NULL;
393   int color_type = png_get_color_type(png_ptr, info_ptr);
394   int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
395   i_img_dim y;
396   int number_passes, pass;
397   i_img *im;
398   unsigned char *line;
399   unsigned char * volatile vline = NULL;
400
401   if (setjmp(png_jmpbuf(png_ptr))) {
402     if (vim) i_img_destroy(vim);
403     if (vline) myfree(vline);
404
405     return NULL;
406   }
407
408   number_passes = png_set_interlace_handling(png_ptr);
409   mm_log((1,"number of passes=%d\n",number_passes));
410
411   png_set_strip_16(png_ptr);
412   png_set_packing(png_ptr);
413
414   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
415     png_set_expand(png_ptr);
416     
417   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
418     channels++;
419     mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
420     png_set_expand(png_ptr);
421   }
422   
423   png_read_update_info(png_ptr, info_ptr);
424   
425   im = vim = i_img_8_new(width,height,channels);
426   if (!im) {
427     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
428     return NULL;
429   }
430   
431   line = vline = mymalloc(channels * width);
432   for (pass = 0; pass < number_passes; pass++) {
433     for (y = 0; y < height; y++) {
434       if (pass > 0)
435         i_gsamp(im, 0, width, y, line, NULL, channels);
436       png_read_row(png_ptr,(png_bytep)line, NULL);
437       i_psamp(im, 0, width, y, line, NULL, channels);
438     }
439   }
440   myfree(line);
441   vline = NULL;
442   
443   png_read_end(png_ptr, info_ptr); 
444
445   return im;
446 }
447
448 static i_img *
449 read_direct16(png_structp png_ptr, png_infop info_ptr, int channels,
450              i_img_dim width, i_img_dim height) {
451   i_img * volatile vim = NULL;
452   i_img_dim x, y;
453   int number_passes, pass;
454   i_img *im;
455   unsigned char *line;
456   unsigned char * volatile vline = NULL;
457   unsigned *bits_line;
458   unsigned * volatile vbits_line = NULL;
459   size_t row_bytes;
460
461   if (setjmp(png_jmpbuf(png_ptr))) {
462     if (vim) i_img_destroy(vim);
463     if (vline) myfree(vline);
464     if (vbits_line) myfree(vbits_line);
465
466     return NULL;
467   }
468
469   number_passes = png_set_interlace_handling(png_ptr);
470   mm_log((1,"number of passes=%d\n",number_passes));
471
472   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
473     channels++;
474     mm_log((1, "image has transparency, adding alpha: channels = %d\n", channels));
475     png_set_expand(png_ptr);
476   }
477   
478   png_read_update_info(png_ptr, info_ptr);
479   
480   im = vim = i_img_16_new(width,height,channels);
481   if (!im) {
482     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
483     return NULL;
484   }
485   
486   row_bytes = png_get_rowbytes(png_ptr, info_ptr);
487   line = vline = mymalloc(row_bytes);
488   memset(line, 0, row_bytes);
489   bits_line = vbits_line = mymalloc(sizeof(unsigned) * width * channels);
490   for (pass = 0; pass < number_passes; pass++) {
491     for (y = 0; y < height; y++) {
492       if (pass > 0) {
493         i_gsamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
494         for (x = 0; x < width * channels; ++x) {
495           line[x*2] = bits_line[x] >> 8;
496           line[x*2+1] = bits_line[x] & 0xff;
497         }
498       }
499       png_read_row(png_ptr,(png_bytep)line, NULL);
500       for (x = 0; x < width * channels; ++x)
501         bits_line[x] = (line[x*2] << 8) + line[x*2+1];
502       i_psamp_bits(im, 0, width, y, bits_line, NULL, channels, 16);
503     }
504   }
505   myfree(line);
506   myfree(bits_line);
507   vline = NULL;
508   vbits_line = NULL;
509   
510   png_read_end(png_ptr, info_ptr); 
511
512   return im;
513 }
514
515 static i_img *
516 read_bilevel(png_structp png_ptr, png_infop info_ptr,
517              i_img_dim width, i_img_dim height) {
518   i_img * volatile vim = NULL;
519   i_img_dim x, y;
520   int number_passes, pass;
521   i_img *im;
522   unsigned char *line;
523   unsigned char * volatile vline = NULL;
524   i_color palette[2];
525
526   if (setjmp(png_jmpbuf(png_ptr))) {
527     if (vim) i_img_destroy(vim);
528     if (vline) myfree(vline);
529
530     return NULL;
531   }
532
533   number_passes = png_set_interlace_handling(png_ptr);
534   mm_log((1,"number of passes=%d\n",number_passes));
535
536   png_set_packing(png_ptr);
537
538   png_set_expand(png_ptr);  
539   
540   png_read_update_info(png_ptr, info_ptr);
541   
542   im = vim = i_img_pal_new(width, height, 1, 256);
543   if (!im) {
544     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
545     return NULL;
546   }
547
548   palette[0].channel[0] = palette[0].channel[1] = palette[0].channel[2] = 
549     palette[0].channel[3] = 0;
550   palette[1].channel[0] = palette[1].channel[1] = palette[1].channel[2] = 
551     palette[1].channel[3] = 255;
552   i_addcolors(im, palette, 2);
553   
554   line = vline = mymalloc(width);
555   memset(line, 0, width);
556   for (pass = 0; pass < number_passes; pass++) {
557     for (y = 0; y < height; y++) {
558       if (pass > 0) {
559         i_gpal(im, 0, width, y, line);
560         /* expand indexes back to 0/255 */
561         for (x = 0; x < width; ++x)
562           line[x] = line[x] ? 255 : 0;
563       }
564       png_read_row(png_ptr,(png_bytep)line, NULL);
565
566       /* back to palette indexes */
567       for (x = 0; x < width; ++x)
568         line[x] = line[x] ? 1 : 0;
569       i_ppal(im, 0, width, y, line);
570     }
571   }
572   myfree(line);
573   vline = NULL;
574   
575   png_read_end(png_ptr, info_ptr); 
576
577   return im;
578 }
579
580 /* FIXME: do we need to unscale palette color values from the 
581    supplied alphas? */
582 static i_img *
583 read_paletted(png_structp png_ptr, png_infop info_ptr, int channels,
584               i_img_dim width, i_img_dim height) {
585   i_img * volatile vim = NULL;
586   int color_type = png_get_color_type(png_ptr, info_ptr);
587   int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
588   i_img_dim y;
589   int number_passes, pass;
590   i_img *im;
591   unsigned char *line;
592   unsigned char * volatile vline = NULL;
593   int num_palette, i;
594   png_colorp png_palette;
595   png_bytep png_pal_trans;
596   png_color_16p png_color_trans;
597   int num_pal_trans;
598
599   if (setjmp(png_jmpbuf(png_ptr))) {
600     if (vim) i_img_destroy(vim);
601     if (vline) myfree(vline);
602
603     return NULL;
604   }
605
606   number_passes = png_set_interlace_handling(png_ptr);
607   mm_log((1,"number of passes=%d\n",number_passes));
608
609   png_set_strip_16(png_ptr);
610   png_set_packing(png_ptr);
611
612   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
613     png_set_expand(png_ptr);
614     
615   if (!png_get_PLTE(png_ptr, info_ptr, &png_palette, &num_palette)) {
616     i_push_error(0, "Paletted image with no PLTE chunk");
617     return NULL;
618   }
619
620   if (png_get_tRNS(png_ptr, info_ptr, &png_pal_trans, &num_pal_trans,
621                    &png_color_trans)) {
622     channels++;
623   }
624   else {
625     num_pal_trans = 0;
626   }
627   
628   png_read_update_info(png_ptr, info_ptr);
629   
630   im = vim = i_img_pal_new(width, height, channels, 256);
631   if (!im) {
632     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
633     return NULL;
634   }
635
636   for (i = 0; i < num_palette; ++i) {
637     i_color c;
638
639     c.rgba.r = png_palette[i].red;
640     c.rgba.g = png_palette[i].green;
641     c.rgba.b = png_palette[i].blue;
642     if (i < num_pal_trans)
643       c.rgba.a = png_pal_trans[i];
644     else
645       c.rgba.a = 255;
646     i_addcolors(im, &c, 1);
647   }
648
649   line = vline = mymalloc(width);
650   for (pass = 0; pass < number_passes; pass++) {
651     for (y = 0; y < height; y++) {
652       if (pass > 0)
653         i_gpal(im, 0, width, y, line);
654       png_read_row(png_ptr,(png_bytep)line, NULL);
655       i_ppal(im, 0, width, y, line);
656     }
657   }
658   myfree(line);
659   vline = NULL;
660   
661   png_read_end(png_ptr, info_ptr); 
662
663   return im;
664 }
665
666 struct png_text_name {
667   const char *keyword;
668   const char *tagname;
669 };
670
671 static const struct png_text_name
672 text_tags[] = {
673   { "Author", "png_author" },
674   { "Comment", "i_comment" },
675   { "Copyright", "png_copyright" },
676   { "Creation Time", "png_creation_time" },
677   { "Description", "png_description" },
678   { "Disclaimer", "png_disclaimer" },
679   { "Software", "png_software" },
680   { "Source", "png_source" },
681   { "Title", "png_title" },
682   { "Warning", "png_warning" }
683 };
684
685 static const int text_tags_count = sizeof(text_tags) / sizeof(*text_tags);
686
687 static void
688 get_png_tags(i_img *im, png_structp png_ptr, png_infop info_ptr, int bit_depth) {
689   png_uint_32 xres, yres;
690   int unit_type;
691
692   i_tags_set(&im->tags, "i_format", "png", -1);
693   if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type)) {
694     mm_log((1,"pHYs (%d, %d) %d\n", xres, yres, unit_type));
695     if (unit_type == PNG_RESOLUTION_METER) {
696       i_tags_set_float2(&im->tags, "i_xres", 0, xres * 0.0254, 5);
697       i_tags_set_float2(&im->tags, "i_yres", 0, yres * 0.0254, 5);
698     }
699     else {
700       i_tags_setn(&im->tags, "i_xres", xres);
701       i_tags_setn(&im->tags, "i_yres", yres);
702       i_tags_setn(&im->tags, "i_aspect_only", 1);
703     }
704   }
705   {
706     int interlace = png_get_interlace_type(png_ptr, info_ptr);
707
708     i_tags_setn(&im->tags, "png_interlace", interlace != PNG_INTERLACE_NONE);
709     switch (interlace) {
710     case PNG_INTERLACE_NONE:
711       i_tags_set(&im->tags, "png_interlace_name", "none", -1);
712       break;
713       
714     case PNG_INTERLACE_ADAM7:
715       i_tags_set(&im->tags, "png_interlace_name", "adam7", -1);
716       break;
717       
718     default:
719       i_tags_set(&im->tags, "png_interlace_name", "unknown", -1);
720       break;
721     }
722   }
723
724   /* the various readers can call png_set_expand(), libpng will make
725      it's internal record of bit_depth at least 8 in that case */
726   i_tags_setn(&im->tags, "png_bits", bit_depth);
727
728   
729   {
730     int intent;
731     if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
732       i_tags_setn(&im->tags, "png_srgb_intent", intent);
733     }
734   }
735   {
736     double gamma;
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   {
742     double white_x, white_y;
743     double red_x, red_y;
744     double green_x, green_y;
745     double blue_x, blue_y;
746     if (png_get_cHRM(png_ptr, info_ptr, &white_x, &white_y,
747                      &red_x, &red_y, &green_x, &green_y,
748                      &blue_x, &blue_y)) {
749       i_tags_set_float2(&im->tags, "png_chroma_white_x", 0, white_x, 4);
750       i_tags_set_float2(&im->tags, "png_chroma_white_y", 0, white_y, 4);
751       i_tags_set_float2(&im->tags, "png_chroma_red_x", 0, red_x, 4);
752       i_tags_set_float2(&im->tags, "png_chroma_red_y", 0, red_y, 4);
753       i_tags_set_float2(&im->tags, "png_chroma_green_x", 0, green_x, 4);
754       i_tags_set_float2(&im->tags, "png_chroma_green_y", 0, green_y, 4);
755       i_tags_set_float2(&im->tags, "png_chroma_blue_x", 0, blue_x, 4);
756       i_tags_set_float2(&im->tags, "png_chroma_blue_y", 0, blue_y, 4);
757     }
758   }
759
760   {
761     int num_text;
762     png_text *text;
763
764     if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
765       int i;
766       for (i = 0; i < num_text; ++i) {
767         int j;
768         char tag_name[50];
769         sprintf(tag_name, "png_text%d_key", i);
770         i_tags_set(&im->tags, tag_name, text[i].key, -1);
771         sprintf(tag_name, "png_text%d_text", i);
772         i_tags_set(&im->tags, tag_name, text[i].text, -1);
773         sprintf(tag_name, "png_text%d_type", i);
774         i_tags_set(&im->tags, tag_name, 
775                    (text[i].compression == PNG_TEXT_COMPRESSION_NONE
776                     || text[i].compression == PNG_TEXT_COMPRESSION_zTXt) ?
777                    "text" : "itxt", -1);
778
779         for (j = 0; j < text_tags_count; ++j) {
780           if (strcmp(text_tags[j].keyword, text[i].key) == 0) {
781             i_tags_set(&im->tags, text_tags[j].tagname, text[i].text, -1);
782             break;
783           }
784         }
785       }
786     }
787   }
788 }
789
790 static int
791 write_direct8(png_structp png_ptr, png_infop info_ptr, i_img *im) {
792   unsigned char *data, *volatile vdata = NULL;
793   i_img_dim y;
794
795   if (setjmp(png_jmpbuf(png_ptr))) {
796     if (vdata)
797       myfree(vdata);
798
799     return 0;
800   }
801
802   png_write_info(png_ptr, info_ptr);
803
804   vdata = data = mymalloc(im->xsize * im->channels);
805   for (y = 0; y < im->ysize; y++) {
806     i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
807     png_write_row(png_ptr, (png_bytep)data);
808   }
809   myfree(data);
810
811   return 1;
812 }
813
814 static int
815 write_direct16(png_structp png_ptr, png_infop info_ptr, i_img *im) {
816   unsigned *data, *volatile vdata = NULL;
817   unsigned char *tran_data, * volatile vtran_data = NULL;
818   i_img_dim samples_per_row = im->xsize * im->channels;
819   
820   i_img_dim y;
821
822   if (setjmp(png_jmpbuf(png_ptr))) {
823     if (vdata)
824       myfree(vdata);
825     if (vtran_data)
826       myfree(vtran_data);
827
828     return 0;
829   }
830
831   png_write_info(png_ptr, info_ptr);
832
833   vdata = data = mymalloc(samples_per_row * sizeof(unsigned));
834   vtran_data = tran_data = mymalloc(samples_per_row * 2);
835   for (y = 0; y < im->ysize; y++) {
836     i_img_dim i;
837     unsigned char *p = tran_data;
838     i_gsamp_bits(im, 0, im->xsize, y, data, NULL, im->channels, 16);
839     for (i = 0; i < samples_per_row; ++i) {
840       p[0] = data[i] >> 8;
841       p[1] = data[i] & 0xff;
842       p += 2;
843     }
844     png_write_row(png_ptr, (png_bytep)tran_data);
845   }
846   myfree(tran_data);
847   myfree(data);
848
849   return 1;
850 }
851
852 static int
853 write_paletted(png_structp png_ptr, png_infop info_ptr, i_img *im, int bits) {
854   unsigned char *data, *volatile vdata = NULL;
855   i_img_dim y;
856   unsigned char pal_map[256];
857   png_color pcolors[256];
858   i_color colors[256];
859   int count = i_colorcount(im);
860   int i;
861
862   if (setjmp(png_jmpbuf(png_ptr))) {
863     if (vdata)
864       myfree(vdata);
865
866     return 0;
867   }
868
869   i_getcolors(im, 0, colors, count);
870   if (im->channels < 3) {
871     /* convert the greyscale palette to color */
872     int i;
873     for (i = 0; i < count; ++i) {
874       i_color *c = colors + i;
875       c->channel[3] = c->channel[1];
876       c->channel[2] = c->channel[1] = c->channel[0];
877     }
878   }
879
880   if (i_img_has_alpha(im)) {
881     int i;
882     int bottom_index = 0, top_index = count-1;
883
884     /* fill out the palette map */
885     for (i = 0; i < count; ++i)
886       pal_map[i] = i;
887
888     /* the PNG spec suggests sorting the palette by alpha, but that's
889        unnecessary - all we want to do is move the opaque entries to
890        the end */
891     while (bottom_index < top_index) {
892       if (colors[bottom_index].rgba.a == 255) {
893         pal_map[bottom_index] = top_index;
894         pal_map[top_index--] = bottom_index;
895       }
896       ++bottom_index;
897     }
898   }
899
900   for (i = 0; i < count; ++i) {
901     int srci = i_img_has_alpha(im) ? pal_map[i] : i;
902
903     pcolors[i].red = colors[srci].rgb.r;
904     pcolors[i].green = colors[srci].rgb.g;
905     pcolors[i].blue = colors[srci].rgb.b;
906   }
907
908   png_set_PLTE(png_ptr, info_ptr, pcolors, count);
909
910   if (i_img_has_alpha(im)) {
911     unsigned char trans[256];
912     int i;
913
914     for (i = 0; i < count && colors[pal_map[i]].rgba.a != 255; ++i) {
915       trans[i] = colors[pal_map[i]].rgba.a;
916     }
917     png_set_tRNS(png_ptr, info_ptr, trans, i, NULL);
918   }
919
920   png_write_info(png_ptr, info_ptr);
921
922   png_set_packing(png_ptr);
923
924   vdata = data = mymalloc(im->xsize);
925   for (y = 0; y < im->ysize; y++) {
926     i_gpal(im, 0, im->xsize, y, data);
927     if (i_img_has_alpha(im)) {
928       i_img_dim x;
929       for (x = 0; x < im->xsize; ++x)
930         data[x] = pal_map[data[x]];
931     }
932     png_write_row(png_ptr, (png_bytep)data);
933   }
934   myfree(data);
935
936   return 1;
937 }
938
939 static int
940 write_bilevel(png_structp png_ptr, png_infop info_ptr, i_img *im) {
941   unsigned char *data, *volatile vdata = NULL;
942   i_img_dim y;
943
944   if (setjmp(png_jmpbuf(png_ptr))) {
945     if (vdata)
946       myfree(vdata);
947
948     return 0;
949   }
950
951   png_write_info(png_ptr, info_ptr);
952
953   png_set_packing(png_ptr);
954
955   vdata = data = mymalloc(im->xsize);
956   for (y = 0; y < im->ysize; y++) {
957     i_gsamp(im, 0, im->xsize, y, data, NULL, 1);
958     png_write_row(png_ptr, (png_bytep)data);
959   }
960   myfree(data);
961
962   return 1;
963 }
964
965 static void
966 read_warn_handler(png_structp png_ptr, png_const_charp msg) {
967   i_png_read_statep rs = (i_png_read_statep)png_get_error_ptr(png_ptr);
968   char *workp;
969   size_t new_size;
970
971   mm_log((1, "PNG read warning '%s'\n", msg));
972
973   /* in case this is part of an error report */
974   i_push_error(0, msg);
975   
976   /* and save in the warnings so if we do manage to succeed, we 
977    * can save it as a tag
978    */
979   new_size = (rs->warnings ? strlen(rs->warnings) : 0)
980     + 1 /* NUL */
981     + strlen(msg) /* new text */
982     + 1; /* newline */
983   workp = myrealloc(rs->warnings, new_size);
984   if (!rs->warnings)
985     *workp = '\0';
986   strcat(workp, msg);
987   strcat(workp, "\n");
988   rs->warnings = workp;
989 }
990
991 static void
992 cleanup_read_state(i_png_read_statep rs) {
993   if (rs->warnings)
994     myfree(rs->warnings);
995 }