/*
 * $Id: readpnm.cxx,v 4.1 2004/05/25 06:40:20 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.
 * */

/*------------------------------------------------------------------------
 *
 * Reads the pbm, pgm and ppm formats
 * This will be particularly useful for reading just about anything
 * thanks to the ppm library (netpbm or whaterver...)
 *
 * Based on similar readers in Z-IMAGE
 *
 * Hugues Talbot	28 Jul 1998
 *
 *-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include "imview.hxx"
#include "imSystem.hxx"
#include "../imageIO.hxx"
#include "readpnm.hxx"

extern imageIO *IOBlackBox;

static int load_pbm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp);

static int load_pbm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp);

static int load_pgm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp);

static int load_pgm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp);

static int load_ppm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp);

static int load_ppm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp);

static bool garbage;
static long numgot;

int readPNMImage(const char *name)
{
    void      *p;
    int       start[3], end[3], nbsamples = 0;
    pixtype   thepixtype;
    imgtype   theimgtype;
    FILE *fp;
    char  magic_num[3];
    int   retval = 0;
    
    if ((fp = im_fopen(name,"rb")) == NULL)
	return 1;

    fscanf (fp,"%2s",magic_num);

    switch(magic_num[1]) {
      case '1':
	retval = load_pbm_ascii(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 1;
	break;
      case '2':
	retval = load_pgm_ascii(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 1;
	break;
      case '3':
	retval = load_ppm_ascii(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 3;
	break;
      case '4':
	retval = load_pbm_raw(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 1;
	break;
      case '5':
	retval = load_pgm_raw(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 1;
	break;
      case '6':
	retval = load_ppm_raw(fp, start, end, &thepixtype, &theimgtype, &p);
	nbsamples = 3;
	break;
      default:
	errprintf("readPNMImage: Unsupported PNM format\n");
	retval = 100; // unsupported format
	break;
    }

    if (retval == 0) {
	if (garbage)
	    errprintf("readPNMImage: There was some garbage in this picture\n");
	IOBlackBox->setCurrBuffp((void **)p);
	IOBlackBox->setCurrImgWidth(end[0]-start[0]+1);
	IOBlackBox->setCurrImgHeight(end[1]-start[1]+1);
	IOBlackBox->setCurrImgThickness(end[2]-start[2]+1);
	IOBlackBox->setXOffset(start[0]);
	IOBlackBox->setYOffset(start[1]);
	IOBlackBox->setZOffset(start[2]);
	IOBlackBox->setCurrImgType((imgtype)theimgtype);
	IOBlackBox->setCurrPixType((pixtype)thepixtype);
	IOBlackBox->setCurrImgNbComps(1); // only a single image in a PNM file
	IOBlackBox->setCurrImgNbSamples(nbsamples);
	IOBlackBox->setCurrImgColourMap(0);
	IOBlackBox->setImgDesc("PNM");
    }
    
    fclose(fp);
    
    return retval;
}



static int getint(FILE *fp)
{
  int c, i, firstchar;

  /* note:  if it sees a '#' character, all characters from there to end of
     line are appended to the comment string */

  /* skip forward to start of next number */
  c = getc(fp);
  while (1) {
      /* eat comments */
      if (c=='#') {   /* if we're at a comment, read to end of line */
	  char cmt[256], *sp;

	  sp = cmt;  firstchar = 1;
	  while (1) {
	      c=getc(fp);
	      if (firstchar && c == ' ') firstchar = 0;  /* lop off 1 sp after # */
	      else {
		  if (c == '\n' || c == EOF) break;
		  if ((sp-cmt)<250) *sp++ = c;
	      }
	  }
	  *sp++ = '\n';
	  *sp   = '\0';
      }

      if (c==EOF) return 0;
      if (c>='0' && c<='9') break;   /* we've found what we were looking for */
      
      /* see if we are getting garbage (non-whitespace) */
      if (c!=' ' && c!='\t' && c!='\r' && c!='\n' && c!=',') garbage = true;

      c = getc(fp);
  }
  /* we're at the start of a number, continue until we hit a non-number */
  i = 0;
  while (1) {
      i = (i*10) + (c - '0');
      c = getc(fp);
      if (c==EOF) return i;
      if (c<'0' || c>'9') break;
  }

  numgot++;
  return i;
}

