/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * filename: m-png.c                                                       *
 *                                                                         *
 * UTIL C-source: Medical Image Conversion Utility                         *
 *                                                                         *
 * purpose      : read and write PNG files                                 *
 *                                                                         *
 * project      : (X)MedCon by Erik Nolf                                   *
 *                                                                         *
 * Functions    : MdcPngErr()                - PNG Error message routine   *
 *                MdcPngWarn()               - PNG Warn  message routine   *
 *                MdcCheckPNG()              - Check for PNG format        *
 *                MdcReadPNG()               - Read PNG format             *
 *                MdcWritePNG()              - Write PNG format            *
 *                                                                         *
 * Notes        : code fragments from 'example.c' included with PNG lib    *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 */

/*
   Copyright (C) 1997-2026 by Erik Nolf

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any later
   version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
   Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */

/****************************************************************************
                              H E A D E R S
****************************************************************************/

#include "m-depend.h"

#include <png.h>
#ifdef LIBMDC_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef LIBMDC_HAVE_STRING_H
#include <string.h>
#endif


#include "medcon.h"

/****************************************************************************
                              D E F I N E S
****************************************************************************/


/****************************************************************************
                            F U N C T I O N S
****************************************************************************/

static void MdcPngErr(png_structp png_ptr, png_const_charp error_msg)
{
  MdcPrntWarn("PNG  %s\n",error_msg);

  if (!png_ptr) return;

  longjmp(png_jmpbuf(png_ptr), 1);
}

static void MdcPngWarn(png_structp png_ptr, png_const_charp warning_msg)
{
  if (!png_ptr) return;

  MdcPrntWarn("PNG %s\n",warning_msg);
}

int MdcCheckPNG(FILEINFO *fi)
{
  unsigned char buf[MDC_PNG_BYTES_TO_CHECK];

  /* read in some of the signature bytes */
  if (fread(buf, 1, MDC_PNG_BYTES_TO_CHECK, fi->ifp) != MDC_PNG_BYTES_TO_CHECK)
    return(MDC_BAD_READ);

  /* compare the first MDC_PNG_BYTES_TO_CHECK bytes of the signature          */
  /* png_sig_cmp() returns zero if image is a PNG and nonzero if it isn't */
  if (png_sig_cmp(buf,(png_size_t)0,MDC_PNG_BYTES_TO_CHECK))
    return(MDC_FRMT_NONE);

  return(MDC_FRMT_PNG);
}

