/*
 * $Id: saveZimage.cxx,v 4.0 2003/04/28 14:44:37 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  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 of the License, 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 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

/*------------------------------------------------------------------------
 *
 * Write a Z image file.
 *
 * Lousy format but useful nonetheless.
 *
 * By Stewart Heitmann, based on code by Hugues Talbot	17 Jan 1998
 * Updated Hugues Talbot	29 Apr 1998 for ImView
 * Modified Hugues Talbot	 3 Dec 2002 to handle type promotion
 *      
 *-----------------------------------------------------------------------*/


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "imview.hxx"
#include "imSystem.hxx"
#include "readZimage.hxx"

const char HEADER3[]="CSIROZ version 3"; /* old header */ 
const char HEADER[]="CSIROZ version 5";  /* S-Z addition */
const char HEADER7[]="CSIROZ version 7"; /* endianness support */
#define LINESIZE 200



/* local functions */
static const char * zpixtypestr(int ptype);
static int zpixsize(int ptype);
static int zpixrealsize(int ptype);
static int writeheader(FILE *fp, int n, int *nx, int *ny, int *nz, 
                       int *x0, int *y0, int *z0, int *ptype);
static int writecomps(FILE *fp, int n, int *nx, int *ny, int *nz, 
                      int *ptype, void **pixbuff);
static int promote_buffer(char *datain, int ptype, int nbpix, void **dataout);


int save_zimage(const char *fname,      /* input file name */
		int         numcomp,    /* number of components */
		int       *nx,          /* ptr to array of nx values (cols), one per component */
		int       *ny,          /* ptr to array of ny values (rows), one per component */
		int       *nz,          /* ptr to array of nz values (planes), one per component */
		int       *x0,          /* ptr to array of x0 offsets, one per component */
		int       *y0,          /* ptr to array of y0 offsets, one per component */
		int       *z0,          /* ptr to array of z0 offsets, one per component */
		int        *ptype,      /* ptr to array of pixel types, one per component */
		void      **pixbuff)    /* ptr to array of pixel buffer pointers, one per component */
   {
   /************************************************************
   Saves a multicomponent image in Z-IMAGE file format

   RETURN VALUE:
   0 upon success.

   1 if file could not be created or written onto.


   DESCRIPTION:
   Saves the multi-component image described by the set of
   arrays nx[], ny[], nz[], x0[], y0[], z0[], ptype[], pixbuff[] 
   into a Z-IMAGE file 'fname'.
   The user is responsible or creating the set of arrays.
   Consecutive elements in each array refers to consecutive components in the
   multi-component image. As such, all arrays must have the same number of 
   elements; the user supplies this number in 'numcomp'.


   Components may be either 2D (ie:nz[i]=1) or 3D (ie:nz[i]>1). If any
   component is 3D then the Z-IMAGE file is automatically written using the
   version 5 Z-IMAGE file format necessary for 3D images, otherwise the
   version 3 Z-IMAGE file format is used.

   TESTS:

   REFERENCES:

   KEYWORDS:
   **************************************************************/
   FILE *fp;

   /* open the image file for writing */
   fp = im_fopen(fname, "wb");
   if (!fp) 
	return 1;

   /* write the header */
   if( writeheader(fp,numcomp,nx,ny,nz,x0,y0,z0,ptype) != 0 )
        {
        fclose(fp);
	return 1;
        }

   /* write the pixel data */
   if( writecomps(fp,numcomp,nx,ny,nz,ptype,pixbuff) != 0 )
        {
        fclose(fp);
	return 1;
        }

   fclose(fp);
   return 0;
   }



/* local structures only */
struct Zpixcode
{
    char *str;
    int code;
    int size;
    int realsize;
};

/* handles promotion */
static struct Zpixcode Zpixtypes[] =
   {
      { "BINARY", IM_BINARY,  sizeof(char),   sizeof(char)     },  /* IM_BINARY */
      { "CHAR",   IM_UINT1,   sizeof(char),   sizeof(char)     },  /* IM_CHAR, IM_INT1 */
      { "GREY",   IM_INT1,    sizeof(char),   sizeof(char)     },
      { "INT",    IM_INT2,    sizeof(int),    sizeof(short)    }, 
      { "INT",    IM_UINT2,   sizeof(int),    sizeof(short)    },
      { "INT",    IM_INT4,    sizeof(int),    sizeof(int)      },  /* IM_INT4 */
      { "INT",    IM_UINT4,   sizeof(int),    sizeof(int)      },
      { "DOUBLE", IM_FLOAT,   sizeof(double), sizeof(float)    }, 
      { "DOUBLE", IM_DOUBLE,  sizeof(double), sizeof(double)   }   /* IM_DOUBLE */
   };



