Package net.sourceforge.jiu.codecs

Source Code of net.sourceforge.jiu.codecs.IFFCodec

/*
* IFFCodec
*
* Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006 Marco Schmidt.
* All rights reserved.
*/

package net.sourceforge.jiu.codecs;

import java.io.DataInput;
import java.io.IOException;
import net.sourceforge.jiu.codecs.ImageCodec;
import net.sourceforge.jiu.codecs.InvalidFileStructureException;
import net.sourceforge.jiu.codecs.UnsupportedTypeException;
import net.sourceforge.jiu.codecs.WrongFileFormatException;
import net.sourceforge.jiu.data.MemoryPaletted8Image;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.Palette;
import net.sourceforge.jiu.data.Paletted8Image;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.OperationFailedException;
import net.sourceforge.jiu.ops.WrongParameterException;

/**
* A codec to read Amiga IFF image files.
* IFF (Interchange File Format) is an Amiga wrapper file format for texts, images, animations, sound and other kinds of data.
* This codec only deals with image IFF files.
* Typical file extensions for IFF image files are <code>.lbm</code> and <code>.iff</code>.
* <h3>Loading / saving</h3>
* Only loading is supported by this codec.
* <h3>Supported file types</h3>
* Both uncompressed and run-length encoded files are read.
* <ul>
* <li>1 to 8 bit indexed (paletted) color</li>
* <li>24 bit RGB truecolor</li>
* <li>HAM6 and HAM8 images (which are a mixture of paletted and truecolor)</li>
* </ul>
* <h3>Usage example</h3>
* <pre>
* IFFCodec codec = new IFFCodec();
* codec.setFile("image.iff", CodecMode.LOAD);
* codec.process();
* PixelImage image = codec.getImage();
* </pre>
* @author Marco Schmidt
* @since 0.3.0
*/
public class IFFCodec extends ImageCodec
{
  private final static int MAGIC_BMHD = 0x424d4844;
  private final static int MAGIC_BODY = 0x424f4459;
  private final static int MAGIC_CMAP = 0x434d4150;
  private final static int MAGIC_CAMG = 0x43414d47;
  private final static int MAGIC_FORM = 0x464f524d;
  private final static int MAGIC_ILBM = 0x494c424d;
  private final static int MAGIC_PBM = 0x50424d20;
  private final static int SIZE_BMHD = 0x00000014;
  private final static byte COMPRESSION_NONE = 0x00;
  private final static byte COMPRESSION_RLE = 0x01;
  private int camg;
  private byte compression;
  private boolean ehb;
  private boolean ham;
  private boolean ham6;
  private boolean ham8;
  private int height;
  private int numPlanes;
  private Palette palette;
  private boolean rgb24;
  private int type;
  private int width;

