/*
 *   SGI image file format library routines for CUPS.
 *
 *   Copyright 2007-2011 by Apple Inc.
 *   Copyright 1993-2005 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "COPYING"
 *   which should have been included with this file.
 *
 * Contents:
 *
 *   sgiClose()    - Close an SGI image file.
 *   sgiGetRow()   - Get a row of image data from a file.
 *   sgiOpen()     - Open an SGI image file for reading or writing.
 *   sgiOpenFile() - Open an SGI image file for reading or writing.
 *   sgiPutRow()   - Put a row of image data to a file.
 *   getlong()     - Get a 32-bit big-endian integer.
 *   getshort()    - Get a 16-bit big-endian integer.
 *   putlong()     - Put a 32-bit big-endian integer.
 *   putshort()    - Put a 16-bit big-endian integer.
 *   read_rle8()   - Read 8-bit RLE data.
 *   read_rle16()  - Read 16-bit RLE data.
 *   write_rle8()  - Write 8-bit RLE data.
 *   write_rle16() - Write 16-bit RLE data.
 */

#include "image-sgi.h"
#include "image-private.h"


/*
 * Local functions...
 */

static int	getlong(FILE *);
static int	getshort(FILE *);
static int	putlong(long, FILE *);
static int	putshort(unsigned short, FILE *);
static int	read_rle8(FILE *, unsigned short *, int);
static int	read_rle16(FILE *, unsigned short *, int);
static int	write_rle8(FILE *, unsigned short *, int);
static int	write_rle16(FILE *, unsigned short *, int);


/*
 * 'sgiClose()' - Close an SGI image file.
 */

int					/* O - 0 on success, -1 on error */
sgiClose(sgi_t *sgip)			/* I - SGI image */
{
  int	i;				/* Return status */
  long	*offset;			/* Looping var for offset table */


  if (sgip == NULL)
    return (-1);

  if (sgip->mode == SGI_WRITE && sgip->comp != SGI_COMP_NONE)
  {
   /*
    * Write the scanline offset table to the file...
    */

    fseek(sgip->file, 512, SEEK_SET);

    for (i = sgip->ysize * sgip->zsize, offset = sgip->table[0];
         i > 0;
         i --, offset ++)
      if (putlong(offset[0], sgip->file) < 0)
        return (-1);

    for (i = sgip->ysize * sgip->zsize, offset = sgip->length[0];
         i > 0;
         i --, offset ++)
      if (putlong(offset[0], sgip->file) < 0)
        return (-1);
  }

  if (sgip->table != NULL)
  {
    free(sgip->table[0]);
    free(sgip->table);
  }

  if (sgip->length != NULL)
  {
    free(sgip->length[0]);
    free(sgip->length);
  }

  if (sgip->comp == SGI_COMP_ARLE)
    free(sgip->arle_row);

  i = fclose(sgip->file);
  free(sgip);

  return (i);
}


/*
 * 'sgiGetRow()' - Get a row of image data from a file.
 */

int					/* O - 0 on success, -1 on error */
sgiGetRow(sgi_t          *sgip,		/* I - SGI image */
          unsigned short *row,		/* O - Row to read */
          int            y,		/* I - Line to read */
          int            z)		/* I - Channel to read */
{
  int	x;				/* X coordinate */
  long	offset;				/* File offset */


  if (sgip == NULL ||
      row == NULL ||
      y < 0 || y >= sgip->ysize ||
      z < 0 || z >= sgip->zsize)
    return (-1);

  switch (sgip->comp)
  {
    case SGI_COMP_NONE :
       /*
        * Seek to the image row - optimize buffering by only seeking if
        * necessary...
        */

        offset = 512 + (y + z * sgip->ysize) * sgip->xsize * sgip->bpp;
        if (offset != ftell(sgip->file))
          fseek(sgip->file, offset, SEEK_SET);

        if (sgip->bpp == 1)
        {
          for (x = sgip->xsize; x > 0; x --, row ++)
            *row = getc(sgip->file);
        }
        else
        {
          for (x = sgip->xsize; x > 0; x --, row ++)
            *row = getshort(sgip->file);
        }
        break;

    case SGI_COMP_RLE :
        offset = sgip->table[z][y];
        if (offset != ftell(sgip->file))
          fseek(sgip->file, offset, SEEK_SET);

        if (sgip->bpp == 1)
          return (read_rle8(sgip->file, row, sgip->xsize));
        else
          return (read_rle16(sgip->file, row, sgip->xsize));
  }

  return (0);
}


