/* +-------------------------------------------------------------------+ */
/* | Copyright 1993, David Koblas (koblas@netcom.com)		       | */
/* |								       | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.	 There is no	       | */
/* | representations about the suitability of this software for	       | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.					       | */
/* |								       | */
/* +-------------------------------------------------------------------+ */

/* $Id: readWriteSGI.c,v 1.21 2005/03/20 20:15:34 demailly Exp $ */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <sys/fcntl.h>
#include "image.h"

extern void *xmalloc(size_t n);
extern void AlphaWarning(char *format, int mode);

#define RLE(bpp)		(0x0100 | (bpp))

typedef struct {
   unsigned short imagic;
   unsigned short type;
   unsigned short dim;
   unsigned short xsize, ysize, zsize;
   unsigned int min, max;
   unsigned int wasteBytes;
   char name[80];
   unsigned int colorMap;
   FILE *file;
   unsigned char *tmp;
   unsigned int swapFlag;
   unsigned int rleEnd;
   unsigned int *rowStart;
   int *rowSize;
} ImageSGI;

static void ConvertShort(unsigned short *array, long length) 
{
   unsigned b1, b2;
   unsigned char *ptr;

   ptr = (unsigned char *)array;
   while (length--) {
      b1 = *ptr++;
      b2 = *ptr++;
      *array++ = (b1 << 8) | (b2);
   }
}

static void ConvertLong(unsigned int *array, long length) 
{
   unsigned b1, b2, b3, b4;
   unsigned char *ptr;

   ptr = (unsigned char *)array;
   while (length--) {
      b1 = *ptr++;
      b2 = *ptr++;
      b3 = *ptr++;
      b4 = *ptr++;
      *array++ = (b1 << 24) | (b2 << 16) | (b3 << 8) | (b4);
   }
}

static void ImageSGIClose(ImageSGI *image) 
{
   if (image->file) fclose(image->file);
   if (image->tmp) free(image->tmp);
   if (image->rowSize) free(image->rowSize);
   if (image->rowStart) free(image->rowStart);
   free(image);
}

static ImageSGI *ImageSGIOpen(const char *fileName)
{
   union {
      int testWord;
      char testByte[4];
   } endianTest;
   ImageSGI *image;
   int x;

   image = (ImageSGI *)xmalloc(sizeof(ImageSGI));

   if (image == NULL) {
      fprintf(stderr, "Out of memory!\n");
      return NULL;
   }
   if ((image->file = fopen(fileName, "rb")) == NULL) {
      perror(fileName);
      free(image);
      return NULL;
   }

   endianTest.testWord = 1;
   image->swapFlag = (int) endianTest.testByte[0];

   fread(image, 1, 12, image->file);
   if (image->swapFlag) {
      ConvertShort(&image->imagic, 6);
   }

   x = image->xsize*2+10;
   image->tmp = (unsigned char *)malloc(x);
   if (image->tmp == NULL) {
      fprintf(stderr, "Out of memory!\n");
      ImageSGIClose(image);
      return NULL;
   }

   if ((image->type & 0xFF00) == 0x0100) {
      x = image->ysize * image->zsize * sizeof(unsigned int);
      image->rowStart = (unsigned int*)malloc(x);
      image->rowSize = (int *)malloc(x);
      if (image->rowStart == NULL || image->rowSize == NULL) {
         fprintf(stderr, "Out of memory!\n");
         exit(1);
      }
      image->rleEnd = 512 + (2 * x);
      fseek(image->file, 512, SEEK_SET);
      fread(image->rowStart, 1, x, image->file);
      fread(image->rowSize, 1, x, image->file);
      if (image->swapFlag) {
          ConvertLong(image->rowStart, x/(int)sizeof(int));
          ConvertLong((unsigned int *)image->rowSize, x/(int)sizeof(int));
      }
   } else {
      image->rowStart = NULL;
      image->rowSize = NULL;
   }
   return image;
}

