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