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