static void SGIGetRow(ImageSGI *image, 
    unsigned char *buf, int y, int z) 
{
    unsigned char *iPtr, *oPtr, pixel;
    int count;

    if ((image->type & 0xFF00) == 0x0100) {
        fseek(image->file, (long)image->rowStart[y+z*image->ysize], SEEK_SET);
        fread(image->tmp, 1, (unsigned int)image->rowSize[y+z*image->ysize],
           image->file);

        iPtr = image->tmp;
        oPtr = buf;

        /* Run-Length decoding */
        for (;;) {
            pixel = *iPtr++;
            count = (int)(pixel & 0x7F);
            if (!count) {
                return;
            }
            if (pixel & 0x80) {
	        memcpy(oPtr, iPtr, count);
                oPtr += count;
                iPtr += count;
            } else {
                pixel = *iPtr++;
                memset(oPtr, pixel, count);
                oPtr += count;
            }
        }
    } else {
        fseek(image->file, 512+(y*image->xsize)+(z*image->xsize*image->ysize),
            SEEK_SET);
        fread(buf, 1, image->xsize, image->file);
    }
}

int TestSGI(char *file)
{
    int f = open(file, O_RDONLY);
    unsigned char buf[4];
    int ret = 0;

    if (f < 0)
	return 0;

    ret = ((read(f, buf, 2) == 2) && buf[0] == 0x01 && buf[1] == 0xda);
    close(f);

    return ret;
}

Image *ReadSGI(char *file)
{
    Image *image;
    ImageSGI *in;
    unsigned char *rbuf, *gbuf, *bbuf;
    int x, y;
    unsigned char *ip;

    if ((in = ImageSGIOpen(file)) == NULL)
	return NULL;

    if (in->zsize == 1)
	image = ImageNewGrey(in->xsize, in->ysize);
    else
	image = ImageNew(in->xsize, in->ysize);

    rbuf = (unsigned char *) xmalloc(in->xsize * sizeof(short));
    gbuf = bbuf = rbuf;
    if (in->zsize != 1) {
	gbuf = (unsigned char *) xmalloc(in->xsize * sizeof(short));
	bbuf = (unsigned char *) xmalloc(in->xsize * sizeof(short));
    }
    ip = image->data;

    for (y = in->ysize - 1; y >= 0; y--) {
	SGIGetRow(in, rbuf, y, 0);
	if (gbuf != rbuf)
	    SGIGetRow(in, gbuf, y, 1);
	if (bbuf != rbuf)
	    SGIGetRow(in, bbuf, y, 2);

	for (x = 0; x < in->xsize; x++) {
	    *ip++ = rbuf[x];
	    if (in->zsize != 1) {
		*ip++ = gbuf[x];
		*ip++ = bbuf[x];
	    }
	}
    }

    free(rbuf);
    if (gbuf != rbuf)
	free(gbuf);
    if (bbuf != rbuf)
	free(bbuf);

    ImageSGIClose(in);

    return image;
}

static ImageSGI *ImageSGICreate(char *fileName, 
       unsigned short w, unsigned short h, unsigned short d)
{
   union {
      int testWord;
      char testByte[4];
   } endianTest;
   ImageSGI *image;
   int x, y;

   image = (ImageSGI *)xmalloc(sizeof(ImageSGI));

   if (image == NULL) {
      fprintf(stderr, "Out of memory!\n");
      return NULL;
   }

   memset(image, 0, sizeof(ImageSGI));
   image->imagic = 0x01da;
   image->type = RLE(1); /* RLE compressed */
   image->dim = d;  /* RGB type, 3 components */
   image->xsize = w;
   image->ysize = h;
   image->zsize = d;
   image->min = 0;
   image->max = 255;
   endianTest.testWord = 1;
   image->swapFlag = (int)endianTest.testByte[0];

   strncpy(image->name, basename(fileName), 80);
   image->name[79] = '\0';

   x = w * 2 + 10;
   image->tmp = (unsigned char *)malloc(x);
   memset(image->tmp, 0, x);
   if (image->tmp == NULL) {
      fprintf(stderr, "Out of memory!\n");
      ImageSGIClose(image);      
      return NULL;
   }

   y = h * d * sizeof(unsigned int);
   image->rowStart = (unsigned int *)malloc(y);
   image->rowSize = (int *)malloc(y);
   if (image->rowStart == NULL || image->rowSize == NULL) {
       fprintf(stderr, "Out of memory!\n");
       ImageSGIClose(image);
       return NULL;
   }
   image->rleEnd = 512 + (2 * y);

   return image;
}

