/*
 * Portions copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 */

package demos.util;

import java.io.*;
import javax.media.opengl.*;
import com.sun.opengl.utils.*;

// Test harness
import java.awt.image.*;
import javax.swing.*;

/** <p> Reads SGI RGB/RGBA images. </p>

    <p> Written from <a href =
    "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/">Paul
    Bourke's adaptation</a> of the <a href =
    "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/sgiversion.html">SGI
    specification</a>. </p>
*/

public class SGIImage {
  private Header header;
  private int    format;
  private byte[] data;
  // Used for decoding RLE-compressed images
  private int[]  rowStart;
  private int[]  rowSize;
  private int    rleEnd;
  private byte[] tmpData;
  private byte[] tmpRead;

  static class Header {
    short magic;        // IRIS image file magic number
                        // This should be decimal 474
    byte  storage;      // Storage format
                        // 0 for uncompressed
                        // 1 for RLE compression
    byte  bpc;          // Number of bytes per pixel channel 
                        // Legally 1 or 2
    short dimension;    // Number of dimensions
                        // Legally 1, 2, or 3
                        // 1 means a single row, XSIZE long
                        // 2 means a single 2D image
                        // 3 means multiple 2D images
    short xsize;        // X size in pixels 
    short ysize;        // Y size in pixels 
    short zsize;        // Number of channels
                        // 1 indicates greyscale
                        // 3 indicates RGB
                        // 4 indicates RGB and Alpha
    int pixmin;         // Minimum pixel value
                        // This is the lowest pixel value in the image
    int pixmax;         // Maximum pixel value
                        // This is the highest pixel value in the image
    int dummy;          // Ignored
                        // Normally set to 0
    String imagename;   // Image name; 80 bytes long
                        // Must be null terminated, therefore at most 79 bytes
    int colormap;       // Colormap ID
                        // 0 - normal mode
                        // 1 - dithered, 3 mits for red and green, 2 for blue, obsolete
                        // 2 - index colour, obsolete
                        // 3 - not an image but a colourmap
    // 404 bytes  char    DUMMY      Ignored
    // Should be set to 0, makes the header 512 bytes.

    Header(DataInputStream in) throws IOException {
      magic      = in.readShort();
      storage    = in.readByte();
      bpc        = in.readByte();
      dimension  = in.readShort();
      xsize      = in.readShort();
      ysize      = in.readShort();
      zsize      = in.readShort();
      pixmin     = in.readInt();
      pixmax     = in.readInt();
      dummy      = in.readInt();
      byte[] tmpname = new byte[80];
      in.read(tmpname);
      int numChars = 0;
      while (tmpname[numChars++] != 0);
      imagename  = new String(tmpname, 0, numChars);
      colormap   = in.readInt();
      byte[] tmp = new byte[404];
      in.read(tmp);
    }

    public String toString() {
      return ("magic: " + magic +
              " storage: " + (int) storage +
              " bpc: " + (int) bpc +
              " dimension: " + dimension +
              " xsize: " + xsize +
              " ysize: " + ysize +
              " zsize: " + zsize +
              " pixmin: " + pixmin +
              " pixmax: " + pixmax +
              " imagename: " + imagename +
              " colormap: " + colormap);
    }
  }

  private SGIImage(Header header) {
    this.header = header;
  }

  /** Reads an SGI image from the specified file. */
  public static SGIImage read(String filename) throws IOException {
    return read(new FileInputStream(filename));
  }

  /** Reads an SGI image from the specified InputStream. */
  public static SGIImage read(InputStream in) throws IOException {
    DataInputStream dIn = new DataInputStream(new BufferedInputStream(in));

    Header header = new Header(dIn);
    SGIImage res = new SGIImage(header);
    res.decodeImage(dIn);
    return res;
  }

  /** Returns the width of the image. */
  public int getWidth() {
    return header.xsize;
  }

  /** Returns the height of the image. */
  public int getHeight() {
    return header.ysize;
  }

  /** Returns the OpenGL format for this texture; e.g. GL.GL_BGR or GL.GL_BGRA. */
  public int getFormat() {
    return format;
  }

  /** Returns the raw data for this texture in the correct
      (bottom-to-top) order for calls to glTexImage2D. */
  public byte[] getData()  { return data; }

  public String toString() {
    return header.toString();
  }

  //----------------------------------------------------------------------
  // Internals only below this point
  //
  