char *MdcReadPNG(FILEINFO *fi)
{
  png_structp png_ptr;
  png_infop info_ptr;
  png_uint_32 width, height, rowbytes;
  png_colorp palette;
  png_bytepp row_pointers;
  Uint32 i, commentsize;
  int bit_depth, color_type, transform, num_palette;
  Uint8 *imgRGB, *pbuf;
  IMG_DATA *id;
  int num_text;
  png_textp text_ptr;

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Reading PNG:");

  if (MDC_VERBOSE) MdcPrntMesg("PNG  Reading <%s> ...",fi->ifname);

  /* put some defaults we use */
  fi->endian = MDC_FILE_ENDIAN=MDC_BIG_ENDIAN; /* always for a PNG */
  fi->dim[0] = 4; fi->dim[4]=1;

  /* Create and initialize the png_struct with the desired error handler    */
  /* functions.  If you want to use the default stderr and longjump method, */
  /* you can supply NULL for the last three parameters.  We also supply the */
  /* the compiler header file version, so that we know if the application   */
  /* was compiled with a compatible version of the library.  REQUIRED       */
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING
     , NULL, MdcPngErr, MdcPngWarn);
  if (png_ptr == NULL) return("PNG  Couldn't create read struct");

  /* allocate/initialize the memory for image information.  REQUIRED.       */
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) {
      png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
      return("PNG  Couldn't create read info struct");
  }

  /* Set error handling if you are using the setjmp/longjmp method (this is */
  /* the normal method of doing things with libpng).  REQUIRED unless you   */
  /* set up your own error handlers in the png_create_read_struct() earlier.*/
  if (setjmp(png_jmpbuf(png_ptr))) {
    /* free all of the memory associated with the png_ptr and info_ptr */
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
    /* if we get here, we had a problem reading the file */
    return("PNG  Unexpected file reading error");
  }

  /* I/O initialization with standard C streams */
  png_init_io(png_ptr, fi->ifp);

  /* only allow 8bit or 24bit images */
  transform = PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_16
            | PNG_TRANSFORM_STRIP_ALPHA;

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.3,NULL);

  /* read image, the hilevel way */
  png_read_png(png_ptr, info_ptr , transform, NULL);

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.6,NULL);

  /* get image information */
  width  = png_get_image_width(png_ptr, info_ptr);
  height = png_get_image_height(png_ptr, info_ptr);
  bit_depth = png_get_bit_depth(png_ptr, info_ptr);
  color_type = png_get_color_type(png_ptr, info_ptr);
  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_PLTE)) {
    png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
  }

  /* get comment */
  png_get_text(png_ptr,info_ptr,&text_ptr,&num_text);
  if(num_text > 0) {
    commentsize = 1;

    for(i = 0; i < num_text; i++)
      commentsize += strlen(text_ptr[i].key) + 1 +
                     text_ptr[i].text_length + 2;

    if ((fi->comment = malloc(commentsize)) == NULL) {
      MdcPngWarn(png_ptr,"PNG  Can't malloc comment string");
    }else{
      fi->comment[0] = '\0';
      for (i = 0; i < num_text; i++) {
        strcat(fi->comment, text_ptr[i].key);
        strcat(fi->comment, "::");
        strcat(fi->comment, text_ptr[i].text);
        strcat(fi->comment, "\n");
        fi->comm_length = commentsize; /* otherwise not freed */
      }
    }
  }

  if (MDC_INFO) {
    MdcPrintLine('-',MDC_HALF_LENGTH);
    MdcPrntScrn("Short PNG Information (ver %s)\n",png_get_libpng_ver(png_ptr));
    MdcPrintLine('-',MDC_HALF_LENGTH);
    MdcPrntScrn("image width   : %u\n",width);
    MdcPrntScrn("image height  : %u\n",height);
    MdcPrntScrn("bit depth     : %u\n",bit_depth);
    MdcPrntScrn("color type    : %u\n",color_type);
    MdcPrintLine('-',MDC_HALF_LENGTH);
    MdcPrntScrn("comment block :\n\n%s\n",fi->comment);
    MdcPrintLine('-',MDC_HALF_LENGTH);
  }

  /* preset FILEINFO info */
  fi->mwidth = width; fi->mheight = height;
  fi->bits = 8; fi->type = BIT8_U;

  if (!MdcGetStructID(fi,1)) {
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
    return("PNG  Bad malloc IMG_DATA struct");
  }
  id = (IMG_DATA *)&fi->image[0];
  id->width = fi->mwidth;
  id->height= fi->mheight;
  id->bits  = fi->bits;
  id->type  = fi->type;

  id->buf = MdcGetImgBuffer(width * height);
  if (id->buf == NULL) {
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
    return("PNG  Bad malloc image buffer");
  }

  /* get images: png_destroy will free this one later */
  row_pointers = png_get_rows(png_ptr, info_ptr);
  if (row_pointers == NULL) {
    png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
    return("PNG  Unexpected error retrieving row_pointers");
  }
  rowbytes = png_get_rowbytes(png_ptr, info_ptr);
  switch(color_type) {
    case PNG_COLOR_TYPE_PALETTE:
      /* copy image rows */
      if (rowbytes != width) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        return("PNG  Unexpected number of bytes per row");
      }
      for (i=0; i<height; i++) {
         pbuf = id->buf + (i*width);
         memcpy(pbuf,row_pointers[i],width);
      }
      /* copy color palette */
      for (i=0; i < num_palette; i++) {
         fi->palette[i * 3 + 0] = (Uint8) palette[i].red;
         fi->palette[i * 3 + 1] = (Uint8) palette[i].green;
         fi->palette[i * 3 + 2] = (Uint8) palette[i].blue;
      }
      fi->map = MDC_MAP_PRESENT;
      break;

    case PNG_COLOR_TYPE_GRAY:
      /* copy image rows */
      if (rowbytes != width) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        return("PNG  Unexpeted number of bytes per row");
      }
      for (i=0; i<height; i++) {
         pbuf = id->buf + (i*rowbytes);
         memcpy(pbuf,row_pointers[i],rowbytes);
      }
      fi->map = MDC_MAP_GRAY;
      break;

    case PNG_COLOR_TYPE_GRAY_ALPHA:
      png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
      return("PNG  Color type GRAY + ALPHA unsupported");
      break;

    case PNG_COLOR_TYPE_RGB:
      MdcFree(id->buf); /* no longer needed */
      /* get contiguous RGB memory block */
      imgRGB = malloc(height * rowbytes);
      if (imgRGB == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        return("PNG  Couldn't allocate RGB buffer");
      }
      for (i=0; i<height; i++) {
         pbuf = imgRGB + (i*rowbytes);
         memcpy(pbuf,row_pointers[i],rowbytes);
      }
      fi->map  = MDC_MAP_PRESENT;
      fi->type = COLRGB; fi->bits = 24;
      id->type = COLRGB; id->bits = 24;
      id->buf  = imgRGB;
      break;

    case PNG_COLOR_TYPE_RGB_ALPHA:
      png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
      return("PNG  Color type RGB + ALPHA unsupported");
      break;

    default: return("PNG  Unsupported color type");
  }

  /* finishing up */
  png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);

  MdcCloseFile(fi->ifp);

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,1.0,NULL);

  return(NULL);

}