static void
SGIPutRow(ImageSGI *image, unsigned char * iPtr, int y, int z)
{
    unsigned char *oPtr, *sPtr, *iEnd, *iEndm2;
    unsigned char c;
    short todo;
    int count;

    iEnd = iPtr + image->xsize;
    iEndm2 = iEnd - 2;
    oPtr = image->tmp;

    /* Run-Length encoding */
    while (iPtr<iEnd) {
        sPtr = iPtr;
        while ((iPtr<iEndm2) && ((iPtr[0]!=iPtr[1])||(iPtr[1]!=iPtr[2])))
            ++iPtr;
        count = iPtr-sPtr;
        while (count) {
	    todo = (count>126) ? 126 : count;
            count -= todo;
            *oPtr++ = 0x80|todo;
            memcpy(oPtr, sPtr, todo);
	    oPtr += todo;
	    sPtr += todo;
        }
        sPtr = iPtr;
        c    = *iPtr++;
        while ((iPtr<iEnd) && (*iPtr == c))  ++iPtr;
        count = iPtr-sPtr;
        while (count) {
	    todo = (count>126) ? 126:count;
            count -= todo;
            *oPtr++ = todo;
            *oPtr++ = c;
        }
    }

    *oPtr++ = 0;
    count = oPtr - image->tmp;

    fwrite(image->tmp, 1, count, image->file);
    y = (z+1)*image->ysize - y - 1;
    image->rowSize[y] = count;
    image->rowStart[y] = image->rleEnd;
    image->rleEnd += count;
}

int WriteSGI(char *fileName, Image * image)
{
    ImageSGI *o;
    unsigned char *rbuf = NULL, *gbuf = NULL, *bbuf = NULL;
    int x, y;
    unsigned char *ip;
    int d;
   
    if (image->alpha) AlphaWarning("SGI", 0);

    d = (image->isGrey)? 1 : 3;
    if ((o = ImageSGICreate(fileName, image->width, image->height, d)) == NULL)
	return 1;

    o->file = fopen(fileName, "wb");

    rbuf = (unsigned char *) xmalloc(d * image->width * sizeof(char));
    if (d == 3) {
	gbuf = rbuf + image->width * sizeof(char);
	bbuf = gbuf + image->width * sizeof(char);
    }

    if (!o->file || !rbuf) {
        perror(fileName);
        if (rbuf) free(rbuf);
        ImageSGIClose(o);
        return 1;
    }

    /* Fill (temporarily) beginning of file with zeroes, 
       o->tmp has already been zeroed */
    fseek(o->file, 0L, SEEK_SET);
    x = 2 * image->width + 10;
    y = 0;
    do {
        y += x;
        if (y > o->rleEnd) {
	    x = x - (y - o->rleEnd);
	    y = o->rleEnd;
	}
        fwrite(o->tmp, 1, x, o->file);
    } while (y < o->rleEnd);

    /* Start reading pixels */
    for (y = 0; y < image->height; y++) {
	for (x = 0; x < image->width; x++) {
	    ip = ImagePixel(image, x, y);
	    rbuf[x] = ip[0];
	    if (d != 1) {
		gbuf[x] = ip[1];
		bbuf[x] = ip[2];
	    }
	}
	if (rbuf != NULL)
	    SGIPutRow(o, rbuf, y, 0);
	if (gbuf != NULL)
	    SGIPutRow(o, gbuf, y, 1);
	if (bbuf != NULL)
	    SGIPutRow(o, bbuf, y, 2);
    } 

    x = image->height * d; 
    if (o->swapFlag) {
        ConvertShort(&o->imagic, 6);
        ConvertLong(&o->min, 3);
        ConvertLong(o->rowStart, x);
        ConvertLong((unsigned int *)o->rowSize, x);
    }

    /* Finally, write image header, i.e. 108 significant bytes */
    fseek(o->file, 0L, SEEK_SET);
    fwrite(o, 1, 108, o->file);

    /* Write RLE offsets and sizes */
    fseek(o->file, 512L, SEEK_SET);
    y = x * (int) sizeof(int);
    fwrite(o->rowStart, 1, y, o->file);
    fwrite(o->rowSize, 1, y, o->file);

    if (rbuf) free(rbuf);
    ImageSGIClose(o);

    return 0;
}