  private void decodeImage(DataInputStream in) throws IOException {
    if (header.storage == 1) {
      // Read RLE compression data; row starts and sizes
      int x = header.ysize * header.zsize;
      rowStart = new int[x];
      rowSize  = new int[x];
      rleEnd   = 4 * 2 * x + 512;
      for (int i = 0; i < x; i++) {
        rowStart[i] = in.readInt();
      }
      for (int i = 0; i < x; i++) {
        rowSize[i] = in.readInt();
      }
      tmpRead = new byte[header.xsize * 256];
    }
    tmpData = readAll(in);

    int xsize = header.xsize;
    int ysize = header.ysize;
    int zsize = header.zsize;
    int lptr  = 0;

    data = new byte[xsize * ysize * 4];
    byte[] rbuf = new byte[xsize];
    byte[] gbuf = new byte[xsize];
    byte[] bbuf = new byte[xsize];
    byte[] abuf = new byte[xsize];
    for (int y = 0; y < ysize; y++) {
      if (zsize >= 4) {
        getRow(rbuf, y, 0);
        getRow(gbuf, y, 1);
        getRow(bbuf, y, 2);
        getRow(abuf, y, 3);
        rgbatorgba(rbuf, gbuf, bbuf, abuf, data, lptr);
      } else if (zsize == 3) {
        getRow(rbuf, y, 0);
        getRow(gbuf, y, 1);
        getRow(bbuf, y, 2);
        rgbtorgba(rbuf, gbuf, bbuf, data, lptr);
      } else if (zsize == 2) {
        getRow(rbuf, y, 0);
        getRow(abuf, y, 1);
        latorgba(rbuf, abuf, data, lptr);
      } else {
        getRow(rbuf, y, 0);
        bwtorgba(rbuf, data, lptr);
      }
      lptr += 4 * xsize;
    }
    rowStart = null;
    rowSize  = null;
    tmpData  = null;
    tmpRead  = null;
    format   = GL.GL_BGRA;
  }

  private void getRow(byte[] buf, int y, int z) {
    if (header.storage == 1) {
      int offs = rowStart[y + z * header.ysize] - rleEnd;
      System.arraycopy(tmpData, offs, tmpRead, 0, rowSize[y + z * header.ysize]);
      int iPtr = 0;
      int oPtr = 0;
      for (;;) {
        byte pixel = tmpRead[iPtr++];
        int count = (int) (pixel & 0x7F);
        if (count == 0) {
          return;
        }
        if ((pixel & 0x80) != 0) {
          while ((count--) > 0) {
            buf[oPtr++] = tmpRead[iPtr++];
          }
        } else {
          pixel = tmpRead[iPtr++];
          while ((count--) > 0) {
            buf[oPtr++] = pixel;
          }
        }
      }
    } else {
      int offs = (y * header.xsize) + (z * header.xsize * header.ysize);
      System.arraycopy(tmpData, offs, buf, 0, header.xsize);
    }
  }

  private void bwtorgba(byte[] b, byte[] dest, int lptr) {
    for (int i = 0; i < b.length; i++) {
      dest[4 * i + lptr + 0] = b[i];
      dest[4 * i + lptr + 1] = b[i];
      dest[4 * i + lptr + 2] = b[i];
      dest[4 * i + lptr + 3] = (byte) 0xFF;
    }
  }

  private void latorgba(byte[] b, byte[] a, byte[] dest, int lptr) {
    for (int i = 0; i < b.length; i++) {
      dest[4 * i + lptr + 0] = b[i];
      dest[4 * i + lptr + 1] = b[i];
      dest[4 * i + lptr + 2] = b[i];
      dest[4 * i + lptr + 3] = a[i];
    }
  }

  private void rgbtorgba(byte[] r, byte[] g, byte[] b, byte[] dest, int lptr) {
    for (int i = 0; i < b.length; i++) {
      dest[4 * i + lptr + 0] = r[i];
      dest[4 * i + lptr + 1] = g[i];
      dest[4 * i + lptr + 2] = b[i];
      dest[4 * i + lptr + 3] = (byte) 0xFF;
    }
  }

  private void rgbatorgba(byte[] r, byte[] g, byte[] b, byte[] a, byte[] dest, int lptr) {
    for (int i = 0; i < b.length; i++) {
      dest[4 * i + lptr + 0] = r[i];
      dest[4 * i + lptr + 1] = g[i];
      dest[4 * i + lptr + 2] = b[i];
      dest[4 * i + lptr + 3] = a[i];
    }
  }

  private byte[] readAll(DataInputStream in) throws IOException {
    byte[] dest = new byte[16384];
    int pos = 0;
    int numRead = 0;
    
    boolean done = false;

    do {
      numRead = in.read(dest, pos, dest.length - pos);
      if (pos == dest.length) {
        // Resize destination buffer
        byte[] newDest = new byte[2 * dest.length];
        System.arraycopy(dest, 0, newDest, 0, pos);
        dest = newDest;
      }
      if (numRead > 0) {
        pos += numRead;
      }

      done = ((numRead == -1) || (in.available() == 0));
    } while (!done);

    // Trim destination buffer
    if (pos != dest.length) {
      byte[] finalDest = new byte[pos];
      System.arraycopy(dest, 0, finalDest, 0, pos);
      dest = finalDest;
    }

    return dest;
  }

  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      try {
        System.out.println(args[i] + ":");
        SGIImage image = SGIImage.read(args[i]);
        System.out.println(image);
        BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        WritableRaster raster = img.getRaster();
        DataBufferByte db = (DataBufferByte) raster.getDataBuffer();
        byte[] src  = image.getData();
        byte[] dest = db.getData();
        for (int j = 0; j < src.length; j += 4) {
          dest[j + 0] = src[j + 3];
          dest[j + 1] = src[j + 2];
          dest[j + 2] = src[j + 1];
          dest[j + 3] = src[j + 0];
        }
        // System.arraycopy(src, 0, dest, 0, src.length);
        ImageIcon icon = new ImageIcon(img);
        JLabel label = new JLabel();
        label.setIcon(icon);
        JFrame frame = new JFrame(args[i]);
        frame.getContentPane().add(label);
        frame.pack();
        frame.show();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}