static
const char * zpixtypestr(int ptype)
   {
   /************************************************************
   Returns ptr to a string corresponding to the Z-IMAGE pixel
   type code in 'ptype'.
   **************************************************************/
   const int n = sizeof(Zpixtypes)/sizeof(struct Zpixcode);
   int i;
   for(i=0; i<n; i++)
      if(Zpixtypes[i].code == ptype)
	  return(Zpixtypes[i].str);
   return(NULL);
   }


static
int zpixsize(int ptype)
   {
   /************************************************************
   Returns number of bytes required to store a pixel with
   Z-IMAGE pixel type code 'ptype'.
   **************************************************************/
   const int n = sizeof(Zpixtypes)/sizeof(struct Zpixcode);
   int i;
   for(i=0; i<n; i++)
      if(Zpixtypes[i].code == ptype)
	  return(Zpixtypes[i].size);
   return 0;
   }


static
int zpixrealsize(int ptype)
   {
   /************************************************************
   Returns number of bytes required to store a pixel with
   Z-IMAGE pixel type code 'ptype'.
   **************************************************************/
   const int n = sizeof(Zpixtypes)/sizeof(struct Zpixcode);
   int i;
   for(i=0; i<n; i++)
      if(Zpixtypes[i].code == ptype)
	  return(Zpixtypes[i].realsize);
   return 0;
   }



static 
int maxdim(int n, int *nz)
   {
   /************************************************************
   Scans the nz[] array for any values greater than 1. 
   If one is found then returns 3 (for 3D) otherwise returns 2
   for (2D)
   **************************************************************/
   int i;
   for (i=0; i<n; i++)
      {
      if (nz[i]>1)
         return 3;
      }
   return 2;
   }



static
int writeheader(FILE *fp, int n, int *nx, int *ny, int *nz, 
                int *x0, int *y0, int *z0, int *ptype)
   {
   /************************************************************
   Writes a ZIMAGE header to the file fp according to the 
   components given in the arrays nx[],ny[],nz[],etc.
   If any of the components is 3D then the image header is written
   using the version 5 ZIMAGE file format which supports 3D images.
   Otherwise the file is written using the version 3 file format
   which supports only 2D images.
   Components cannot be 4D.

   Note for version Z-7: we only write little-endian data, hence
   the keyword is always "(null)"

   returns 0 upon success.
   returns 1 if write failed.
   **************************************************************/
   int i;

   if( maxdim(n,nz)==3 ) {
       /*-- write the version 5 header which supports 3D components --*/
       if (fprintf(fp, "%s\n%d\n", HEADER7, n) < 0) {
	   errprintf("saveZimage: Error writing first part of the header\n");
	   return 1;
       }
       for (i=0; i<n; i++) {
	   const int firstR = y0[i];
	   const int lastR  = y0[i] + ny[i] - 1;
	   const int firstC = x0[i];
	   const int lastC  = x0[i] + nx[i] - 1;
	   const int firstP = z0[i];
	   const int lastP  = z0[i] + nz[i] - 1;
	   const char* ptypestr = zpixtypestr(ptype[i]);
	   int res;
	   
	   res = fprintf(fp, "%d \"(null)\" %d %d %d %d %d %d %s\n",
			 i, firstR,lastR,firstC,lastC,firstP,lastP,ptypestr);
	   if (res<0) {
	       errprintf("saveZimage: error writing header component %d\n",
			 i);
	       return 1;
	   }
       }
   } else {
       /*-- write the version 3 header which supports 2D components only --*/
       if (fprintf(fp, "%s\n%d\n", HEADER7, n) < 0) {
	   errprintf("saveZimage: error writing first part of the header\n");
	   return 1;
       }
       for (i=0; i<n; i++) {
         const int firstR = y0[i];
         const int lastR  = y0[i] + ny[i] - 1;
	 const int firstC = x0[i];
         const int lastC  = x0[i] + nx[i] - 1;
         const char* ptypestr = zpixtypestr(ptype[i]);
         int res;

	 res = fprintf(fp, "%d \"(null)\" %d %d %d %d %s\n",
	               i, firstR, lastR, firstC, lastC, ptypestr);
         if (res<0) {
	     errprintf("saveZimage: error writing header component %d\n",
			 i);
	     return 1;
	 }
       }
   }

   /*-- do the funny stuff that nobody can explain --*/
   fprintf(fp, "  ");
   fseek(fp, -2L, 1);
   putc('\0', fp);

   return 0;
}