int pnmnbsubfiles(const char *)
{
    return 1; // particularly simple
}

static int load_pgm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, maxgrey, j, i;
    unsigned char *cp;
    short         *sp;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);
    maxgrey = getint(fp);

    if (maxgrey <= 1)
	*pixtype = IM_BINARY;
    else if (maxgrey <= 255)
	*pixtype = IM_UINT1;
    else *pixtype = IM_INT2; // short int is the larger type

    *imgtype = IM_SPECTRUM; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(sizeof(void *));
    
    if ((*pixtype == IM_BINARY) || (*pixtype == IM_UINT1)) {
	// get all the data
	buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
	cp = (unsigned char *)(buffp[0]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*cp++ = (unsigned char)getint(fp);
	    }
	}
    } else { // short
	buffp[0] = malloc(ncol*nrow*sizeof(short));
	sp = (short *)(buffp[0]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*sp++ = (short)getint(fp);
	    }
	}
    }

    *inbuffp = buffp;
    
    return retval;
}

//
// Read short values found in RAW pgm output.
//

static int getshort(FILE *fp)
{
  int c1, c2;

  c1 = getc(fp);
  if (c1 == EOF)
      return 0;
  c2 = getc(fp);
  if (c2 == EOF)
      return 0;

  numgot++;

  return (c2 << 8) | c1;
}


static int load_pgm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, maxgrey, j, i;
    unsigned char *cp;
    short         *sp;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);
    maxgrey = getint(fp);

    if (maxgrey <= 1)
	*pixtype = IM_BINARY;
    else if (maxgrey <= 255)
	*pixtype = IM_UINT1;
    else *pixtype = IM_INT2; // short int is the larger type

    *imgtype = IM_SPECTRUM; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(sizeof(void *));
    
    if ((*pixtype == IM_BINARY) || (*pixtype == IM_UINT1)) {
	// get all the data
	buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
	cp = (unsigned char *)(buffp[0]);
	numgot = fread(cp, (size_t) 1, (size_t) nrow*ncol, fp);
    } else { // short
	buffp[0] = malloc(ncol*nrow*sizeof(short));
	sp = (short *)(buffp[0]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*sp++ = getshort(fp);
	    }
	}
    }

    *inbuffp = buffp;
    
    return retval;
}


static int getbit(FILE *fp)
{
  int c;

  /* skip forward to start of next number */
  c = getc(fp);
  while (1) {
    /* eat comments */
    if (c=='#') {   /* if we're at a comment, read to end of line */
      char cmt[256], *sp;
      sp = cmt;
      while (1) {
        c=getc(fp);
        if (c == '\n' || c == EOF) break;

        if ((sp-cmt)<250) *sp++ = c;
      }
      *sp++ = '\n';
      *sp = '\0';

    }
    if (c==EOF) return 0;
    if (c=='0' || c=='1') break;   /* we've found what we were looking for */

    /* see if we are getting garbage (non-whitespace) */
    if (c!=' ' && c!='\t' && c!='\r' && c!='\n' && c!=',') garbage=true;

    c = getc(fp);
  }
  numgot++;
  return(c-'0');
}



static int load_pbm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, j, i;
    unsigned char *cp;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);

    *pixtype = IM_BINARY;
    *imgtype = IM_SPECTRUM; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(sizeof(void *));
    
	// get all the data
    buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
    cp = (unsigned char *)(buffp[0]);
    for (j = 0 ; j < ncol ; j++) {
	for (i = 0 ; i < nrow ; i++) {
	    *cp++ = (unsigned char)getbit(fp);
	}
    }

    *inbuffp = buffp;
    
    return retval;
}

static int load_pbm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, k, j, i;
    unsigned char *cp;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);

    *pixtype = IM_BINARY;
    *imgtype = IM_SPECTRUM; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(sizeof(void *));
    
	// get all the data
    buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
    cp = (unsigned char *)(buffp[0]);

    for (k = 0, j = 0 ; j < ncol ; j++) {
	for (i = 0 ; i < nrow ; i++) {
	    if (i%8 == 0) {
		// read one char, ignore errors
		k = getc(fp);
		if (k == EOF) garbage = true;
	    }
	    *cp++ = (unsigned char)(k&0x80 ? 1: 0);
	    k <<= 1; // double k
	}
    }

    *inbuffp = buffp;
    
    return retval;
}