  private void checkAndLoad() throws
    InvalidFileStructureException,
    IOException,
    MissingParameterException,
    UnsupportedTypeException,
    WrongFileFormatException,
    WrongParameterException
  {
    DataInput in = getInputAsDataInput();
    if (in == null)
    {
      throw new MissingParameterException("InputStream / DataInput object is missing.");
    }
    int formMagic = in.readInt();
    if (formMagic != MAGIC_FORM)
    {
      throw new WrongFileFormatException("Cannot load image. The " +
        "input stream is not a valid IFF file (wrong magic byte " +
        "sequence).");
    }
    in.readInt(); // read and discard "file size" field
    type = in.readInt();
    if (type != MAGIC_ILBM && type != MAGIC_PBM)
    {
      throw new UnsupportedTypeException("Cannot load image. The " +
        "input stream is an IFF file, but not of type ILBM or PBM" +
        " (" + getChunkName(type) + ")");
    }
    PixelImage result = null;
    boolean hasBMHD = false;
    boolean hasCAMG = false;
    do
    {
      int magic = in.readInt();
      //System.out.println(chunkNameToString(magic));
      int size = in.readInt();
      // chunks must always have an even number of bytes
      if ((size & 1) == 1)
      {
        size++;
      }
      //System.out.println("Chunk " + getChunkName(magic) + ", size=" + size);
      switch(magic)
      {
        case(MAGIC_BMHD): // main header with width, height, bit depth
        {
          if (hasBMHD)
          {
            throw new InvalidFileStructureException("Error in " +
              "IFF file: more than one BMHD chunk.");
          }
          if (size != SIZE_BMHD)
          {
            throw new InvalidFileStructureException("Cannot " +
              "load image. The bitmap header chunk does not " +
              "have the expected size.");
          }
          // image resolution in pixels
          width = in.readShort();
          height = in.readShort();
          if (width < 1 || height < 1)
          {
            throw new InvalidFileStructureException("Cannot " +
              "load image. The IFF file's bitmap header " +
              "contains invalid width and height values: " +
              width + ", " + height);
          }
          // next four bytes don't matter
          in.skipBytes(4);
          // color depth, 1..8 or 24
          numPlanes = in.readByte();
          if ((numPlanes != 24) && (numPlanes < 1 || numPlanes > 8))
          {
            throw new UnsupportedTypeException("Cannot load " +
              "image, unsupported number of bits per pixel: " +
              numPlanes);
          }
          //System.out.println("\nnum planes=" + numPlanes);
          in.readByte(); // discard "masking" value
          // compression type, must be 0 or 1
          compression = in.readByte();
          if (compression != COMPRESSION_NONE &&
              compression != COMPRESSION_RLE)
          {
            throw new UnsupportedTypeException("Cannot load " +
              "image, unsupported compression type: " +
              compression);
          }
          //System.out.println(getCompressionName(compression));
          in.skipBytes(9);
          hasBMHD = true;
          break;
        }
        case(MAGIC_BODY):
        {
          if (!hasBMHD)
          {
            // width still has its initialization value -1; no
            // bitmap chunk was encountered
            throw new InvalidFileStructureException("Cannot load image. Error in " +
              "IFF input stream: No bitmap header chunk " +
              "encountered before image body chunk.");
          }
          if (palette == null && (!rgb24))
          {
            // a missing color map is allowed only for truecolor images
            throw new InvalidFileStructureException("Cannot load image. Error in " +
              "IFF input stream: No colormap chunk " +
              "encountered before image body chunk.");
          }
          result = loadImage(in);
          break;
        }
        case(MAGIC_CAMG):
        {
          if (hasCAMG)
          {
            throw new InvalidFileStructureException("Cannot load image. Error in " +
              "IFF input stream: More than one CAMG chunk.");
          }
          hasCAMG = true;
          if (size < 4)
          {
            throw new InvalidFileStructureException("Cannot load" +
              " image. CAMG must be at least four bytes large; " +
              "found: " + size);
          }
          camg = in.readInt();
          ham = (camg & 0x800) != 0;
          ehb = (camg & 0x80) != 0;
          //System.out.println("ham=" + ham);
          in.skipBytes(size - 4);
          break;
        }
        case(MAGIC_CMAP): // palette (color map)
        {
          if (palette != null)
          {
            throw new InvalidFileStructureException("Cannot " +
              "load image. Error in IFF " +
              "input stream: More than one palette.");
          }
          if (size < 3 || (size % 3) != 0)
          {
            throw new InvalidFileStructureException("Cannot " +
              "load image. The size of the colormap is " +
              "invalid: " + size);
          }
          int numColors = size / 3;
          palette = new Palette(numColors, 255);
          for (int i = 0; i < numColors; i++)
          {
            palette.putSample(Palette.INDEX_RED, i, in.readByte() & 0xff);
            palette.putSample(Palette.INDEX_GREEN, i, in.readByte() & 0xff);
            palette.putSample(Palette.INDEX_BLUE, i, in.readByte() & 0xff);
          }
          break;
        }
        default:
        {
          if (in.skipBytes(size) != size)
          {
            throw new IOException("Error skipping " + size +
              " bytes of input stream.");
          }
          break;
        }
      }
    }
    while(result == null);
    setImage(result);
  }

