/* tifftori8.c
 *
 * Procedure to convert a tiff file to an array of unsigned char data.
 * This array can then be used as the dataPtr field in a pixmap.
 * All code contained herein written by Scott Bulmahn for NCSA.
 *
 * Modified by Peter Webb, Summer 1990
 *   - Main routine returns FileInfo structure.
 *   - Changed error handling.
 *   - Modifications for machines with non-standard word sizes.
 *
 */

/* Standard include files */

#include <stdio.h>
#include <math.h>

/* Package include files */

#include "tiff2ri8.h"
#include "types.h"      /* Package-wide type definitions */
#include "error.h"      /* Error codes */
#include "extern.h"     /* External routines and global variables */
#include "reformat.h"   /* Package-wide contants */
#include "window.h"

/* Static variables */

static unsigned char *buffStart,*buffPtr;
static unsigned char *theData;

/*
 * DoThePalette()
 * 
 * This routine will interpret the info record to determine if palette 
 * information is available. The palette info may be of two types, gray scale 
 * or color. If no palette is available, then use the photometric result to
 * create one.
 */
 
#define MinSampleWhite	0
#define MinSampleBlack	1
#define RGBColorUnits	2

static void DoThePalette (theInfo, info)
     struct tagData * theInfo;
     FileInfo *info;
{
  double rCurve;
  int i;
  short * numPtr;
  int numColors;
  unsigned char Red[256], Green[256], Blue[256];
  
  info->values |= (BitMask)CvtPalette;

/* first determine if the palette should be gray scale or what. */
	
  numColors =  1 << (int) (theInfo->numBits);
  
  switch (theInfo->photo) {
	
/* min sample value is white, max is black. */
		
  case MinSampleWhite:

    rCurve = 1.0/ (double) power (10, (int) theInfo->GrayResponseUnit);
    if (theInfo->GrayResponseCurve)
      for (i = 0, numPtr = theInfo->GrayResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) (255.0 - ((*numPtr * rCurve) * 255.0));
    else
      for (i = 0, numPtr = theInfo->GrayResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) (255 - i);
    break;
    
/* min sample value is Black, max is White, gray scale. */
		
  case MinSampleBlack:

    rCurve = 1.0/ (double) power (10, (int) theInfo->GrayResponseUnit);
    if (theInfo->GrayResponseCurve)
      for (i = 0, numPtr = theInfo->GrayResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) ((*numPtr * rCurve) * 255.0);
    else
      for (i = 0, numPtr = theInfo->GrayResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) i;
    break;
	
/* RGB Colors. */
		
  case RGBColorUnits:

    rCurve = 1.0/ (double) pow (10, (int) theInfo->ColorResponseUnit);
    if (theInfo->ColorResponseCurve)
      for (i = 0, numPtr = theInfo->ColorResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) ((*numPtr * rCurve) * 255.0);
    else
      for (i = 0, numPtr = theInfo->ColorResponseCurve; i < numColors; i++)
	Red [i] = Green [i] = Blue [i] =
	  (unsigned char) (255 - i);
    break;

  default:
    err_msg(TiffToHDF, NoPalette);  
    info->values &= (BitMask)~CvtPalette;  /* Palette not found */
    break;

  }
		
/* Interleave the palette */

  for (i = 0; i < numColors; i++)
    {
      info->palette [i*3] = Red [i];
      info->palette [i*3+1] = Green [i];
      info->palette [i*3+2] = Blue [i];
    }
}


/* 
 * GetIFD()
 *
 * Get the directory info, such as the number of tags
 *
 */ 
   
static GetIFD(theOffset,myIFD)
     long theOffset;
     struct IFDrec *myIFD;
{
  unsigned char * theNum;

  theNum = buffStart + theOffset;
  myIFD->num = ((*theNum) << 4) | (*(theNum+1)) ;    /* Get the next 16 bits */
  myIFD->next= (long) ((long)theNum + (12 * myIFD->num));
}


/*
 * DotheRecords
 *
 * Get all of the data from the file tags, such as the dimensions of the
 * image, and the offset to the actual data
 *
 */