/*
 * 'sgiOpen()' - Open an SGI image file for reading or writing.
 */

sgi_t *					/* O - New image */
sgiOpen(const char *filename,		/* I - File to open */
        int        mode,		/* I - Open mode (SGI_READ or SGI_WRITE) */
        int        comp,		/* I - Type of compression */
        int        bpp,			/* I - Bytes per pixel */
        int        xsize,		/* I - Width of image in pixels */
        int        ysize,		/* I - Height of image in pixels */
        int        zsize)		/* I - Number of channels */
{
  sgi_t	*sgip;				/* New SGI image file */
  FILE	*file;				/* Image file pointer */


  if (mode == SGI_READ)
    file = fopen(filename, "rb");
  else
    file = fopen(filename, "wb+");

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

  if ((sgip = sgiOpenFile(file, mode, comp, bpp, xsize, ysize, zsize)) == NULL)
    fclose(file);

  return (sgip);
}


/*
 * 'sgiOpenFile()' - Open an SGI image file for reading or writing.
 */

sgi_t *					/* O - New image */
sgiOpenFile(FILE *file,			/* I - File to open */
            int  mode,			/* I - Open mode (SGI_READ or SGI_WRITE) */
            int  comp,			/* I - Type of compression */
            int  bpp,			/* I - Bytes per pixel */
            int  xsize,			/* I - Width of image in pixels */
            int  ysize,			/* I - Height of image in pixels */
            int  zsize)			/* I - Number of channels */
{
  int	i, j;				/* Looping var */
  char	name[80];			/* Name of file in image header */
  short	magic;				/* Magic number */
  sgi_t	*sgip;				/* New image pointer */


  if ((sgip = calloc(sizeof(sgi_t), 1)) == NULL)
    return (NULL);

  sgip->file = file;

  switch (mode)
  {
    case SGI_READ :
        sgip->mode = SGI_READ;

        magic = getshort(sgip->file);
        if (magic != SGI_MAGIC)
        {
          free(sgip);
          return (NULL);
        }

        sgip->comp  = getc(sgip->file);
        sgip->bpp   = getc(sgip->file);
        getshort(sgip->file);		/* Dimensions */
        sgip->xsize = getshort(sgip->file);
        sgip->ysize = getshort(sgip->file);
        sgip->zsize = getshort(sgip->file);
        getlong(sgip->file);		/* Minimum pixel */
        getlong(sgip->file);		/* Maximum pixel */

        if (sgip->comp)
        {
         /*
          * This file is compressed; read the scanline tables...
          */

          fseek(sgip->file, 512, SEEK_SET);

          if ((sgip->table = calloc(sgip->zsize, sizeof(long *))) == NULL)
	  {
	    free(sgip);
	    return (NULL);
	  }

          if ((sgip->table[0] = calloc(sgip->ysize * sgip->zsize,
	                               sizeof(long))) == NULL)
          {
	    free(sgip->table);
	    free(sgip);
	    return (NULL);
	  }

          for (i = 1; i < sgip->zsize; i ++)
            sgip->table[i] = sgip->table[0] + i * sgip->ysize;

          for (i = 0; i < sgip->zsize; i ++)
            for (j = 0; j < sgip->ysize; j ++)
              sgip->table[i][j] = getlong(sgip->file);
        }
        break;

    case SGI_WRITE :
	if (xsize < 1 ||
	    ysize < 1 ||
	    zsize < 1 ||
	    bpp < 1 || bpp > 2 ||
	    comp < SGI_COMP_NONE || comp > SGI_COMP_ARLE)
        {
          free(sgip);
          return (NULL);
        }

        sgip->mode = SGI_WRITE;

        putshort(SGI_MAGIC, sgip->file);
        putc(((sgip->comp = comp) != 0) ? '1': '0', sgip->file);
        putc(sgip->bpp = bpp, sgip->file);
        putshort(3, sgip->file);		/* Dimensions */
        putshort(sgip->xsize = xsize, sgip->file);
        putshort(sgip->ysize = ysize, sgip->file);
        putshort(sgip->zsize = zsize, sgip->file);
        if (bpp == 1)
        {
          putlong(0, sgip->file);	/* Minimum pixel */
          putlong(255, sgip->file);	/* Maximum pixel */
        }
        else
        {
          putlong(-32768, sgip->file);	/* Minimum pixel */
          putlong(32767, sgip->file);	/* Maximum pixel */
        }
        putlong(0, sgip->file);		/* Reserved */

        memset(name, 0, sizeof(name));
        fwrite(name, sizeof(name), 1, sgip->file);

        for (i = 0; i < 102; i ++)
          putlong(0, sgip->file);

        switch (comp)
        {
          case SGI_COMP_NONE : /* No compression */
             /*
              * This file is uncompressed.  To avoid problems with sparse files,
              * we need to write blank pixels for the entire image...
              */

              if (bpp == 1)
              {
        	for (i = xsize * ysize * zsize; i > 0; i --)
        	  putc(0, sgip->file);
              }
              else
              {
        	for (i = xsize * ysize * zsize; i > 0; i --)
        	  putshort(0, sgip->file);
              }
              break;

          case SGI_COMP_ARLE : /* Aggressive RLE */
              sgip->arle_row    = calloc(xsize, sizeof(unsigned short));
              sgip->arle_offset = 0;

          case SGI_COMP_RLE : /* Run-Length Encoding */
             /*
              * This file is compressed; write the (blank) scanline tables...
              */

              for (i = 2 * ysize * zsize; i > 0; i --)
        	putlong(0, sgip->file);

              sgip->firstrow = ftell(sgip->file);
              sgip->nextrow  = ftell(sgip->file);
              if ((sgip->table = calloc(sgip->zsize, sizeof(long *))) == NULL)
	      {
	        free(sgip);
		return (NULL);
	      }

              if ((sgip->table[0] = calloc(sgip->ysize * sgip->zsize,
	                                   sizeof(long))) == NULL)
              {
	        free(sgip->table);
		free(sgip);
		return (NULL);
	      }

              for (i = 1; i < sgip->zsize; i ++)
        	sgip->table[i] = sgip->table[0] + i * sgip->ysize;

              if ((sgip->length = calloc(sgip->zsize, sizeof(long *))) == NULL)
	      {
	        free(sgip->table);
		free(sgip);
		return (NULL);
	      }

              if ((sgip->length[0] = calloc(sgip->ysize * sgip->zsize,
	                                    sizeof(long))) == NULL)
              {
	        free(sgip->length);
		free(sgip->table);
		free(sgip);
		return (NULL);
	      }

              for (i = 1; i < sgip->zsize; i ++)
        	sgip->length[i] = sgip->length[0] + i * sgip->ysize;
              break;
        }
        break;

    default :
        free(sgip);
        return (NULL);
  }

  return (sgip);
}