  /**
   * Converts input planes to index or truecolor output values.
   * Exact interpretation depends on the type of ILBM image storage:
   * <ul>
   * <li>normal mode; the 1 to 8 planes create index values which are used
   *  with the colormap</li>
   * <li>RGB24; each of the 24 planes adds one bit to the three intensity
   *  values for red, green and blue; no color map is necessary</li>
   * <li>HAM6; a six bit integer (0 to 63) is assembled from the planes
   *  and the top two bits determine if the previous color is modified or
   *  if the lower four bits are used as an index into the palette (which
   *  has consequently 2<sup>4</sup> = 16 entries</li>
   * </ul>
   * @param sourcePlanes
   * @param dest
   */
  private void convertRow(byte[][] sourcePlaneData, byte[][] dest)
  {
    int sourceMask = 0x80;
    int sourceIndex = 0;
    int lastRed = 0;
    int lastGreen = 0;
    int lastBlue = 0;
    for (int x = 0; x < width; x++)
    {
      int destMask = 1;
      int index = 0;
      for (int p = 0; p < sourcePlaneData.length; p++)
      {
        if ((sourcePlaneData[p][sourceIndex] & sourceMask) != 0)
        {
          index |= destMask;
        }
        destMask <<= 1;
      }
      if ((x & 7) == 7)
      {
        sourceIndex++;
      }
      if (sourceMask == 0x01)
      {
        sourceMask = 0x80;
      }
      else
      {
        sourceMask >>= 1;
      }
      if (ham6)
      {
        //System.out.println("enter ham6");
        int paletteIndex = index & 0x0f;
        //System.out.println("palette index=" + paletteIndex);
        switch((index >> 4) & 0x03)
        {
          case(0): // HOLD
          {
            lastRed = palette.getSample(Palette.INDEX_RED, paletteIndex);
            lastGreen = palette.getSample(Palette.INDEX_GREEN, paletteIndex);
            lastBlue = palette.getSample(Palette.INDEX_BLUE, paletteIndex);
            break;
          }
          case(1): // MODIFY BLUE
          {
            lastBlue = (lastBlue & 0x0f) | (paletteIndex << 4);
            break;
          }
          case(2): // MODIFY RED
          {
            lastRed = (lastRed & 0x0f) | (paletteIndex << 4);
            break;
          }
          case(3): // MODIFY GREEN
          {
            lastGreen = (lastGreen & 0x0f) | (paletteIndex << 4);
            break;
          }
        }
        dest[0][x] = (byte)lastRed;
        dest[1][x] = (byte)lastGreen;
        dest[2][x] = (byte)lastBlue;
      }
      else
      if (ham8)
      {
        int paletteIndex = index & 0x3f;
        //System.out.println("palette index=" + paletteIndex);
        switch((index >> 6) & 0x03)
        {
          case(0): // HOLD
          {
            lastRed = palette.getSample(Palette.INDEX_RED, paletteIndex);
            lastGreen = palette.getSample(Palette.INDEX_GREEN, paletteIndex);
            lastBlue = palette.getSample(Palette.INDEX_BLUE, paletteIndex);
            break;
          }
          case(1): // MODIFY BLUE
          {
            lastBlue = (lastBlue & 0x03) | (paletteIndex << 2);
            break;
          }
          case(2): // MODIFY RED
          {
            lastRed = (lastRed & 0x03) | (paletteIndex << 2);
            break;
          }
          case(3): // MODIFY GREEN
          {
            lastGreen = (lastGreen & 0x03) | (paletteIndex << 2);
            break;
          }
        }
        dest[0][x] = (byte)lastRed;
        dest[1][x] = (byte)lastGreen;
        dest[2][x] = (byte)lastBlue;
      }
      else
      if (rgb24)
      {
        dest[2][x] = (byte)(index >> 16);
        dest[1][x] = (byte)(index >> 8);
        dest[0][x] = (byte)index;
      }
      else
      {
        /* the value is an index into the lookup table */
        //destRgbData[destOffset++] = rgbLookup[index];
        dest[0][x] = (byte)index;
      }
    }
  }

  private void createExtraHalfbritePalette()
  {
    if (palette == null)
    {
      return;
    }
    int numPaletteEntries = palette.getNumEntries();
    Palette tempPalette = new Palette(numPaletteEntries * 2, 255);
    for (int i = 0; i < numPaletteEntries; i++)
    {
      int red = palette.getSample(Palette.INDEX_RED, i);
      tempPalette.putSample(Palette.INDEX_RED, numPaletteEntries + i, red);
      tempPalette.putSample(Palette.INDEX_RED, i, (red / 2) & 0xf0);
      int green = palette.getSample(Palette.INDEX_GREEN, i);
      tempPalette.putSample(Palette.INDEX_GREEN, numPaletteEntries + i, red);
      tempPalette.putSample(Palette.INDEX_GREEN, i, (green / 2) & 0xf0);
      int blue = palette.getSample(Palette.INDEX_BLUE, i);
      tempPalette.putSample(Palette.INDEX_BLUE, numPaletteEntries + i, blue);
      tempPalette.putSample(Palette.INDEX_BLUE, i, (blue / 2) & 0xf0);
    }
    palette = tempPalette;
  }

  private static String getChunkName(int name)
  {
    StringBuffer sb = new StringBuffer(4);
    sb.setLength(4);
    sb.setCharAt(0, (char)((name >> 24) & 0xff));
    sb.setCharAt(1, (char)((name >> 16) & 0xff));
    sb.setCharAt(2, (char)((name >> 8) & 0xff));
    sb.setCharAt(3, (char)((name & 0xff)));
    return new String(sb);
  }