/* promotes data into a static buffer */
static int promote_buffer(char *datain, int ptype, int nbpix, void **dataout)
{
    static void *dataout_p = NULL;
    static int   nbbytes = 0;
    int          newsize, i, retval = 0;

    newsize = zpixsize(ptype) * nbpix;

    /* make sure we have enough room */
    if (newsize != nbbytes)
        dataout_p = realloc(dataout_p, newsize);
    
    switch(ptype) {
    case IM_BINARY:
    case IM_INT1:
    case IM_UINT1:
        /* no promotion needed */
        *dataout = datain;
        break;

    case IM_INT4:
    case IM_UINT4:
        /* no promotion needed */
        *dataout = datain;
        if (checkendian() != ENDIAN_LITTLE)
            swapintbuf((int*)(*dataout), nbpix);
        break;

    case IM_DOUBLE:
        /* no promotion needed */
        *dataout = datain;
         if (checkendian() != ENDIAN_LITTLE)
            swapdoublebuf((double*)(*dataout), nbpix);
        break;
        
    case IM_INT2: {
        short *in;
        int   *out;
        /* promote short to int */
        in = (short*)datain;
        out = (int*)dataout_p;
        /* straightforward copy */
        for (i = 0 ; i < nbpix ; ++i) {
            *out++ = *in++;
        }
        *dataout = dataout_p;
        // further processing
        if (checkendian() != ENDIAN_LITTLE)
            swapintbuf((int*)(*dataout), nbpix);
    }
        break;

    case IM_UINT2: {
        unsigned short *in;
        int   *out;
        /* promote short to int */
        in = (unsigned short *)datain;
        out = (int *)dataout_p;
        /* straightforward copy */
        for (i = 0 ; i < nbpix ; ++i) {
            *out++ = *in++;
        }
        *dataout = dataout_p;
        // further processing
        if (checkendian() != ENDIAN_LITTLE)
            swapintbuf((int*)(*dataout), nbpix);
    }
        break;

    case IM_FLOAT: {
        float  *in;
        double *out;
        /* promote short to int */
        in = (float *)datain;
        out = (double *)dataout_p;
        /* straightforward copy */
        for (i = 0 ; i < nbpix ; ++i) {
            *out++ = *in++;
        }
        *dataout = dataout_p;
        if (checkendian() != ENDIAN_LITTLE)
            swapdoublebuf((double*)(*dataout), nbpix);
    }
        break;

    default:
        *dataout = NULL;
        retval = 1; 
        break;
        
    }

    return retval;
}

static
int writecomps(FILE *fp, int n, int *nx, int *ny, int *nz, 
               int *ptype, void **pixbuff)
{
   /************************************************************
   Writes the pixel data buffers from each component to fp.
   The data is written one row at a time to be a bit gentler on
   the io buffer.

   returns 0 upon success.
   returns 1 if write failed.
   **************************************************************/
   int i, nb;

   /*-- for each component ... --*/
   for (i=0; i<n; i++) {
       const int pixsize = zpixsize(ptype[i]);
       const int realpixsize = zpixrealsize(ptype[i]);
       char *pbuff = (char *)(pixbuff[i]);
       void *prombuff = NULL;
       int row,plane;

       if (ptype[i] == IM_BINARY) {
	   char *inverted = new char[nx[i]];
	   int   k;
	   /* binary images never need to be promoted */
	   for (plane=0; plane<nz[i]; plane++)
	       for (row=0; row<ny[i]; row++) {
		   for (k = 0 ; k < nx[i] ; k++)
		       inverted[k] = !pbuff[k];
		   if( (nb=(int)(fwrite(inverted,pixsize,nx[i],fp))) != nx[i]) {
		       errprintf("saveZimage: wrote %d bytes instead of expected %d\n"
				 "row %, plane %d\n",
				 nb, nx[i], row, plane);
		       return 1;
		   }
		   pbuff += nx[i] * pixsize;
	       }
	   delete[] inverted;
       } else {
	   for (plane=0; plane<nz[i]; plane++)
	       for (row=0; row<ny[i]; row++) {
                   if (promote_buffer(pbuff, ptype[i], nx[i], &prombuff) == 0) {
                       if( (nb=(int)(fwrite(prombuff,pixsize,nx[i],fp))) != nx[i]) {
                           errprintf("saveZimage: wrote %d bytes instead of expected %d\n"
                                     "row %, plane %d\n",
                                     nb, nx[i], row, plane);
                           return 1;
                       }
                   } else {
                       errprintf("saveZimage: buffer promotion failed\n");
                       return 2;
                   }
		   pbuff += nx[i] * realpixsize;
	       }
       }
   }
   return 0;
}