/*
 * 'sgiPutRow()' - Put a row of image data to a file.
 */

int					/* O - 0 on success, -1 on error */
sgiPutRow(sgi_t          *sgip,		/* I - SGI image */
          unsigned short *row,		/* I - Row to write */
          int            y,		/* I - Line to write */
          int            z)		/* I - Channel to write */
{
  int	x;				/* X coordinate */
  long	offset;				/* File offset */


  if (sgip == NULL ||
      row == NULL ||
      y < 0 || y >= sgip->ysize ||
      z < 0 || z >= sgip->zsize)
    return (-1);

  switch (sgip->comp)
  {
    case SGI_COMP_NONE :
       /*
        * Seek to the image row - optimize buffering by only seeking if
        * necessary...
        */

        offset = 512 + (y + z * sgip->ysize) * sgip->xsize * sgip->bpp;
        if (offset != ftell(sgip->file))
          fseek(sgip->file, offset, SEEK_SET);

        if (sgip->bpp == 1)
        {
          for (x = sgip->xsize; x > 0; x --, row ++)
            putc(*row, sgip->file);
        }
        else
        {
          for (x = sgip->xsize; x > 0; x --, row ++)
            putshort(*row, sgip->file);
        }
        break;

    case SGI_COMP_ARLE :
        if (sgip->table[z][y] != 0)
          return (-1);

       /*
        * First check the last row written...
        */

        if (sgip->arle_offset > 0)
        {
          for (x = 0; x < sgip->xsize; x ++)
            if (row[x] != sgip->arle_row[x])
              break;

          if (x == sgip->xsize)
          {
            sgip->table[z][y]  = sgip->arle_offset;
            sgip->length[z][y] = sgip->arle_length;
            return (0);
          }
        }

       /*
        * If that didn't match, search all the previous rows...
        */

        fseek(sgip->file, sgip->firstrow, SEEK_SET);

        if (sgip->bpp == 1)
        {
          for (;;)
          {
            sgip->arle_offset = ftell(sgip->file);
            if ((sgip->arle_length = read_rle8(sgip->file, sgip->arle_row, sgip->xsize)) < 0)
            {
              x = 0;
              break;
            }

            if (memcmp(row, sgip->arle_row, sgip->xsize * sizeof(unsigned short)) == 0)
	    {
	      x = sgip->xsize;
	      break;
	    }
          }
        }
        else
        {
          for (;;)
          {
            sgip->arle_offset = ftell(sgip->file);
            if ((sgip->arle_length = read_rle16(sgip->file, sgip->arle_row, sgip->xsize)) < 0)
            {
              x = 0;
              break;
            }

            if (memcmp(row, sgip->arle_row, sgip->xsize * sizeof(unsigned short)) == 0)
	    {
	      x = sgip->xsize;
	      break;
	    }
          }
        }

	if (x == sgip->xsize)
	{
          sgip->table[z][y]  = sgip->arle_offset;
          sgip->length[z][y] = sgip->arle_length;
          return (0);
	}
	else
	  fseek(sgip->file, 0, SEEK_END);	/* Clear EOF */

    case SGI_COMP_RLE :
        if (sgip->table[z][y] != 0)
          return (-1);

        offset = sgip->table[z][y] = sgip->nextrow;

        if (offset != ftell(sgip->file))
          fseek(sgip->file, offset, SEEK_SET);

        if (sgip->bpp == 1)
          x = write_rle8(sgip->file, row, sgip->xsize);
        else
          x = write_rle16(sgip->file, row, sgip->xsize);

        if (sgip->comp == SGI_COMP_ARLE)
        {
          sgip->arle_offset = offset;
          sgip->arle_length = x;
          memcpy(sgip->arle_row, row, sgip->xsize * sizeof(unsigned short));
        }

        sgip->nextrow      = ftell(sgip->file);
        sgip->length[z][y] = x;

        return (x);
  }

  return (0);
}


