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