static int load_ppm_ascii(FILE *fp,
			  int   start[3],
			  int   end[3],
			  pixtype *pixtype,
			  imgtype *imgtype,
			  void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, maxgrey, j, i;
    unsigned char *cpR, *cpG, *cpB;
    short         *spR, *spG, *spB;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);
    maxgrey = getint(fp);

    if (maxgrey <= 1)
	*pixtype = IM_BINARY;
    else if (maxgrey <= 255)
	*pixtype = IM_UINT1;
    else *pixtype = IM_INT2; // short int is the larger type

    *imgtype = IM_RGB; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(3*sizeof(void *));
    
    if ((*pixtype == IM_BINARY) || (*pixtype == IM_UINT1)) {
	// get all the data
	buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
	buffp[1] = malloc(ncol*nrow*sizeof(unsigned char));
	buffp[2] = malloc(ncol*nrow*sizeof(unsigned char));
	cpR = (unsigned char *)(buffp[0]);
	cpG = (unsigned char *)(buffp[1]);
	cpB = (unsigned char *)(buffp[2]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*cpR++ = (unsigned char)getint(fp);
		*cpG++ = (unsigned char)getint(fp);
		*cpB++ = (unsigned char)getint(fp);
	    }
	}
    } else { // short
	buffp[0] = malloc(ncol*nrow*sizeof(short));
	buffp[1] = malloc(ncol*nrow*sizeof(short));
	buffp[2] = malloc(ncol*nrow*sizeof(short));
	spR = (short *)(buffp[0]);
	spG = (short *)(buffp[1]);
	spB = (short *)(buffp[2]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < 3*nrow ; i++) {
		*spR++ = (short)getint(fp);
		*spG++ = (short)getint(fp);
		*spB++ = (short)getint(fp);
	    }
	}
    }

    *inbuffp = buffp;
    
    return retval;
}


static int load_ppm_raw(FILE *fp,
			int   start[3],
			int   end[3],
			pixtype *pixtype,
			imgtype *imgtype,
			void **inbuffp)
{
    int            retval = 0;
    void         **buffp;
    int            ncol, nrow, maxgrey, j, i;
    unsigned char *cpR, *cpG, *cpB;
    short         *spR, *spG, *spB;
    
    garbage = false;
    numgot = 0;

    ncol = getint(fp);
    nrow = getint(fp);
    maxgrey = getint(fp);

    if (maxgrey <= 1)
	*pixtype = IM_BINARY;
    else if (maxgrey <= 255)
	*pixtype = IM_UINT1;
    else *pixtype = IM_INT2; // short int is the larger type

    *imgtype = IM_RGB; // in all cases for a first approximation

    start[0] = 0;
    start[1] = 0;
    start[2] = 0;
    end[0] = ncol-1;
    end[1] = nrow-1;
    end[2] = 0; // no 3D data in there.

    buffp = (void **)malloc(3*sizeof(void *));
    
    if ((*pixtype == IM_BINARY) || (*pixtype == IM_UINT1)) {
	// get all the data
	// get all the data
	buffp[0] = malloc(ncol*nrow*sizeof(unsigned char));
	buffp[1] = malloc(ncol*nrow*sizeof(unsigned char));
	buffp[2] = malloc(ncol*nrow*sizeof(unsigned char));
	cpR = (unsigned char *)(buffp[0]);
	cpG = (unsigned char *)(buffp[1]);
	cpB = (unsigned char *)(buffp[2]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*cpR++ = getc(fp);
		*cpG++ = getc(fp);
		*cpB++ = getc(fp);
	    }
	}
    } else { // short
	buffp[0] = malloc(ncol*nrow*sizeof(short));
	buffp[1] = malloc(ncol*nrow*sizeof(short));
	buffp[2] = malloc(ncol*nrow*sizeof(short));
	spR = (short *)(buffp[0]);
	spG = (short *)(buffp[1]);
	spB = (short *)(buffp[2]);
	for (j = 0 ; j < ncol ; j++) {
	    for (i = 0 ; i < nrow ; i++) {
		*spR++ = getshort(fp);
		*spG++ = getshort(fp);
		*spB++ = getshort(fp);
	    }
	}
    }

    *inbuffp = buffp;
    
    return retval;
}
