1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  * filename: m-png.c                                                       *
3  *                                                                         *
4  * UTIL C-source: Medical Image Conversion Utility                         *
5  *                                                                         *
6  * purpose      : read and write PNG files                                 *
7  *                                                                         *
8  * project      : (X)MedCon by Erik Nolf                                   *
9  *                                                                         *
10  * Functions    : MdcPngErr()                - PNG Error message routine   *
11  *                MdcPngWarn()               - PNG Warn  message routine   *
12  *                MdcCheckPNG()              - Check for PNG format        *
13  *                MdcReadPNG()               - Read PNG format             *
14  *                MdcWritePNG()              - Write PNG format            *
15  *                                                                         *
16  * Notes        : code fragments from 'example.c' included with PNG lib    *
17  *                                                                         *
18  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
19 /*
20  */
21 
22 /*
23    Copyright (C) 1997-2021 by Erik Nolf
24 
25    This program is free software; you can redistribute it and/or modify it
26    under the terms of the GNU General Public License as published by the
27    Free Software Foundation; either version 2, or (at your option) any later
28    version.
29 
30    This program is distributed in the hope that it will be useful, but
31    WITHOUT ANY WARRANTY; without even the implied warranty of
32    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
33    Public License for more details.
34 
35    You should have received a copy of the GNU General Public License along
36    with this program; if not, write to the Free Software Foundation, Inc.,
37    59 Place - Suite 330, Boston, MA 02111-1307, USA.  */
38 
39 /****************************************************************************
40                               H E A D E R S
41 ****************************************************************************/
42 
43 #include "m-depend.h"
44 
45 #include <png.h>
46 #ifdef HAVE_STDLIB_H
47 #include <stdlib.h>
48 #endif
49 #ifdef HAVE_STRING_H
50 #include <string.h>
51 #endif
52 
53 
54 #include "medcon.h"
55 
56 /****************************************************************************
57                               D E F I N E S
58 ****************************************************************************/
59 
60 
61 /****************************************************************************
62                             F U N C T I O N S
63 ****************************************************************************/
64 
MdcPngErr(png_structp png_ptr,png_const_charp error_msg)65 static void MdcPngErr(png_structp png_ptr, png_const_charp error_msg)
66 {
67   MdcPrntWarn("PNG  %s\n",error_msg);
68 
69   if (!png_ptr) return;
70 
71   longjmp(png_jmpbuf(png_ptr), 1);
72 }
73 
MdcPngWarn(png_structp png_ptr,png_const_charp warning_msg)74 static void MdcPngWarn(png_structp png_ptr, png_const_charp warning_msg)
75 {
76   if (!png_ptr) return;
77 
78   MdcPrntWarn("PNG %s\n",warning_msg);
79 }
80 
MdcCheckPNG(FILEINFO * fi)81 int MdcCheckPNG(FILEINFO *fi)
82 {
83   unsigned char buf[MDC_PNG_BYTES_TO_CHECK];
84 
85   /* read in some of the signature bytes */
86   if (fread(buf, 1, MDC_PNG_BYTES_TO_CHECK, fi->ifp) != MDC_PNG_BYTES_TO_CHECK)
87     return(MDC_BAD_READ);
88 
89   /* compare the first MDC_PNG_BYTES_TO_CHECK bytes of the signature          */
90   /* png_sig_cmp() returns zero if image is a PNG and nonzero if it isn't */
91   if (png_sig_cmp(buf,(png_size_t)0,MDC_PNG_BYTES_TO_CHECK))
92     return(MDC_FRMT_NONE);
93 
94   return(MDC_FRMT_PNG);
95 }
96 
MdcReadPNG(FILEINFO * fi)97 char *MdcReadPNG(FILEINFO *fi)
98 {
99   png_structp png_ptr;
100   png_infop info_ptr;
101   png_uint_32 width, height, rowbytes;
102   png_colorp palette;
103   png_bytepp row_pointers;
104   Uint32 i, commentsize;
105   int bit_depth, color_type, transform, num_palette;
106   Uint8 *imgRGB, *pbuf;
107   IMG_DATA *id;
108   int num_text;
109   png_textp text_ptr;
110 
111   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Reading PNG:");
112 
113   if (MDC_VERBOSE) MdcPrntMesg("PNG  Reading <%s> ...",fi->ifname);
114 
115   /* put some defaults we use */
116   fi->endian = MDC_FILE_ENDIAN=MDC_BIG_ENDIAN; /* always for a PNG */
117   fi->dim[0] = 4; fi->dim[4]=1;
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 supply the */
122   /* the compiler header file version, so that we know if the application   */
123   /* was compiled with a compatible version of the library.  REQUIRED       */
124   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING
125      , NULL, MdcPngErr, MdcPngWarn);
126   if (png_ptr == NULL) return("PNG  Couldn't create read struct");
127 
128   /* allocate/initialize the memory for image information.  REQUIRED.       */
129   info_ptr = png_create_info_struct(png_ptr);
130   if (info_ptr == NULL) {
131       png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
132       return("PNG  Couldn't create read info struct");
133   }
134 
135   /* Set error handling if you are using the setjmp/longjmp method (this is */
136   /* the normal method of doing things with libpng).  REQUIRED unless you   */
137   /* set up your own error handlers in the png_create_read_struct() earlier.*/
138   if (setjmp(png_jmpbuf(png_ptr))) {
139     /* free all of the memory associated with the png_ptr and info_ptr */
140     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
141     /* if we get here, we had a problem reading the file */
142     return("PNG  Unexpected file reading error");
143   }
144 
145   /* I/O initialization with standard C streams */
146   png_init_io(png_ptr, fi->ifp);
147 
148   /* only allow 8bit or 24bit images */
149   transform = PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_16
150             | PNG_TRANSFORM_STRIP_ALPHA;
151 
152   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.3,NULL);
153 
154   /* read image, the hilevel way */
155   png_read_png(png_ptr, info_ptr , transform, NULL);
156 
157   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.6,NULL);
158 
159   /* get image information */
160   width  = png_get_image_width(png_ptr, info_ptr);
161   height = png_get_image_height(png_ptr, info_ptr);
162   bit_depth = png_get_bit_depth(png_ptr, info_ptr);
163   color_type = png_get_color_type(png_ptr, info_ptr);
164   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)) {
165     png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
166   }
167 
168   /* get comment */
169   png_get_text(png_ptr,info_ptr,&text_ptr,&num_text);
170   if(num_text > 0) {
171     commentsize = 1;
172 
173     for(i = 0; i < num_text; i++)
174       commentsize += strlen(text_ptr[i].key) + 1 +
175                      text_ptr[i].text_length + 2;
176 
177     if ((fi->comment = malloc(commentsize)) == NULL) {
178       MdcPngWarn(png_ptr,"PNG  Can't malloc comment string");
179     }else{
180       fi->comment[0] = '\0';
181       for (i = 0; i < num_text; i++) {
182         strcat(fi->comment, text_ptr[i].key);
183         strcat(fi->comment, "::");
184         strcat(fi->comment, text_ptr[i].text);
185         strcat(fi->comment, "\n");
186       }
187     }
188   }
189 
190   if (MDC_INFO) {
191     MdcPrintLine('-',MDC_HALF_LENGTH);
192     MdcPrntScrn("Short PNG Information (ver %s)\n",png_get_libpng_ver(png_ptr));
193     MdcPrintLine('-',MDC_HALF_LENGTH);
194     MdcPrntScrn("image width   : %u\n",width);
195     MdcPrntScrn("image height  : %u\n",height);
196     MdcPrntScrn("bit depth     : %u\n",bit_depth);
197     MdcPrntScrn("color type    : %u\n",color_type);
198     MdcPrintLine('-',MDC_HALF_LENGTH);
199     MdcPrntScrn("comment block :\n\n%s\n",fi->comment);
200     MdcPrintLine('-',MDC_HALF_LENGTH);
201   }
202 
203   /* preset FILEINFO info */
204   fi->mwidth = width; fi->mheight = height;
205   fi->bits = 8; fi->type = BIT8_U;
206 
207   if (!MdcGetStructID(fi,1)) {
208     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
209     return("PNG  Bad malloc IMG_DATA struct");
210   }
211   id = (IMG_DATA *)&fi->image[0];
212   id->width = fi->mwidth;
213   id->height= fi->mheight;
214   id->bits  = fi->bits;
215   id->type  = fi->type;
216 
217   id->buf = MdcGetImgBuffer(width * height);
218   if (id->buf == NULL) {
219     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
220     return("PNG  Bad malloc image buffer");
221   }
222 
223   /* get images: png_destroy will free this one later */
224   row_pointers = png_get_rows(png_ptr, info_ptr);
225   if (row_pointers == NULL) {
226     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
227     return("PNG  Unexpected error retrieving row_pointers");
228   }
229   rowbytes = png_get_rowbytes(png_ptr, info_ptr);
230   switch(color_type) {
231     case PNG_COLOR_TYPE_PALETTE:
232       /* copy image rows */
233       if (rowbytes != width) {
234         png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
235         return("PNG  Unexpected number of bytes per row");
236       }
237       for (i=0; i<height; i++) {
238          pbuf = id->buf + (i*width);
239          memcpy(pbuf,row_pointers[i],width);
240       }
241       /* copy color palette */
242       for (i=0; i < num_palette; i++) {
243          fi->palette[i * 3 + 0] = (Uint8) palette[i].red;
244          fi->palette[i * 3 + 1] = (Uint8) palette[i].green;
245          fi->palette[i * 3 + 2] = (Uint8) palette[i].blue;
246       }
247       fi->map = MDC_MAP_PRESENT;
248       break;
249 
250     case PNG_COLOR_TYPE_GRAY:
251       /* copy image rows */
252       if (rowbytes != width) {
253         png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
254         return("PNG  Unexpeted number of bytes per row");
255       }
256       for (i=0; i<height; i++) {
257          pbuf = id->buf + (i*rowbytes);
258          memcpy(pbuf,row_pointers[i],rowbytes);
259       }
260       fi->map = MDC_MAP_GRAY;
261       break;
262 
263     case PNG_COLOR_TYPE_GRAY_ALPHA:
264       png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
265       return("PNG  Color type GRAY + ALPHA unsupported");
266       break;
267 
268     case PNG_COLOR_TYPE_RGB:
269       /* get contiguous RGB memory block */
270       imgRGB = malloc(height * rowbytes);
271       if (imgRGB == NULL) {
272         png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
273         return("PNG  Couldn't allocate RGB buffer");
274       }
275       for (i=0; i<height; i++) {
276          pbuf = imgRGB + (i*rowbytes);
277          memcpy(pbuf,row_pointers[i],rowbytes);
278       }
279       fi->map  = MDC_MAP_PRESENT;
280       fi->type = COLRGB; fi->bits = 24;
281       id->type = COLRGB; id->bits = 24;
282       id->buf  = imgRGB;
283       break;
284 
285     case PNG_COLOR_TYPE_RGB_ALPHA:
286       png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
287       return("PNG  Color type RGB + ALPHA unsupported");
288       break;
289 
290     default: return("PNG  Unsupported color type");
291   }
292 
293   /* finishing up */
294   png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
295 
296   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,1.0,NULL);
297 
298   return(NULL);
299 
300 }
301 
MdcWritePNG(FILEINFO * fi)302 char *MdcWritePNG(FILEINFO *fi)
303 {
304   char suffix[11], *pext;
305   png_structp png_ptr;
306   png_infop info_ptr;
307   png_colorp palette;
308   png_bytepp row_pointers;
309   png_text text_ptr[3];
310   IMG_DATA *id;
311   Uint32 n, i, width, height, length, row_bytes;
312   Uint8 *pbuf, FREE = MDC_NO;
313   int bit_depth, color_type, interlace, compression, filter;
314 
315   MDC_FILE_ENDIAN = MDC_BIG_ENDIAN; /* always for a PNG */
316 
317   if ((MDC_FILE_STDOUT == MDC_YES) && (fi->number > 1))
318     return("PNG  Output to stdout not appropriate for multiple images");
319 
320   if (XMDC_GUI == MDC_NO) {
321     MdcDefaultName(fi,MDC_FRMT_PNG,fi->ofname,fi->ifname);
322   }
323 
324   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Writing PNG:");
325 
326   if (MDC_VERBOSE) MdcPrntMesg("PNG  Writing <%s> ...",fi->ofname);
327 
328   /* desktop output - no use of 16 bit feature */
329   if (MDC_FORCE_INT != MDC_NO) {
330     if (MDC_FORCE_INT != BIT8_U) {
331       MdcPrntWarn("PNG  Only Uint8 pixels supported");
332     }
333   }
334 
335   /* check supported things */
336   if (MDC_QUANTIFY || MDC_CALIBRATE) {
337     MdcPrntWarn("PNG  Normalization loses quantified values!");
338   }
339 
340   if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.0,NULL);
341 
342   length = strlen(fi->ofname);
343 
344   pext = strrchr(fi->ofname,'.');
345   if (pext == NULL) pext = &fi->ofname[length];
346 
347   /* split up in separate files   */
348   /* PNG is a single image format */
349   for (n=0; n < fi->number; n++) {
350 
351      /* add slice number to filename */
352      if (fi->number > 1) {
353        sprintf(suffix,"-%.5u.%.3s",n+1,FrmtExt[MDC_FRMT_PNG]);
354        strcpy(pext,suffix);
355      }
356 
357      if ((MDC_FILE_STDOUT == MDC_YES) && (fi->number == 1)) {
358        fi->ofp = stdout;
359      }else{
360        if (MdcKeepFile(fi->ofname))
361          return("PNG  File exists!!");
362        if ( (fi->ofp=fopen(fi->ofname,"wb")) == NULL )
363          return ("PNG  Couldn't open file");
364      }
365 
366      /* set some defaults */
367      id = &fi->image[n];
368      width = id->width;
369      height= id->height;
370      bit_depth = 8;
371      if (fi->type == COLRGB) {
372        /* true color */
373        color_type = PNG_COLOR_TYPE_RGB;
374        row_bytes = width * 3;
375      }else{
376         /* indexed */
377        if (fi->map == MDC_MAP_GRAY) {
378          /* gray */
379          color_type = PNG_COLOR_TYPE_GRAY;
380          row_bytes = width;
381        }else{
382          /* color */
383          color_type = PNG_COLOR_TYPE_PALETTE;
384          row_bytes = width;
385        }
386      }
387      compression = PNG_COMPRESSION_TYPE_BASE;
388      interlace = PNG_INTERLACE_NONE;
389      filter = PNG_FILTER_TYPE_BASE;
390 
391   /* Create and initialize the png_struct with the desired error handler    */
392   /* functions.  If you want to use the default stderr and longjump method, */
393   /* you can supply NULL for the last three parameters.  We also check that */
394   /* the library version is compatible with the one used at compile time,   */
395   /* in case we are using dynamically linked libraries.  REQUIRED.          */
396      png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING
397         , NULL, MdcPngErr, MdcPngWarn);
398      if (png_ptr == NULL) return("PNG  Couldn't create write struct");
399 
400   /* allocate/initialize the image information data.  REQUIRED              */
401      info_ptr = png_create_info_struct(png_ptr);
402      if (info_ptr == NULL) {
403        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
404        return ("PNG  Couldn't create write info struct");
405      }
406 
407   /* Set error handling.  REQUIRED if you aren't supplying your own         */
408   /* error handling functions in the png_create_write_struct() call.        */
409      if (setjmp(png_jmpbuf(png_ptr))) {
410        /* if we get here, we had a problem writing the file */
411        png_destroy_write_struct(&png_ptr, &info_ptr);
412        return ("PNG  Unexpected fire write error");
413      }
414 
415   /* set up the output control using standard C streams */
416      png_init_io(png_ptr, fi->ofp);
417 
418   /* can't write hilevel way, so here goes the hard way */
419 
420   /* Set the image information here.  Width and height are up to 2^31,       */
421   /* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on  */
422   /* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,      */
423   /* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,  */
424   /* or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or */
425   /* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST      */
426   /* currently be PNG_COMPRESSION_TYPE_BASE & PNG_FILTER_TYPE_BASE. REQUIRED */
427      png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type
428         , interlace, compression, filter);
429 
430   /* set the palette if there is one.  REQUIRED for indexed-color images */
431      palette = (png_colorp)png_malloc(png_ptr, 256 * sizeof (png_color));
432      if (color_type == PNG_COLOR_TYPE_PALETTE) {
433        for (i=0; i<256; i++) {
434           palette[i].red   = fi->palette[i*3 + 0];
435           palette[i].green = fi->palette[i*3 + 1];
436           palette[i].blue  = fi->palette[i*3 + 2];
437        }
438        png_set_PLTE(png_ptr, info_ptr, palette, 256);
439      }
440   /* You must not free palette here, because png_set_PLTE only makes a link */
441   /* to the palette that you malloced.  Wait until you are about to destroy */
442   /* the png structure.                                                     */
443 
444   /* optional significant bit chunk */
445   /* if we are dealing with a grayscale image then */
446   /* sig_bit.gray = true_bit_depth; */
447   /* otherwise, if we are dealing with a color image then */
448   /* sig_bit.red = true_bit_depth; */
449   /* sig_bit.green = true_bit_depth; */
450   /* sig_bit.blue = true_bit_depth; */
451   /* if the image has an alpha channel then */
452   /* sig_bit.alpha = true_bit_depth; */
453   /* png_set_sBIT(png_ptr, info_ptr, sig_bit); */
454 
455   /* Optional gamma chunk is strongly suggested if you have any guess */
456   /* as to the correct gamma of the image.                            */
457   /* png_set_gAMA(png_ptr, info_ptr, gamma);                          */
458 
459   /* Optionally write comments into the image */
460      mdcbufr[0] = '\0';
461      if ( fi->acquisition_type != MDC_ACQUISITION_UNKNOWN ) {
462        if ( !MdcMakeScanInfoStr(fi)) mdcbufr[0]='\0';
463      }
464      text_ptr[0].key = "Program";
465      text_ptr[0].text = XMEDCON_PRGR;
466      text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
467      text_ptr[1].key = "Version";
468      text_ptr[1].text = XMEDCON_VERSION;
469      text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
470      text_ptr[2].key = "Information";
471      text_ptr[2].text = mdcbufr;
472      text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt;
473 #ifdef PNG_iTXt_SUPPORTED
474      text_ptr[0].lang = NULL;
475      text_ptr[1].lang = NULL;
476      text_ptr[2].lang = NULL;
477 #endif
478      png_set_text(png_ptr, info_ptr, text_ptr, 3);
479 
480   /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs,        */
481   /* note that if sRGB is present the gAMA and cHRM chunks must be ignored */
482   /* on read and must be written in accordance with the sRGB profile       */
483 
484   /* write the file header information.  REQUIRED */
485      png_write_info(png_ptr, info_ptr);
486 
487   /* get 8bits image */
488      if ((id->type != COLRGB) && (id->type != BIT8_U)) {
489        if ((pbuf = MdcGetImgBIT8_U(fi, n)) == NULL) {
490          png_free(png_ptr, palette);
491          png_destroy_write_struct(&png_ptr, &info_ptr);
492          return("PNG  Bad malloc new image buffer");
493        }
494        FREE = MDC_YES;
495      }else{
496        pbuf = id->buf;
497        FREE = MDC_NO;
498      }
499 
500   /* allocate pointers to rows */
501      row_pointers = (png_bytepp)malloc(sizeof(png_bytep) * height);
502      if (row_pointers == NULL) {
503        if (FREE == MDC_YES) MdcFree(pbuf);
504        png_free(png_ptr, palette);
505        png_destroy_write_struct(&png_ptr, &info_ptr);
506        return("PNG  Couldn't alloc row_pointers table");
507      }
508      for (i=0; i<height; i++) row_pointers[i]=pbuf+(i * row_bytes);
509 
510   /* write the image */
511      png_write_image(png_ptr, row_pointers);
512 
513   /* it is REQUIRED to call this to finish writing the rest of the file */
514      png_write_end(png_ptr, info_ptr);
515 
516   /* free the row pointers */
517      MdcFree(row_pointers);
518 
519   /* free image buffer */
520      if (FREE == MDC_YES) MdcFree(pbuf);
521 
522   /* free palette */
523      png_free(png_ptr, palette); palette=NULL;
524 
525   /* clean up after the write, and free any memory allocated */
526      png_destroy_write_struct(&png_ptr, &info_ptr);
527 
528      if (MDC_PROGRESS)
529        MdcProgress(MDC_PROGRESS_SET,(float)(n + 1)/(float)fi->number,NULL);
530 
531      MdcCloseFile(fi->ofp);
532 
533   }
534 
535   return(NULL);
536 }
537