/*
 * FSDefineImage2.java
 * Transform
 * 
 * Copyright (c) 2001-2008 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.flagstone.transform;

import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

/**
 * FSDefineImage2 is used to define a transparent image compressed using the
 * lossless zlib compression algorithm.
 * 
 * <p>
 * The class supports colour-mapped images where the image data contains an
 * index into a colour table or images where the image data specifies the colour
 * directly. It extends FSDefineImage by including alpha channel information for
 * the colour table and pixels in the image.
 * </p>
 * 
 * <p>
 * For colour-mapped images the colour table contains up to 256, 32-bit colours.
 * The image contains one byte for each pixel which is an index into the table
 * to specify the colour for that pixel. The colour table and the image data are
 * compressed as a single block, with the colour table placed before the image.
 * </p>
 * 
 * <p>
 * For images where the colour is specified directly, the image data contains 32
 * bit colour values.
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>type</td>
 * <td>Identifies the data structure when it is encoded. The type attribute is
 * read-only and may be used when iterating through the objects in an FSMovie
 * object to identify the object class without using run-time type checking.</td>
 * </tr>
 * 
 * <tr>
 * <td>identifier</td>
 * <td>A unique identifier, in the range 1..65535, that is used to reference
 * the image from other objects.</td>
 * </tr>
 * 
 * <tr>
 * <td>width</td>
 * <td>Width of the image in pixels, NOT twips. 1 pixel = 20 twips.</td>
 * </tr>
 * 
 * <tr>
 * <td>height</td>
 * <td>Height of the image in pixels, NOT twips. 1 pixel = 20 twips.</td>
 * </tr>
 * 
 * <tr>
 * <td>pixelSize</td>
 * <td>The number of bits per pixel, either 8 or 32. 8-bit pixels are used for
 * indexed images where the colour table contains the colours for each pixel.
 * 32-bit pixels are used to specify the colour and transparency directly.</td>
 * </tr>
 * 
 * <tr>
 * <td>tableSize</td>
 * <td>The number of entries in the colour table - which is compressed as part
 * of the image. Each entry in the colour table contains one byte each for the
 * red, green and blue colour channels. The colour table is only used for
 * indexed colour images. The table is not used when the colour is specified
 * directly in each pixel.</td>
 * </tr>
 * 
 * <tr>
 * <td>compressedData</td>
 * <td>An array of bytes containing the zlib compressed colour table and image.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * The image data is stored in zlib compressed form within the object. For
 * colour-mapped images the compressed data contains the colour table followed
 * by the image data.
 * </p>
 * 
 * <h1 class="datasheet">History</h1>
 * 
 * <p>
 * The FSDefineImage2 class represents the DefineBitsLossless2 tag from the
 * Macromedia Flash (SWF) File Format Specification. It was introduced in Flash
 * 3.
 * </p>
 */
public class FSDefineImage2 extends FSDefineObject
{
	private int width = 0;
	private int height = 0;
	private int pixelSize = 8;
	private int tableSize = 0;
	private byte[] compressedData = null;

	/**
	 * Construct an FSCall object, initialising it with values decoded from an
	 * encoded object.
	 * 
	 * @param coder
	 *            an FSCoder containing the binary data.
	 */
	public FSDefineImage2(FSCoder coder)
	{
		super(DefineImage2, 0);
		extendLength = true;
		decode(coder);
	}

	/**
	 * Constructs an FSDefineImage2 object defining a colour-mapped image.
	 * 
	 * @param anIdentifier
	 *            the unique identifier for this object
	 * @param width
	 *            the width of the image.
	 * @param height
	 *            the height of the image.
	 * @param tableSize
	 *            the number of entries in the colour table in the compressed
	 *            data. Each entry is 32 bits.
	 * @param compressedBytes
	 *            the zlib compressed colour table and image data.
	 */
	public FSDefineImage2(int anIdentifier, int width, int height,
							int tableSize, byte[] compressedBytes)
	{
		super(DefineImage2, anIdentifier);
		extendLength = true;
		setWidth(width);
		setHeight(height);
		setPixelSize(8);
		setTableSize(tableSize);
		setCompressedData(compressedBytes);
	}

	/**
	 * Constructs an FSDefineImage object defining an image that specifies the
	 * colour directly. Each pixel in the image is 32 bits - 8 bits for the red,
	 * green, blue and alpha colour channels.
	 * 
	 * @param anIdentifier
	 *            the unique identifier for this object
	 * @param width
	 *            the width of the image.
	 * @param height
	 *            the height of the image.
	 * @param compressedBytes
	 *            the zlib compressed image data.
	 */
	public FSDefineImage2(int anIdentifier, int width, int height,
							byte[] compressedBytes)
	{
		super(DefineImage2, anIdentifier);
		extendLength = true;
		setWidth(width);
		setHeight(height);
		setPixelSize(32);
		setTableSize(0);
		setCompressedData(compressedBytes);
	}

	/**
	 * Constructs an FSDefineImage2 object by copying values from an existing
	 * object.
	 * 
	 * @param obj
	 *            an FSDefineImage2 object.
	 */
	public FSDefineImage2(FSDefineImage2 obj)
	{
		super(obj);
		width = obj.width;
		height = obj.height;
		pixelSize = obj.pixelSize;
		tableSize = obj.tableSize;
		compressedData = Transform.clone(obj.compressedData);
	}