static DoTheRecords(myTagData,theIFD)
     struct tagData *myTagData;
     struct IFDrec *theIFD;   

{
  int i;
  struct infoRec theInfo;

  for(i=1;i<theIFD->num;i++)
    {
      GetInfo(&theInfo);

      switch(theInfo.tag)
	{
     	case ImageWidthTag:
	  myTagData->dimX = theInfo.val;
	  theInfo.tagName = "ImageWidth";
	  break;

       	case ImageLengthTag:
	  myTagData->dimY = theInfo.val;
	  theInfo.tagName = "ImageLength";
	  break;

       	case StripOffsetTag:
	  if (myTagData->rowsPerStrip == 65536)
	    myTagData->rowsPerStrip = myTagData->dimY;
	 
	  if (myTagData->numberStrips >= myTagData->dimY)
	    myTagData->numberStrips =1;
	  else myTagData->numberStrips =
	    (myTagData->dimY/myTagData->rowsPerStrip);

	  myTagData->datOffset = (long)theInfo.val;
	  theInfo.tagName = "StripOffsets";
	  break;

       	case CompressionTag:
	  theInfo.tagName = "Compression";
	  myTagData->compression = theInfo.val;
	  break;
	 
    	case NumBitsTag:
	  theInfo.tagName = "BitsPerSample";
	  myTagData->numBits = (short)theInfo.val;
	  break;

       	case PhotoMetricTag:
	  theInfo.tagName = "PhotoMetricInterpretation";
	  myTagData->photo = (short) theInfo.val;
	  break;

	case GrayUnitTag:
	  theInfo.tagName = "ColorResponsUnit";
	  myTagData->ColorResponseUnit = (short) theInfo.val;
	  break;

	case GrayCurveTag:
	  theInfo.tagName = "ColorResponsUnit";				
	  myTagData->ColorResponseUnit = (short) theInfo.val;
	  break;

	case ColorUnitTag:
	  theInfo.tagName = "GrayResponsUnit";
	  myTagData->GrayResponseUnit = (short) theInfo.val;
	  break;
			
	case ColorCurveTag:
	  theInfo.tagName = "ColorResponsUnit";				
	  myTagData->ColorResponseUnit = (short) theInfo.val;
	  break;

	case PlanarConfigTag:
	  theInfo.tagName = "PlanarConfiguration";
	  myTagData->planarConfig = theInfo.val;
	  break;

	case ResolutionUnitTag:
	  theInfo.tagName = "ResolutionUnit";
	  break;
	 
	case SamplesPerPixelTag:
	  theInfo.tagName = "SamplesPerPixel";
	  myTagData->samplesPixel = (short)(theInfo.val);
	  break;
	 
	case StripCountsTag:
	  theInfo.tagName = "StripByteCounts";
	  break;

	case XResTag:
	  theInfo.tagName = "XResolution";
	  break;

	case RowsPerStripTag:
	  theInfo.tagName = "RowsPerStrip";
	  myTagData->rowsPerStrip = theInfo.val;
	  break;

       	default:
	  theInfo.tagName = " ";
	  break;
    	}
      
#ifdef notdef
printf("Record\n");
printf(" name %s\n", theInfo.tagName);
printf(" tag %d\n", theInfo.tag);
printf(" type %d\n", theInfo.type);
printf(" length %d\n", theInfo.length);
printf(" val %d\n", theInfo.val);
#endif

    }
}  


/*
 * GetInfo()
 *
 * Called by DoTheRecords.  This gets just 1 record worth of data
 * and returns it.  Make adjustments for word sizes that are different from
 * the expected 16 (shorts) and 32 (longs) bits.
 *
 */ 

static GetInfo(theInfoRec)
  struct infoRec *theInfoRec;

{
  theInfoRec->tag = SHORT(buffPtr);
  buffPtr += 2;
  
  theInfoRec->type = SHORT(buffPtr);
  buffPtr += 2;
	
  theInfoRec->length = LONG(buffPtr);
  buffPtr += 4;
	