char *MdcWritePNG(FILEINFO *fi)
{
  char suffix[11], *pext;
  png_structp png_ptr;
  png_infop info_ptr;
  png_colorp palette;
  png_bytepp row_pointers;
  png_text text_ptr[3];
  IMG_DATA *id;
  Uint32 n, i, width, height, length, row_bytes;
  Uint8 *pbuf, FREE = MDC_NO;
  int bit_depth, color_type, interlace, compression, filter;

  MDC_FILE_ENDIAN = MDC_BIG_ENDIAN; /* always for a PNG */

  if ((MDC_FILE_STDOUT == MDC_YES) && (fi->number > 1))
    return("PNG  Output to stdout not appropriate for multiple images");

  if (XMDC_GUI == MDC_NO) {
    MdcDefaultName(fi,MDC_FRMT_PNG,fi->ofname,fi->ifname);
  }

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Writing PNG:");

  if (MDC_VERBOSE) MdcPrntMesg("PNG  Writing <%s> ...",fi->ofname);

  /* desktop output - no use of 16 bit feature */
  if (MDC_FORCE_INT != MDC_NO) {
    if (MDC_FORCE_INT != BIT8_U) {
      MdcPrntWarn("PNG  Only Uint8 pixels supported");
    }
  }

  /* check supported things */
  if (MDC_QUANTIFY || MDC_CALIBRATE) {
    MdcPrntWarn("PNG  Normalization loses quantified values!");
  }

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.0,NULL);

  length = strlen(fi->ofname);

  pext = strrchr(fi->ofname,'.');
  if (pext == NULL) pext = &fi->ofname[length];

  /* split up in separate files   */
  /* PNG is a single image format */
  for (n=0; n < fi->number; n++) {

     /* add slice number to filename */
     if (fi->number > 1) {
       sprintf(suffix,"-%.5u.%.3s",n+1,FrmtExt[MDC_FRMT_PNG]);
       strcpy(pext,suffix);
     }

     if ((MDC_FILE_STDOUT == MDC_YES) && (fi->number == 1)) {
       fi->ofp = stdout;
     }else{
       if (MdcKeepFile(fi->ofname))
         return("PNG  File exists!!");
       if ( (fi->ofp=fopen(fi->ofname,"wb")) == NULL )
         return ("PNG  Couldn't open file");
     }

     /* set some defaults */
     id = &fi->image[n];
     width = id->width;
     height= id->height;
     bit_depth = 8;
     if (fi->type == COLRGB) {
       /* true color */
       color_type = PNG_COLOR_TYPE_RGB;
       row_bytes = width * 3;
     }else{
        /* indexed */
       if (fi->map == MDC_MAP_GRAY) {
         /* gray */
         color_type = PNG_COLOR_TYPE_GRAY;
         row_bytes = width;
       }else{
         /* color */
         color_type = PNG_COLOR_TYPE_PALETTE;
         row_bytes = width;
       }
     }
     compression = PNG_COMPRESSION_TYPE_BASE;
     interlace = PNG_INTERLACE_NONE;
     filter = PNG_FILTER_TYPE_BASE;

  /* Create and initialize the png_struct with the desired error handler    */
  /* functions.  If you want to use the default stderr and longjump method, */
  /* you can supply NULL for the last three parameters.  We also check that */
  /* the library version is compatible with the one used at compile time,   */
  /* in case we are using dynamically linked libraries.  REQUIRED.          */
     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING
        , NULL, MdcPngErr, MdcPngWarn);
     if (png_ptr == NULL) return("PNG  Couldn't create write struct");

  /* allocate/initialize the image information data.  REQUIRED              */
     info_ptr = png_create_info_struct(png_ptr);
     if (info_ptr == NULL) {
       png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
       return ("PNG  Couldn't create write info struct");
     }

  /* Set error handling.  REQUIRED if you aren't supplying your own         */
  /* error handling functions in the png_create_write_struct() call.        */
     if (setjmp(png_jmpbuf(png_ptr))) {
       /* if we get here, we had a problem writing the file */
       png_destroy_write_struct(&png_ptr, &info_ptr);
       return ("PNG  Unexpected fire write error");
     }

  /* set up the output control using standard C streams */
     png_init_io(png_ptr, fi->ofp);

  /* can't write hilevel way, so here goes the hard way */

  /* Set the image information here.  Width and height are up to 2^31,       */
  /* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on  */
  /* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,      */
  /* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,  */
  /* or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or */
  /* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST      */
  /* currently be PNG_COMPRESSION_TYPE_BASE & PNG_FILTER_TYPE_BASE. REQUIRED */
     png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type
        , interlace, compression, filter);

  /* set the palette if there is one.  REQUIRED for indexed-color images */
     palette = (png_colorp)png_malloc(png_ptr, 256 * sizeof (png_color));
     if (color_type == PNG_COLOR_TYPE_PALETTE) {
       for (i=0; i<256; i++) {
          palette[i].red   = fi->palette[i*3 + 0];
          palette[i].green = fi->palette[i*3 + 1];
          palette[i].blue  = fi->palette[i*3 + 2];
       }
       png_set_PLTE(png_ptr, info_ptr, palette, 256);
     }
  /* You must not free palette here, because png_set_PLTE only makes a link */
  /* to the palette that you malloced.  Wait until you are about to destroy */
  /* the png structure.                                                     */

  /* optional significant bit chunk */
  /* if we are dealing with a grayscale image then */
  /* sig_bit.gray = true_bit_depth; */
  /* otherwise, if we are dealing with a color image then */
  /* sig_bit.red = true_bit_depth; */
  /* sig_bit.green = true_bit_depth; */
  /* sig_bit.blue = true_bit_depth; */
  /* if the image has an alpha channel then */
  /* sig_bit.alpha = true_bit_depth; */
  /* png_set_sBIT(png_ptr, info_ptr, sig_bit); */

  /* Optional gamma chunk is strongly suggested if you have any guess */
  /* as to the correct gamma of the image.                            */
  /* png_set_gAMA(png_ptr, info_ptr, gamma);                          */

  /* Optionally write comments into the image */
     mdcbufr[0] = '\0';
     if ( fi->acquisition_type != MDC_ACQUISITION_UNKNOWN ) {
       if ( !MdcMakeScanInfoStr(fi)) mdcbufr[0]='\0';
     }
     text_ptr[0].key = "Program";
     text_ptr[0].text = XMEDCON_PRGR;
     text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
     text_ptr[1].key = "Version";
     text_ptr[1].text = XMEDCON_VERSION;
     text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
     text_ptr[2].key = "Information";
     text_ptr[2].text = mdcbufr;
     text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt;