/*
 * 'getlong()' - Get a 32-bit big-endian integer.
 */

static int				/* O - Long value */
getlong(FILE *fp)			/* I - File to read from */
{
  unsigned char	b[4];			/* Bytes from file */


  if (fread(b, 4, 1, fp) == 0 && ferror(fp))
    DEBUG_printf(("Error reading file!"));
  return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
}


/*
 * 'getshort()' - Get a 16-bit big-endian integer.
 */

static int				/* O - Short value */
getshort(FILE *fp)			/* I - File to read from */
{
  unsigned char	b[2];			/* Bytes from file */


  if (fread(b, 2, 1, fp) == 0 && ferror(fp))
    DEBUG_printf(("Error reading file!"));
  return ((b[0] << 8) | b[1]);
}


/*
 * 'putlong()' - Put a 32-bit big-endian integer.
 */

static int				/* O - 0 on success, -1 on error */
putlong(long n,				/* I - Long to write */
        FILE *fp)			/* I - File to write to */
{
  if (putc(n >> 24, fp) == EOF)
    return (EOF);
  if (putc(n >> 16, fp) == EOF)
    return (EOF);
  if (putc(n >> 8, fp) == EOF)
    return (EOF);
  if (putc(n, fp) == EOF)
    return (EOF);
  else
    return (0);
}


/*
 * 'putshort()' - Put a 16-bit big-endian integer.
 */

static int				/* O - 0 on success, -1 on error */
putshort(unsigned short n,		/* I - Short to write */
         FILE           *fp)		/* I - File to write to */
{
  if (putc(n >> 8, fp) == EOF)
    return (EOF);
  if (putc(n, fp) == EOF)
    return (EOF);
  else
    return (0);
}


/*
 * 'read_rle8()' - Read 8-bit RLE data.
 */

static int				/* O - Value on success, -1 on error */
read_rle8(FILE           *fp,		/* I - File to read from */
          unsigned short *row,		/* O - Data */
          int            xsize)		/* I - Width of data in pixels */
{
  int	i,				/* Looping var */
	ch,				/* Current character */
	count,				/* RLE count */
	length;				/* Number of bytes read... */


  length = 0;

  while (xsize > 0)
  {
    if ((ch = getc(fp)) == EOF)
      return (-1);
    length ++;

    count = ch & 127;
    if (count == 0)
      break;

    if (ch & 128)
    {
      for (i = 0; i < count; i ++, row ++, xsize --, length ++)
        if (xsize > 0)
	  *row = getc(fp);
    }
    else
    {
      ch = getc(fp);
      length ++;
      for (i = 0; i < count && xsize > 0; i ++, row ++, xsize --)
        *row = ch;
    }
  }

  return (xsize > 0 ? -1 : length);
}