  if (theInfoRec->type == 3)
    theInfoRec->val = (long) SHORT(buffPtr);
  else 
    theInfoRec->val = LONG(buffPtr);
	
  buffPtr += 4;
}



/*
 * DoTheData()
 *
 * This routine gets the data from the file, and goes thru it.
 * It goes thru the data and converts it to an unsigned char, keeping
 * track of the min and max values.
 * Finally it puts all of this shit into a global variable to be returned
 * by the calling procedure.  If there is the inverse photometric interp., the 
 * routine flips the data by XOR-ing it with $ff
 *
 */

static ErrorCode DoTheData(myTagData,theBuffPtr,theSize)
     struct tagData *myTagData;
     unsigned char *theBuffPtr;
     long theSize;

{
  long i=0;
  unsigned char *tempPtr;
  short min,max;
  register unsigned char *dataPtr;
  register short aShort;

  if (theSize == 0) return(err_msg(TiffTheData, InternalError));
  if (theBuffPtr == NULL) return(err_msg(TiffTheData, NullPtr));
  if (NULL == (theData = (unsigned char *)malloc(theSize)))
     return(err_msg(TiffTheData, NoMemory));

  dataPtr = theData;

/*
 * There are two different photometric interpretations, and for each case,
 * there are two more cases : 8 and 16 bit number.  So there are 4 different
 * loops, one to handle each case.  The code is a lot longer and a lot more
 * messy, but this way the code executes a lot faster 
 */



/**************  do normal photometric interpretation */

  if (myTagData->photo == 0 )
    {
      if (myTagData->numBits == 8) 
	{

	  for (tempPtr = theBuffPtr,min = max= 0;i<theSize;
	       i++,tempPtr++,dataPtr++)
	    {
	      aShort = (short) (*tempPtr);
	      if (aShort & (1 << 16)) aShort += 255;     

	      *dataPtr = (unsigned char)aShort;  
	      
	      if (aShort<min) min = aShort;
	      if (aShort>max) max = aShort;
	    }
	}
      
      else if (myTagData->numBits == 16) 
        {  
	  for (tempPtr = theBuffPtr,min = max= 0;i<theSize;i++,tempPtr+=2,
	       dataPtr++)
            {
	      aShort = SHORT(tempPtr);
              if (aShort & (1 << 16)) aShort += 255;

              *dataPtr = (unsigned char)aShort;
              if (aShort<min) min = aShort;
              if (aShort>max) max = aShort;
	    }
	}
    }

 /***************  this is for the negative photometric interp. */

  if (myTagData->photo ==1 )
    {
      if (myTagData->numBits == 8)
	{
	  for (tempPtr = theBuffPtr,min = max= 0; i<theSize;
	       i++,tempPtr++,dataPtr++)
	    {	  
	      aShort = (short) (*tempPtr);
	      if (aShort & (1<<16)) aShort +=255;
	      aShort = (aShort ^ 0xff);        /* XOR with $ff */
	      *dataPtr = (unsigned char)aShort;	  
	      
	      if (aShort<min) min = aShort;
	      if (aShort>max) max = aShort;
	    }
	} 
	 
      else if (myTagData->numBits ==16)
        {
	  for (tempPtr = theBuffPtr,min = max= 0;i<theSize;
	       i++,tempPtr+=2,dataPtr++)
            {
              aShort = SHORT(tempPtr);
              if (aShort & (1<<16)) aShort += 255;
	      aShort ^= 0xff;

              *dataPtr = (unsigned char)aShort;
              if (aShort<min) min = aShort;
              if (aShort>max) max = aShort;
	    }
	}
    }
	
  dataPtr = theData;
  return (AllOk);
}





/* 
 * SetUpDefaults()
 *
 * This routine sets up the default tagData record.  Assume that there is
 * normal photo interp, and set the dimensions to zero, etc.
 * Also set the rows per strip to some ridiculously huge-ass number.  This
 * way it is possible to later calculate the number of strips.
 *
 */

static void SetUpDefaults(myTagData)
     struct tagData *myTagData;
     