	/**
	 * Gets the width of the image.
	 * 
	 * @return the width of the image.
	 */
	public int getWidth()
	{
		return width;
	}

	/**
	 * Gets the height of the image.
	 * 
	 * @return the height of the image in pixels.
	 */
	public int getHeight()
	{
		return height;
	}

	/**
	 * Gets the number of bits used to represent each pixel. Either 8 or 32
	 * bits. The pixel size is 8-bits for colour-mapped images and 32 bits for
	 * images where the colour is specified directly.
	 * 
	 * @return the number of bits per pixel: 8 or 32.
	 */
	public int getPixelSize()
	{
		return pixelSize;
	}

	/**
	 * Gets the number of entries in the colour table encoded the compressed
	 * image. For images where the colour is specified directly in the image
	 * then the table size is zero.
	 * 
	 * @return the number of entries in the colour table.
	 */
	public int getTableSize()
	{
		return tableSize;
	}

	/**
	 * Gets the compressed colour table and image.
	 * 
	 * @return an array of bytes containing the compressed colour table and
	 *         image.
	 */
	public byte[] getCompressedData()
	{
		return compressedData;
	}

	/**
	 * Sets the width of the image
	 * 
	 * @param aNumber
	 *            the width of the image.
	 */
	public void setWidth(int aNumber)
	{
		width = aNumber;
	}

	/**
	 * Sets the height of the image.
	 * 
	 * @param aNumber
	 *            the height of the image in pixels.
	 */
	public void setHeight(int aNumber)
	{
		height = aNumber;
	}

	/**
	 * Sets the size of the pixel in bits: 8 for colour-mapped images, 32 for
	 * direct images.
	 * 
	 * @param aNumber
	 *            the size of each pixel in bits: 8 or 32.
	 */
	public void setPixelSize(int aNumber)
	{
		pixelSize = aNumber;
	}

	/**
	 * Sets the number of entries in the colour table in the compressed image.
	 * For images where the colour is specified directly in the image then the
	 * table size should be zero.
	 * 
	 * @param aNumber
	 *            the number of entries in the colour table in the compressed
	 *            image.
	 */
	public void setTableSize(int aNumber)
	{
		tableSize = aNumber;
	}

	/**
	 * Sets the compressed image data using compressed data.
	 * 
	 * @param bytes
	 *            byte array containing zlib compressed colour table and image.
	 */
	public void setCompressedData(byte[] bytes)
	{
		compressedData = bytes;
	}

	public Object clone()
	{
		FSDefineImage2 anObject = (FSDefineImage2) super.clone();

		anObject.compressedData = Transform.clone(compressedData);

		return anObject;
	}

	public boolean equals(Object anObject)
	{
		boolean result = false;

		if (super.equals(anObject))
		{
			FSDefineImage2 typedObject = (FSDefineImage2) anObject;

			result = pixelSize == typedObject.pixelSize;
			result = result && width == typedObject.width;
			result = result && height == typedObject.height;
			result = result && tableSize == typedObject.tableSize;

			try
			{
				result = result
								&& Transform
												.equals(
																unzip(compressedData),
																unzip(typedObject.compressedData));
			} catch (DataFormatException e)
			{
				result = false;
			}
		}
		return result;
	}

	private byte[] unzip(byte[] bytes) throws DataFormatException
	{
		byte[] data = new byte[width * height * 8];
		int count = 0;

		Inflater inflater = new Inflater();
		inflater.setInput(bytes);
		count = inflater.inflate(data);

		byte[] uncompressedData = new byte[count];

		System.arraycopy(data, 0, uncompressedData, 0, count);

		return uncompressedData;
	}

	public void appendDescription(StringBuffer buffer, int depth)
	{
		buffer.append(name());

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "pixelSize", pixelSize);
			Transform.append(buffer, "width", width);
			Transform.append(buffer, "height", height);
			Transform.append(buffer, "tableSize", tableSize);
			Transform.append(buffer, "compressedData", "<data>");
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		super.length(coder);

		length += 5;
		length += (pixelSize == 8) ? 1 : 0;
		length += compressedData.length;

		return length;
	}

	public void encode(FSCoder coder)
	{
		coder.beginObject(name());

		super.encode(coder);

		switch (pixelSize)
		{
			case 8:
				coder.writeWord(3, 1);
				break;
			case 32:
				coder.writeWord(5, 1);
				break;
		}

		coder.writeWord(width, 2);
		coder.writeWord(height, 2);

		if (pixelSize == 8)
			coder.writeWord(tableSize - 1, 1);

		coder.writeBytes(compressedData);
		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		super.decode(coder);

		switch (coder.readWord(1, false))
		{
			case 3:
				pixelSize = 8;
				break;
			case 5:
				pixelSize = 32;
				break;
		}

		width = coder.readWord(2, false);
		height = coder.readWord(2, false);

		if (pixelSize == 8)
		{
			tableSize = coder.readWord(1, false) + 1;
			compressedData = new byte[length - 8];
			coder.readBytes(compressedData);
		} else
		{
			compressedData = new byte[length - 7];
			coder.readBytes(compressedData);
		}
		coder.endObject(name());
	}
}