/*
 * 'read_rle16()' - Read 16-bit RLE data.
 */

static int				/* O - Value on success, -1 on error */
read_rle16(FILE           *fp,		/* I - File to read from */
           unsigned short *row,		/* O - Data */
           int            xsize)	/* I - Width of data in pixels */
{
  int	i,				/* Looping var */
	ch,				/* Current character */
	count,				/* RLE count */
	length;				/* Number of bytes read... */


  length = 0;

  while (xsize > 0)
  {
    if ((ch = getshort(fp)) == EOF)
      return (-1);
    length ++;

    count = ch & 127;
    if (count == 0)
      break;

    if (ch & 128)
    {
      for (i = 0; i < count; i ++, row ++, xsize --, length ++)
        if (xsize > 0)
	  *row = getshort(fp);
    }
    else
    {
      ch = getshort(fp);
      length ++;
      for (i = 0; i < count && xsize > 0; i ++, row ++, xsize --)
	*row = ch;
    }
  }

  return (xsize > 0 ? -1 : length * 2);
}


/*
 * 'write_rle8()' - Write 8-bit RLE data.
 */

static int				/* O - Length on success, -1 on error */
write_rle8(FILE           *fp,		/* I - File to write to */
           unsigned short *row,		/* I - Data */
           int            xsize)	/* I - Width of data in pixels */
{
  int			length,		/* Length in bytes */
			count,		/* Number of repeating pixels */
			i,		/* Looping var */
			x;		/* Current column */
  unsigned short	*start,		/* Start of current sequence */
			repeat;		/* Repeated pixel */


  for (x = xsize, length = 0; x > 0;)
  {
    start = row;
    row   += 2;
    x     -= 2;

    while (x > 0 && (row[-2] != row[-1] || row[-1] != row[0]))
    {
      row ++;
      x --;
    }

    row -= 2;
    x   += 2;

    count = row - start;
    while (count > 0)
    {
      i     = count > 126 ? 126 : count;
      count -= i;

      if (putc(128 | i, fp) == EOF)
        return (-1);
      length ++;

      while (i > 0)
      {
	if (putc(*start, fp) == EOF)
          return (-1);
        start ++;
        i --;
        length ++;
      }
    }

    if (x <= 0)
      break;

    start  = row;
    repeat = row[0];

    row ++;
    x --;

    while (x > 0 && *row == repeat)
    {
      row ++;
      x --;
    }

    count = row - start;
    while (count > 0)
    {
      i     = count > 126 ? 126 : count;
      count -= i;

      if (putc(i, fp) == EOF)
        return (-1);
      length ++;

      if (putc(repeat, fp) == EOF)
        return (-1);
      length ++;
    }
  }

  length ++;

  if (putc(0, fp) == EOF)
    return (-1);
  else
    return (length);
}


/*
 * 'write_rle16()' - Write 16-bit RLE data.
 */

static int				/* O - Length in words */
write_rle16(FILE           *fp,		/* I - File to write to */
            unsigned short *row,	/* I - Data */
            int            xsize)	/* I - Width of data in pixels */
{
  int			length,		/* Length in words */
			count,		/* Number of repeating pixels */
			i,		/* Looping var */
			x;		/* Current column */
  unsigned short	*start,		/* Start of current sequence */
			repeat;		/* Repeated pixel */


  for (x = xsize, length = 0; x > 0;)
  {
    start = row;
    row   += 2;
    x     -= 2;

    while (x > 0 && (row[-2] != row[-1] || row[-1] != row[0]))
    {
      row ++;
      x --;
    }

    row -= 2;
    x   += 2;

    count = row - start;
    while (count > 0)
    {
      i     = count > 126 ? 126 : count;
      count -= i;

      if (putshort(128 | i, fp) == EOF)
        return (-1);
      length ++;

      while (i > 0)
      {
	if (putshort(*start, fp) == EOF)
          return (-1);
        start ++;
        i --;
        length ++;
      }
    }

    if (x <= 0)
      break;

    start  = row;
    repeat = row[0];

    row ++;
    x --;

    while (x > 0 && *row == repeat)
    {
      row ++;
      x --;
    }

    count = row - start;
    while (count > 0)
    {
      i     = count > 126 ? 126 : count;
      count -= i;

      if (putshort(i, fp) == EOF)
        return (-1);
      length ++;

      if (putshort(repeat, fp) == EOF)
        return (-1);
      length ++;
    }
  }

  length ++;

  if (putshort(0, fp) == EOF)
    return (-1);
  else
    return (2 * length);
}