  /*private static String getCompressionName(byte method)
  {
    switch(method)
    {
      case(COMPRESSION_NONE): return "Uncompressed";
      case(COMPRESSION_RLE): return "RLE";
      default: return "Unknown method (" + (method & 0xff) + ")";
    }
  }*/

  public String[] getFileExtensions()
  {
    return new String[] {".lbm", ".iff"};
  }

  public String getFormatName()
  {
    return "Amiga Interchange File Format (IFF, LBM)";
  }

  public String[] getMimeTypes()
  {
    return new String[] {"image/x-iff"};
  }

  public boolean isLoadingSupported()
  {
    return true;
  }

  public boolean isSavingSupported()
  {
    return false;
  }

  /**
   * Loads data.length bytes from the input stream to the data array,
   * regarding the compression type.
   * COMPRESSION_NONE will make this method load data.length bytes from
   * the input stream.
   * COMPRESSION_RLE will make this method decompress data.length bytes
   * from input.
   */
  private void loadBytes(DataInput in, byte[] data, int num, int y) throws
    InvalidFileStructureException,
    IOException
  {
    switch(compression)
    {
      case(COMPRESSION_NONE):
      {
        in.readFully(data, 0, num);
        break;
      }
      case(COMPRESSION_RLE):
      {
        int x = 0;
        while (x < num)
        {
          int n = in.readByte() & 0xff;
          //System.out.println("value=" + n);
          boolean compressed = false;
          int count = -1;
          try
          {
            if (n < 128)
            {
              // copy next n + 1 bytes literally
              n++;
              in.readFully(data, x, n);
              x += n;
            }
            else
            {
              // if n == -128, nothing happens
              if (n > 128)
              {
                compressed = true;
                // otherwise, compute counter
                count = 257 - n;
                // read another byte
                byte value = in.readByte();
                // write this byte counter times to output
                while (count-- > 0)
                {
                  data[x++] = value;
                }
              }
            }
          }
          catch (ArrayIndexOutOfBoundsException ioobe)
          {
            //System.out.println("Loading error");
            /* if the encoder did anything wrong, the above code
               could potentially write beyond array boundaries
               (e.g. if runs of data exceed line boundaries);
               this would result in an ArrayIndexOutOfBoundsException
               thrown by the virtual machine;
               to give a more understandable error message to the
               user, this exception is caught here and a
               explanatory InvalidFileStructureException is thrown */
            throw new InvalidFileStructureException("Error: " +
              "RLE-compressed image " +
              "file seems to be corrupt (compressed=" + compressed +
              ", x=" + x + ", y=" + y +
              ", count=" + (compressed ? (-((int)n) + 1) : n) +
              ", array length=" + data.length + ").");
          }
        }
        break;
      }
      default:
      {
        throw new InvalidFileStructureException("Error loading " +
          "image; unknown compression type (" + compression + ")");
      }
    }
  }