#ifdef PNG_iTXt_SUPPORTED
     text_ptr[0].lang = NULL;
     text_ptr[1].lang = NULL;
     text_ptr[2].lang = NULL;
#endif
     png_set_text(png_ptr, info_ptr, text_ptr, 3);

  /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs,        */
  /* note that if sRGB is present the gAMA and cHRM chunks must be ignored */
  /* on read and must be written in accordance with the sRGB profile       */

  /* write the file header information.  REQUIRED */
     png_write_info(png_ptr, info_ptr);

  /* get 8bits image */
     if ((id->type != COLRGB) && (id->type != BIT8_U)) {
       if ((pbuf = MdcGetImgBIT8_U(fi, n)) == NULL) {
         png_free(png_ptr, palette);
         png_destroy_write_struct(&png_ptr, &info_ptr);
         return("PNG  Bad malloc new image buffer");
       }
       FREE = MDC_YES;
     }else{
       pbuf = id->buf;
       FREE = MDC_NO;
     }

  /* allocate pointers to rows */
     row_pointers = (png_bytepp)malloc(sizeof(png_bytep) * height);
     if (row_pointers == NULL) {
       if (FREE == MDC_YES) MdcFree(pbuf);
       png_free(png_ptr, palette);
       png_destroy_write_struct(&png_ptr, &info_ptr);
       return("PNG  Couldn't alloc row_pointers table");
     }
     for (i=0; i<height; i++) row_pointers[i]=pbuf+(i * row_bytes);

  /* write the image */
     png_write_image(png_ptr, row_pointers);

  /* it is REQUIRED to call this to finish writing the rest of the file */
     png_write_end(png_ptr, info_ptr);

  /* free the row pointers */
     MdcFree(row_pointers);

  /* free image buffer */
     if (FREE == MDC_YES) MdcFree(pbuf);

  /* free palette */
     png_free(png_ptr, palette); palette=NULL;

  /* clean up after the write, and free any memory allocated */
     png_destroy_write_struct(&png_ptr, &info_ptr);

     if (MDC_PROGRESS)
       MdcProgress(MDC_PROGRESS_SET,(float)(n + 1)/(float)fi->number,NULL);

     MdcCloseFile(fi->ofp);

  }

  return(NULL);
}
