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