  /**
   * Loads an image from given input stream in, regarding the compression
   * type. The image will have 1 to 8 or 24 planes, a resolution given by
   * the dimension width times height. The color map data will be used to
   * convert index values to RGB pixels.
   * Returns the resulting image.
   * Will throw an IOException if either there were errors reading from the
   * input stream or if the file does not exactly match the file format.
   */
  private PixelImage loadImage(DataInput in) throws
    InvalidFileStructureException,
    IOException,
    UnsupportedTypeException,
    WrongParameterException
  {
    setBoundsIfNecessary(width, height);
    checkImageResolution();
    if (ham)
    {
      if (numPlanes == 6)
      {
        ham6 = true;
      }
      else
      if (numPlanes == 8)
      {
        ham8 = true;
      }
      else
      {
        throw new UnsupportedTypeException("Cannot handle " +
          "IFF ILBM HAM image file with number of planes " +
          "other than 6 or 8 (got " + numPlanes + ").");
      }
      if (palette == null)
      {
        throw new InvalidFileStructureException("Invalid IFF ILBM " +
          "file: HAM (Hold And Modify) image without a palette.");
      }
      int numPaletteEntries = palette.getNumEntries();
      if (ham6 && numPaletteEntries < 16)
      {
        throw new InvalidFileStructureException("Invalid IFF ILBM " +
          "file: HAM (Hold And Modify) 6 bit image with a " +
          "number of palette entries less than 16 (" +
          numPaletteEntries + ").");
      }
      if (ham8 && numPaletteEntries < 64)
      {
        throw new InvalidFileStructureException("Invalid IFF ILBM " +
          "file: HAM (Hold And Modify) 8 bit image with a " +
          "number of palette entries less than 64 (" +
          numPaletteEntries + ").");
      }
    }
    if (ehb)
    {
      createExtraHalfbritePalette();
    }
    int numBytesPerPlane = (width + 7) / 8;
    PixelImage image = null;
    Paletted8Image palettedImage = null;
    RGB24Image rgbImage = null;
    if (numPlanes == 24 || ham)
    {
      rgbImage = new MemoryRGB24Image(getBoundsWidth(), getBoundsHeight());
      image = rgbImage;
    }
    else
    {
      palettedImage = new MemoryPaletted8Image(getBoundsWidth(), getBoundsHeight(), palette);
      image = palettedImage;
    }
    /* only matters for uncompressed files;
       will be true if the number of bytes is odd;
       is computed differently for PBM and ILBM types
    */
    boolean oddBytesPerRow = (((numBytesPerPlane * numPlanes) % 2) != 0);
    if (type == MAGIC_PBM)
    {
      oddBytesPerRow = ((width % 2) == 1);
    }
    // plane data will have numPlanes planes for ILBM and 1 plane for PBM
    byte[][] planes = null;
    int numChannels = 1;
   
    if (type == MAGIC_ILBM)
    {
      int allocBytes = numBytesPerPlane;
      if ((numBytesPerPlane % 2) == 1)
      {
        allocBytes++;
      }
      // allocate numPlanes byte arrays
      planes = new byte[numPlanes][];
      if (rgb24 || ham)
      {
        numChannels = 3;
      }
      // for each of these byte arrays allocate numBytesPerPlane bytes
      for (int i = 0; i < numPlanes; i++)
      {
        planes[i] = new byte[allocBytes];
      }
    }
    else
    {
      // only one plane, but each plane has width bytes instead of
      // numBytesPerPlane
      planes = new byte[1][];
      planes[0] = new byte[width];
    }
    byte[][] dest = new byte[numChannels][];
    for (int i = 0; i < numChannels; i++)
    {
      dest[i] = new byte[width];
    }
    for (int y = 0, destY = 0 - getBoundsY1(); y <= getBoundsY2(); y++, destY++)
    {
      // load one row, different approach for PBM and ILBM
      if (type == MAGIC_ILBM)
      {
        // decode all planes for a complete row
        for (int p = 0; p < numPlanes; p++)
        {
          loadBytes(in, planes[p], numBytesPerPlane, y);
        }
      }
      else
      if (type == MAGIC_PBM)
      {
        loadBytes(in, planes[0], numBytesPerPlane, y);
      }
      /* all uncompressed rows must have an even number of bytes
         so in case the number of bytes per row is odd, one byte
         is read and dropped */
      if (compression == COMPRESSION_NONE && oddBytesPerRow)
      {
        in.readByte();
      }
      setProgress(y, getBoundsY2() + 1);
      // if we do not need the row we just loaded we continue loading
      // the next row
      if (!isRowRequired(y))
      {
        continue;
      }
      //System.out.println("storing row " + y + " as " + destY + ", numPlanes="+ numPlanes + ",type=" + type);
      // compute offset into pixel data array
      if (type == MAGIC_ILBM)
      {
        convertRow(planes, dest);
        if (rgb24 || ham)
        {
          rgbImage.putByteSamples(RGB24Image.INDEX_RED, 0, destY,
            getBoundsWidth(), 1, dest[0], getBoundsX1());
          rgbImage.putByteSamples(RGB24Image.INDEX_GREEN, 0, destY,
            getBoundsWidth(), 1, dest[1], getBoundsX1());
          rgbImage.putByteSamples(RGB24Image.INDEX_BLUE, 0, destY,
            getBoundsWidth(), 1, dest[2], getBoundsX1());
        }
        else
        {
          palettedImage.putByteSamples(0, 0, destY,
            getBoundsWidth(), 1, dest[0], getBoundsX1());
        }
      }
      else
      if (type == MAGIC_PBM)
      {
        palettedImage.putByteSamples(0, 0, destY, getBoundsWidth(), 1,
          planes[0], getBoundsX1());
      }
    }
    return image;
  }

  public void process() throws
    InvalidFileStructureException,
    MissingParameterException,
    OperationFailedException,
    UnsupportedTypeException,
    WrongFileFormatException
  {
    initModeFromIOObjects();
    if (getMode() == CodecMode.LOAD)
    {
      try
      {
        checkAndLoad();
      }
      catch (IOException ioe)
      {
        throw new InvalidFileStructureException("I/O error while loading: " + ioe.toString());
      }
    }
    else
    {
      throw new OperationFailedException("Only loading from IFF is supported.");
    }
  }
}
TOP

Related Classes of net.sourceforge.jiu.codecs.IFFCodec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.