package org.lateralgm.file.iconio;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import org.lateralgm.file.StreamDecoder;
import org.lateralgm.file.StreamEncoder;
/**
* <p>
* Parent class for indexed bitmaps (1, 4, and 8 bits per pixel). The value of a pixel refers to an
* entry in the color palette. The bitmap has a mask which is a 1 BPP bitmap specifiying whether a
* pixel is transparent or opaque.
* </p>
*
* @author © Christian Treber, ct@ctreber.com
*/
public abstract class AbstractBitmapIndexed extends AbstractBitmap
{
private static final int OPAQUE = 255;
/**
* The color palette. Refered to by the pixel values. The size is expected to be 2^BPP, but see
* getVerifiedColorCount() for a discussion of this.
*/
private Color[] colorPalette;
/** The pixel values. The value refers to an entry in the color palette. */
protected int[] pixels;
/**
* Create a bitmap with a color table and a mask.
*
* @param pDescriptor The descriptor.
*/
public AbstractBitmapIndexed(final BitmapDescriptor pDescriptor)
{
super(pDescriptor);
pixels = new int[getWidth() * getHeight()];
}
/**
* Needed to be replaced for indexed images because they contain a color palette and a mask which
* needs to be read as well.
*
* @param in The decoder.
* @throws IOException
*/
void read(final StreamDecoder in) throws IOException
{
readColorPalette(in);
readBitmap(in);
readMask(in);
}
/**
* This functions is needed b/c all classes read the bitmap, but not always a color table and a
* mask.
*
* @param pDec The decoder.
* @throws IOException
*/
abstract void readBitmap(final StreamDecoder pDec) throws IOException;
/**
* @param in The decoder.
* @throws IOException
*/
private void readColorPalette(final StreamDecoder in) throws IOException
{
final int lColorCount = getVerifiedColorCount();
colorPalette = new Color[lColorCount];
for (int lColorNo = 0; lColorNo < lColorCount; lColorNo++)
{
setColor(lColorNo,readColor(in));
}
}
private static Color readColor(final StreamDecoder in) throws IOException
{
final int lBlue = in.read();
final int lGreen = in.read();
final int lRed = in.read();
// "Reserved"
in.read();
return new Color(lRed,lGreen,lBlue);
}
/**
* Thanks to eml@ill.com for pointing out that official color count might not be what it should:
* 2^BPP specifies the miminum size for the color palette!
*
* @return The verified color count.
*/
private int getVerifiedColorCount()
{
int lColorCount = getColorCount();
final int lColorCount2 = 1 << descriptor.getBPP();
if (lColorCount < lColorCount2)
{
lColorCount = lColorCount2;
}
return lColorCount;
}
/**
* @return BufferedImage (palette) created from the indexed bitmap.
*/
public BufferedImage createImageIndexed()
{
final IndexColorModel lModel = createColorModel();
final BufferedImage lImage = new BufferedImage(getWidth(),getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,lModel);
lImage.getRaster().setSamples(0,0,getWidth(),getHeight(),0,pixels);
return lImage;
}
/**
* @return Color model created from color palette in entry.
*/
private IndexColorModel createColorModel()
{
final int lColorCount = getVerifiedColorCount();
final byte[] lRed = new byte[lColorCount];
final byte[] lGreen = new byte[lColorCount];
final byte[] lBlue = new byte[lColorCount];
final byte[] lAlpha = new byte[lColorCount];
for (int lColorNo = 0; lColorNo < lColorCount; lColorNo++)
{
final Color lColor = getColor(lColorNo);
lRed[lColorNo] = (byte) lColor.getRed();
lGreen[lColorNo] = (byte) lColor.getGreen();
lBlue[lColorNo] = (byte) lColor.getBlue();
lAlpha[lColorNo] = (byte) OPAQUE;
}
final IndexColorModel lModel = new IndexColorModel(8,lColorCount,lRed,lGreen,lBlue,lAlpha);
return lModel;
}
/**
* @return BufferedImage (ARGB) from the indexed bitmap.
*/
public BufferedImage createImageRGB()
{
final BufferedImage lImage = new BufferedImage(getWidth(),getHeight(),
BufferedImage.TYPE_INT_ARGB);
// For each pixel, copy the color information.
for (int lYPos = 0; lYPos < getHeight(); lYPos++)
{
for (int lXPos = 0; lXPos < getWidth(); lXPos++)
{
int lRGB = getColor(lXPos,lYPos).getRGB();
if (transparencyMask.isOpaque(lXPos,lYPos))
{
// Visible (sic), set alpha to opaque
lRGB |= 0xFF000000;
}
else
{
// Invisible, set alpha to transparent.
lRGB &= 0x00FFFFFF;
}
lImage.setRGB(lXPos,lYPos,lRGB);
}
}
return lImage;
}
/**
* Get the color for the specified point.
*
* @param pXPos The x position.
* @param pYPos The y position.
* @return Color of the selected point.
*/
public Color getColor(final int pXPos, final int pYPos)
{
return getColor(getPaletteIndex(pXPos,pYPos));
}
/**
* Index into the color palette for the specified point.
*
* @param pXPos The x position.
* @param pYPos The y position.
* @return Palette index for pixel x, y
*/
public int getPaletteIndex(final int pXPos, final int pYPos)
{
return pixels[pYPos * getWidth() + pXPos];
}
/**
* Get the color for the specified color palette index.
*
* @param pIndex of the color requested.
* @return Requested color.
*/
public Color getColor(final int pIndex)
{
if (pIndex >= getVerifiedColorCount())
{
throw new IllegalArgumentException("Color index out of range: is " + pIndex + ", max. "
+ getVerifiedColorCount());
}
return colorPalette[pIndex];
}
/**
* @param pIndex Color index.
* @param pColor Color to set.
*/
private void setColor(final int pIndex, final Color pColor)
{
colorPalette[pIndex] = pColor;
}
void write(StreamEncoder out) throws IOException
{
writeColorPalette(out);
writeBitmap(out);
writeMask(out);
}
private void writeColorPalette(StreamEncoder out) throws IOException
{
for (Color c : colorPalette)
{
//bgr res
out.write(c.getBlue());
out.write(c.getGreen());
out.write(c.getRed());
out.write(0); //reserved
}
}
abstract void writeBitmap(StreamEncoder out) throws IOException;
}