{
  myTagData->photo=1;
  myTagData->dimX =0;
  myTagData->dimY=0;
  myTagData->min =0;
  myTagData->max =255;
  myTagData->numBits = 8;
  myTagData->rowsPerStrip = 65536;
  myTagData->numberStrips =0;
  myTagData->samplesPixel = 1;
  myTagData->GrayResponseUnit = 2;
  myTagData->GrayResponseCurve = NULL;
  myTagData->ColorResponseUnit = 2;
  myTagData->ColorResponseCurve = NULL;
}


/*
 * TiffToRi8()
 * 
 * This is the main function to return the pix data and the image dims
 *
 */

ErrorCode TiffToRI8 (theName, file_info)
     char *theName;                     /* input file name */
     FileInfo *file_info;
{
  long IFDoffset;
  struct IFDrec theIFD;
  struct tagData theTagData;
  long mySize;
  ErrorCode myError = AllOk;
  FILE *theFile;
  long magic =  0, version = 0;

/* set up the defaults */
	
  SetUpDefaults(&theTagData);
  if (NULL == (theFile = fopen(theName,"rbw+")) )
    return (err_msg(TiffToHDF, InputOpenFailed));
	
/* Get some space for the image */

  buffStart = (unsigned char *)malloc(300000);
  if (!buffStart)
    {
      fclose(theFile);
      return(err_msg(TiffToHDF, NoMemory));
    }
		
/* Read in the TIFF file data */

  buffPtr = buffStart;
  fread(buffStart,1,290000,theFile);
  fclose(theFile);
	
/* Examine the first 4 bytes to see if this is a TIFF file.  Make sure to
 * cope with machines that might have oddly-sized shorts.  Also, extract the
 * version number of the TIFF implementation.
 */
	
  switch(sizeof(short))
    {
    case 1:   /* Who knows.  Some compiler probably does this. */ 

      if (sizeof(long) < 2)
	return(err_msg(TiffToHDF, BadHardware));

    case 2:   /* Most architectures */
    case 4:  /* Same size as a long. */

      magic = *(short *)buffPtr;
      version = *(short *)(buffPtr+2);
      break;

    case 8:  /* Cray */

      magic = (*(unsigned long *)buffPtr) >> 48;
      version = SHORT(buffPtr+2);
      break;

    default:  /* What? */

      return(err_msg(TiffToHDF, InternalError));
    }
    
/* Check magic number and version. */

  if (magic != 0x4D4D)
    {
      free(buffStart);
      return (err_msg(TiffToHDF, BadFileData));
    }
	 
/* Is this the correct version? */
	
  if (version != 42 )
    {
      free(buffStart);
      return (err_msg(TiffToHDF, BadInputVersion));
    }
	
/* Get the offset of the Image File Directory.  It's a 32 bit number.
 * Some architectures don't permit long pointers on other than 64 
 * bit-boundaries, thus all the bitwise OR's.
 */
	
  buffPtr += 4;
  IFDoffset = LONG(buffPtr);

/* get the image file directory. */
  
  GetIFD(IFDoffset,&theIFD); 

  buffPtr  = (unsigned char *)(buffStart + IFDoffset + 2);

/* get the fields. */
	
  DoTheRecords(&theTagData,&theIFD);
  if (theTagData.dimX == 0 || theTagData.dimY == 0)
    return(err_msg(TiffToHDF, ZeroSizeImage));

  if (theTagData.samplesPixel != 1)
    return(err_msg(TiffToHDF, ImageTooComplex));
  else
    {
      file_info->width = theTagData.dimX;
      file_info->height = theTagData.dimY;
      mySize = (file_info->width)*(file_info->height);
		
      myError = DoTheData(&theTagData,
			  (unsigned char *)(buffStart + theTagData.datOffset),
			  mySize);
      file_info->image = theData;    /* this returns the ptr to the data */
      file_info->values |= (BitMask)CvtImage;
      if (myError == AllOk)	     /* get the color table if one exists. */
	DoThePalette (&theTagData, file_info);
    }
		
  free(buffStart);
  return (myError);
}




