/*
* PNGDecoder.java
* Transform
*
* Copyright (c) 2009-2010 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.util.image;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import com.flagstone.transform.coder.BigDecoder;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.image.DefineImage;
import com.flagstone.transform.image.DefineImage2;
import com.flagstone.transform.image.ImageFormat;
import com.flagstone.transform.image.ImageTag;
/**
* PNGDecoder decodes Portable Network Graphics (PNG) format images so they can
* be used in a Flash file.
*/
@SuppressWarnings("PMD.TooManyMethods")
public final class PNGDecoder implements ImageProvider, ImageDecoder {
/** Alpha channel value for opaque colours. */
private static final int OPAQUE = 255;
/** Mask for reading unsigned 8-bit values. */
private static final int UNSIGNED_BYTE = 255;
/** Size of each colour table entry or pixel in a true colour image. */
private static final int RGBA_CHANNELS = 4;
/** Size of each colour table entry or pixel in a true colour image. */
private static final int RGB_CHANNELS = 3;
/** Size of a pixel in a RGB555 true colour image. */
private static final int RGB5_SIZE = 16;
/** Size of a pixel in a RGB8 true colour image. */
private static final int RGB8_SIZE = 24;
/** Byte offset to red channel. */
private static final int RED = 0;
/** Byte offset to red channel. */
private static final int GREEN = 1;
/** Byte offset to blue channel. */
private static final int BLUE = 2;
/** Byte offset to alpha channel. */
private static final int ALPHA = 3;
/** The image pixels occupy 1 bit. */
private static final int DEPTH_1 = 1;
/** The image pixels occupy 2 bits. */
private static final int DEPTH_2 = 2;
/** The image pixels occupy 4 bits. */
private static final int DEPTH_4 = 4;
/** The image pixels occupy 8 bits. */
private static final int DEPTH_8 = 8;
/** The image pixels occupy 16 bits. */
private static final int DEPTH_16 = 16;
/** Message used to signal that the image cannot be decoded. */
private static final String BAD_FORMAT = "Unsupported format";
/** Table for mapping monochrome images onto a colour palette. */
private static final int[] MONOCHROME = {0, 255};
/** Table for mapping 2-level grey-scale images onto a colour palette. */
private static final int[] GREYCSALE2 = {0, 85, 170, 255};
/** Table for mapping 4-level grey-scale images onto a colour palette. */
private static final int[] GREYCSALE4 = {0, 17, 34, 51, 68, 85, 102, 119,
136, 153, 170, 187, 204, 221, 238, 255};
/** signature identifying a PNG format image. */
private static final int[] SIGNATURE = {137, 80, 78, 71, 13, 10, 26, 10};
/** signature identifying a header block. */
private static final int IHDR = 0x49484452;
/** signature identifying a colour palette block. */
private static final int PLTE = 0x504c5445;
/** signature identifying an image data block. */
private static final int IDAT = 0x49444154;
/** signature identifying an end block. */
private static final int IEND = 0x49454e44;
/** signature identifying a transparency block. */
private static final int TRNS = 0x74524e53;
/*
* private static final int BKGD = 0x624b4744;
* private static final int CHRM = 0x6348524d;
* private static final int FRAC = 0x66524163;
* private static final int GAMA = 0x67414d41;
* private static final int GIFG = 0x67494667;
* private static final int GIFT = 0x67494674;
* private static final int GIFX = 0x67494678;
* private static final int HIST = 0x68495354;
* private static final int ICCP = 0x69434350;
* private static final int ITXT = 0x69545874;
* private static final int OFFS = 0x6f464673;
* private static final int PCAL = 0x7043414c;
* private static final int PHYS = 0x70485973;
* private static final int SBIT = 0x73424954;
* private static final int SCAL = 0x7343414c;
* private static final int SPLT = 0x73504c54;
* private static final int SRGB = 0x73524742;
* private static final int TEXT = 0x74455874;
* private static final int TIME = 0x74494d45;
* private static final int ZTXT = 0x7a545874;
*/
/** colorType value for grey-scale images. */
private static final int GREYSCALE = 0;
/** colorType value for true-colour images. */
private static final int TRUE_COLOUR = 2;
/** colorType value for indexed colour images. */
private static final int INDEXED_COLOUR = 3;
/** colorType value for grey-scale images with transparency. */
private static final int ALPHA_GREYSCALE = 4;
/** colorType value for true-colour images with transparency. */
private static final int ALPHA_TRUECOLOUR = 6;
/** filterMethod value for images with sub-pixel filtering. */
private static final int SUB_FILTER = 1;
/** filterMethod value for images with upper filtering. */
private static final int UP_FILTER = 2;
/** filterMethod value for images with average filtering. */
private static final int AVG_FILTER = 3;
/** filterMethod value for images with Paeth filtering. */
private static final int PAETH_FILTER = 4;
/** starting row for each image block. */
private static final int[] START_ROW = {0, 0, 4, 0, 2, 0, 1};
/** starting column for each image block. */
private static final int[] START_COLUMN = {0, 4, 0, 2, 0, 1, 0};
/** row increment for each image block. */
private static final int[] ROW_STEP = {8, 8, 8, 4, 4, 2, 2};
/** column increment for each image block. */
private static final int[] COLUMN_STEP = {8, 8, 4, 4, 2, 2, 1};
/** The number of bits used to represent each colour component. */
private transient int bitDepth;
/** The number of colour components in each pixel. */
private transient int colorComponents;
/** The method used to compress the image. */
// private int compression;
/** The method used to encode colours in the image. */
private transient int colorType;
/** Block filtering method used in the image. */
// private int filterMethod;
/** Row interlacing method used in the image. */
private transient int interlaceMethod;
/** Default value for transparent grey-scale pixels. */
private transient int transparentGrey;
/** Default value for transparent red pixels. */
private transient int transparentRed;
/** Default value for transparent green pixels. */
// private int transparentGreen;
/** Default value for transparent blue pixels. */
// private int transparentBlue;
/** Binary data taken directly from encoded image. */
private transient byte[] chunkData = new byte[0];
/** The format of the decoded image. */
private transient ImageFormat format;
/** The width of the image in pixels. */
private transient int width;
/** The height of the image in pixels. */
private transient int height;
/** The colour table for indexed images. */
private transient byte[] table;
/** The image data. */
private transient byte[] image;
/** {@inheritDoc} */
@Override
public ImageDecoder newDecoder() {
return new PNGDecoder();
}
/** {@inheritDoc} */
@Override
public void read(final File file) throws IOException, DataFormatException {
final ImageInfo info = new ImageInfo();
info.setInput(new RandomAccessFile(file, "r"));
// info.setDetermineImageNumber(true);
if (!info.check()) {
throw new DataFormatException(BAD_FORMAT);
}
read(new FileInputStream(file));
}
/** {@inheritDoc} */
@Override
public void read(final URL url) throws IOException, DataFormatException {
final URLConnection connection = url.openConnection();
final int length = connection.getContentLength();
if (length < 0) {
throw new FileNotFoundException(url.getFile());
}
read(url.openStream());
}
/** {@inheritDoc} */
@Override
public ImageTag defineImage(final int identifier) {
ImageTag object = null;
final ImageFilter filter = new ImageFilter();
switch (format) {
case IDX8:
object = new DefineImage(identifier, width, height,
table.length / RGBA_CHANNELS,
zip(filter.merge(
filter.adjustScan(width, height, image), table)));
break;
case IDXA:
object = new DefineImage2(identifier, width, height,
table.length / RGBA_CHANNELS,
zip(filter.mergeAlpha(
filter.adjustScan(width, height, image), table)));
break;
case RGB5:
object = new DefineImage(identifier, width, height,
zip(filter.packColors(width, height, image)), RGB5_SIZE);
break;
case RGB8:
filter.orderAlpha(image);
object = new DefineImage(identifier, width, height, zip(image),
RGB8_SIZE);
break;
case RGBA:
applyAlpha(image);
object = new DefineImage2(identifier, width, height, zip(image));
break;
default:
throw new AssertionError(BAD_FORMAT);
}
return object;
}
/**
* Apply the level for the alpha channel to the red, green and blue colour
* channels for encoding the image so it can be added to a Flash movie.
* @param img the image data.
*/
private void applyAlpha(final byte[] img) {
int alpha;
for (int i = 0; i < img.length; i += RGBA_CHANNELS) {
alpha = img[i + ALPHA] & UNSIGNED_BYTE;
img[i + ALPHA] = (byte) (((img[i + BLUE] & UNSIGNED_BYTE) * alpha)
/ OPAQUE);
img[i + BLUE] = (byte) (((img[i + GREEN] & UNSIGNED_BYTE) * alpha)
/ OPAQUE);
img[i + GREEN] = (byte) (((img[i + RED] & UNSIGNED_BYTE) * alpha)
/ OPAQUE);
img[i + RED] = (byte) alpha;
}
}
/** {@inheritDoc} */
@Override
public void read(final InputStream stream)
throws DataFormatException, IOException {
final BigDecoder coder = new BigDecoder(stream);
int length = 0;
int chunkType = 0;
boolean moreChunks = true;
chunkData = new byte[0];
transparentGrey = -1;
transparentRed = -1;
for (int i = 0; i < 8; i++) {
if (coder.readByte() != SIGNATURE[i]) {
throw new DataFormatException(BAD_FORMAT);
}
}
while (moreChunks) {
length = coder.readInt();
chunkType = coder.readInt();
coder.mark();
switch (chunkType) {
case IHDR:
decodeIHDR(coder);
break;
case PLTE:
decodePLTE(coder, length);
break;
case TRNS:
decodeTRNS(coder, length);
break;
case IDAT:
decodeIDAT(coder, length);
break;
case IEND:
moreChunks = false;
coder.skip(length + 4);
break;
default:
coder.skip(length + 4);
break;
}
}
decodeImage();
}
/**
* Decode the header, IHDR, block from a PNG image.
* @param coder the decoder containing the image data.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException is the image contains an unsupported format.
*/
private void decodeIHDR(final BigDecoder coder)
throws IOException, DataFormatException {
width = coder.readInt();
height = coder.readInt();
bitDepth = coder.readByte();
colorType = coder.readByte();
/* compression = */ coder.readByte();
/* filterMethod = */ coder.readByte();
interlaceMethod = coder.readByte();
/* crc = */ coder.readInt();
decodeFormat();
}
/**
* Decode the image format.
* @throws DataFormatException if the image is in an unsupported format.
*/
private void decodeFormat() throws DataFormatException {
switch (colorType) {
case GREYSCALE:
format = ImageFormat.RGB8;
colorComponents = 1;
break;
case TRUE_COLOUR:
format = ImageFormat.RGB8;
colorComponents = RGB_CHANNELS;
break;
case INDEXED_COLOUR:
format = ImageFormat.IDX8;
colorComponents = 1;
break;
case ALPHA_GREYSCALE:
format = ImageFormat.RGBA;
colorComponents = 2;
break;
case ALPHA_TRUECOLOUR:
format = ImageFormat.RGBA;
colorComponents = RGBA_CHANNELS;
break;
default:
throw new DataFormatException(BAD_FORMAT);
}
if (format == ImageFormat.RGB8 && bitDepth <= 5) {
format = ImageFormat.RGB5;
}
}
/**
* Decode the colour palette, PLTE, block from a PNG image.
* @param coder the decoder containing the image data.
* @param length the length of the block in bytes.
* @throws IOException if there is an error decoding the data.
*/
private void decodePLTE(final BigDecoder coder, final int length)
throws IOException {
if (colorType == RGB_CHANNELS) {
final int paletteSize = length / RGB_CHANNELS;
int index = 0;
table = new byte[paletteSize * RGBA_CHANNELS];
for (int i = 0; i < paletteSize; i++, index += RGBA_CHANNELS) {
table[index + ALPHA] = (byte) OPAQUE;
table[index + BLUE] = (byte) coder.readByte();
table[index + GREEN] = (byte) coder.readByte();
table[index + RED] = (byte) coder.readByte();
}
} else {
coder.skip(length);
}
coder.readInt(); // crc
}
/**
* Decode the transparency, TRNS, block from a PNG image.
* @param coder the decoder containing the image data.
* @param length the length of the block in bytes.
* @throws IOException if there is an error decoding the data.
*/
private void decodeTRNS(final BigDecoder coder, final int length)
throws IOException {
int index = 0;
switch (colorType) {
case GREYSCALE:
transparentGrey = coder.readUnsignedShort();
format = ImageFormat.RGBA;
break;
case TRUE_COLOUR:
transparentRed = coder.readUnsignedShort();
format = ImageFormat.RGBA;
/* transparentGreen = */ coder.readUnsignedShort();
/* transparentBlue = */ coder.readUnsignedShort();
break;
case INDEXED_COLOUR:
format = ImageFormat.IDXA;
for (int i = 0; i < length; i++, index += RGBA_CHANNELS) {
table[index + ALPHA] = (byte) coder.readByte();
if (table[index + ALPHA] == 0) {
table[index + RED] = 0;
table[index + GREEN] = 0;
table[index + BLUE] = 0;
}
}
break;
default:
break;
}
coder.readInt(); // crc
}
/**
* Decode the image data, IDAT, block from a PNG image.
* @param coder the decoder containing the image data.
* @param length the length of the block in bytes.
* @throws IOException if there is an error decoding the data.
*/
private void decodeIDAT(final BigDecoder coder, final int length)
throws IOException {
final int currentLength = chunkData.length;
final int newLength = currentLength + length;
final byte[] data = new byte[newLength];
System.arraycopy(chunkData, 0, data, 0, currentLength);
for (int i = currentLength; i < newLength; i++) {
data[i] = (byte) coder.readByte();
}
chunkData = data;
coder.readInt(); // crc
}
/**
* Decode a PNG encoded image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the image cannot be decoded.
*/
private void decodeImage() throws IOException, DataFormatException {
if ((format == ImageFormat.IDX8) || (format == ImageFormat.IDXA)) {
image = new byte[height * width];
} else {
image = new byte[height * width * RGBA_CHANNELS];
}
if (interlaceMethod == 1) {
decodeInterlaced();
} else {
decodeProgressive();
}
}
/**
* Decode an interlaced image.
* @throws IOException if there is an error reading the image data.
* @throws DataFormatException if the image is in an unsupported format.
*/
private void decodeInterlaced() throws IOException, DataFormatException {
final byte[] encodedImage = unzip(chunkData);
final int bitsPerPixel = bitDepth * colorComponents;
final int bitsPerRow = width * bitsPerPixel;
final int rowWidth = (bitsPerRow + 7) >> 3;
final int bytesPerPixel = (bitsPerPixel < 8) ? 1 : bitsPerPixel / 8;
final byte[] current = new byte[rowWidth];
final byte[] previous = new byte[rowWidth];
for (int i = 0; i < rowWidth; i++) {
previous[i] = (byte) 0;
}
int imageIndex = 0;
int row = 0;
int col = 0;
int filter = 0;
int scanBits = 0;
int scanLength = 0;
for (int pass = 0; pass < 7; pass++) {
for (row = START_ROW[pass]; (row < height)
&& (imageIndex < encodedImage.length);
row += ROW_STEP[pass]) {
for (col = START_COLUMN[pass], scanBits = 0; col < width;
col += COLUMN_STEP[pass]) {
scanBits += bitsPerPixel;
}
scanLength = (scanBits + 7) >> 3;
filter = encodedImage[imageIndex++];
for (int i = 0; i < scanLength; i++, imageIndex++) {
current[i] = (imageIndex < encodedImage.length)
? encodedImage[imageIndex] : previous[i];
}
defilter(filter, bytesPerPixel, scanLength, current, previous);
deblock(row, current, START_COLUMN[pass], COLUMN_STEP[pass]);
System.arraycopy(current, 0, previous, 0, scanLength);
}
}
}
/**
* Decode a progressive-scan image.
* @throws IOException if there is an error reading the image data.
* @throws DataFormatException if the image is in an unsupported format.
*/
private void decodeProgressive() throws IOException, DataFormatException {
final byte[] data = unzip(chunkData);
final int bitsPerPixel = bitDepth * colorComponents;
final int bitsPerRow = width * bitsPerPixel;
final int rowWidth = (bitsPerRow + 7) >> 3;
final int bytesPerPixel = (bitsPerPixel < 8) ? 1 : bitsPerPixel / 8;
final byte[] current = new byte[rowWidth];
final byte[] previous = new byte[rowWidth];
for (int i = 0; i < rowWidth; i++) {
previous[i] = (byte) 0;
}
int index = 0;
int row = 0;
int col = 0;
int filter = 0;
int scanBits = 0;
int scanLength = 0;
for (row = 0; (row < height) && (index < data.length); row++) {
for (col = 0, scanBits = 0; col < width; col++) {
scanBits += bitsPerPixel;
}
scanLength = (scanBits + 7) >> 3;
filter = data[index++];
for (int i = 0; i < scanLength; i++, index++) {
current[i] = (index < data.length)
? data[index] : previous[i];
}
defilter(filter, bytesPerPixel, scanLength, current, previous);
deblock(row, current, 0, 1);
System.arraycopy(current, 0, previous, 0, scanLength);
}
}
/**
* Reverse the filter applied to the pixel data.
* @param filter the filter type.
* @param size the offset in the row to the start of the encoded data.
* @param scan the number of bytes in the block
* @param current the pixel data in the encoded image row.
* @param previous the pixel data from the previous row.
*/
private void defilter(final int filter, final int size, final int scan,
final byte[] current, final byte[] previous) {
switch (filter) {
case SUB_FILTER:
subFilter(size, scan, current);
break;
case UP_FILTER:
upFilter(scan, current, previous);
break;
case AVG_FILTER:
averageFilter(size, scan, current, previous);
break;
case PAETH_FILTER:
paethFilter(size, scan, current, previous);
break;
default:
break;
}
}
/**
* Reverse the sub-filter applied to the pixel data.
* @param start the offset in the row to the start of the encoded data.
* @param count the number of bytes in the block
* @param current the pixel data in the encoded image row.
*/
private void subFilter(final int start, final int count,
final byte[] current) {
for (int i = start, j = 0; i < count; i++, j++) {
current[i] = (byte) (current[i] + current[j]);
}
}
/**
* Reverse the up-filter applied to the pixel data.
* @param count the number of bytes in the block
* @param current the pixel data in the encoded image row.
* @param previous the pixel data from the previous row.
*/
private void upFilter(final int count, final byte[] current,
final byte[] previous) {
for (int i = 0; i < count; i++) {
current[i] = (byte) (current[i] + previous[i]);
}
}
/**
* Reverse the average filter applied to the pixel data.
* @param start the offset in the row to the start of the encoded data.
* @param count the number of bytes in the block
* @param current the pixel data in the encoded image row.
* @param previous the pixel data from the previous row.
*/
private void averageFilter(final int start, final int count,
final byte[] current, final byte[] previous) {
for (int cindex = 0; cindex < start; cindex++) {
current[cindex] = (byte) (current[cindex]
+ (0 + (UNSIGNED_BYTE
& previous[cindex])) / 2);
}
for (int cindex = start, pindex = 0;
cindex < count; cindex++, pindex++) {
current[cindex] = (byte) (current[cindex]
+ ((UNSIGNED_BYTE & current[pindex])
+ (UNSIGNED_BYTE & previous[cindex])) / 2);
}
}
/**
* Reverse the Paeth filter applied to the pixel data.
* @param start the offset in the row to the start of the encoded data.
* @param count the number of bytes in the block
* @param current the pixel data in the encoded image row.
* @param previous the pixel data from the previous row.
*/
private void paethFilter(final int start, final int count,
final byte[] current, final byte[] previous) {
for (int cindex = 0; cindex < start; cindex++) {
current[cindex] = (byte) (current[cindex]
+ paeth((byte) 0,
previous[cindex], (byte) 0));
}
for (int cindex = start, pindex = 0;
cindex < count; cindex++, pindex++) {
current[cindex] = (byte) (current[cindex]
+ paeth(current[pindex],
previous[cindex], previous[pindex]));
}
}
/**
* Decode a Paeth encoded pixel.
* @param lower the current pixel.
* @param upper the pixel on the previous row.
* @param next the next pixel in current row.
* @return the decoded value.
*/
private int paeth(final byte lower, final byte upper, final byte next) {
final int left = UNSIGNED_BYTE & lower;
final int above = UNSIGNED_BYTE & upper;
final int upperLeft = UNSIGNED_BYTE & next;
final int estimate = left + above - upperLeft;
int distLeft = estimate - left;
if (distLeft < 0) {
distLeft = -distLeft;
}
int distAbove = estimate - above;
if (distAbove < 0) {
distAbove = -distAbove;
}
int distUpperLeft = estimate - upperLeft;
if (distUpperLeft < 0) {
distUpperLeft = -distUpperLeft;
}
int value;
if ((distLeft <= distAbove) && (distLeft <= distUpperLeft)) {
value = left;
} else if (distAbove <= distUpperLeft) {
value = above;
} else {
value = upperLeft;
}
return value;
}
/**
* Decode a block of image data.
* @param row the current row in the decoded image.
* @param current the encoded block data.
* @param start the offset in the image row.
* @param inc the size of each pixel.
* @throws IOException if there is an error reading the data.
* @throws DataFormatException if the image is encoded in an unsupported
* format.
*/
private void deblock(final int row, final byte[] current,
final int start, final int inc)
throws IOException, DataFormatException {
final ByteArrayInputStream stream = new ByteArrayInputStream(current);
final BigDecoder coder = new BigDecoder(stream);
for (int col = start; col < width; col += inc) {
switch (colorType) {
case GREYSCALE:
decodeGreyscale(coder, row, col);
break;
case TRUE_COLOUR:
decodeTrueColour(coder, row, col);
break;
case INDEXED_COLOUR:
decodeIndexedColour(coder, row, col);
break;
case ALPHA_GREYSCALE:
decodeAlphaGreyscale(coder, row, col);
break;
case ALPHA_TRUECOLOUR:
decodeAlphaTrueColour(coder, row, col);
break;
default:
throw new DataFormatException(BAD_FORMAT);
}
}
}
/**
* Decode a grey-scale pixel with no transparency.
* @param coder the decode containing the image data.
* @param row the row number of the pixel in the image.
* @param col the column number of the pixel in the image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the pixel data cannot be decoded.
*/
private void decodeGreyscale(final BigDecoder coder, final int row,
final int col) throws IOException, DataFormatException {
int pixel = 0;
byte colour = 0;
switch (bitDepth) {
case DEPTH_1:
pixel = coder.readBits(bitDepth, false);
colour = (byte) MONOCHROME[pixel];
break;
case DEPTH_2:
pixel = coder.readBits(bitDepth, false);
colour = (byte) GREYCSALE2[pixel];
break;
case DEPTH_4:
pixel = coder.readBits(bitDepth, false);
colour = (byte) GREYCSALE4[pixel];
break;
case DEPTH_8:
pixel = coder.readByte();
colour = (byte) pixel;
break;
case DEPTH_16:
pixel = coder.readUnsignedShort();
colour = (byte) (pixel >> Coder.TO_LOWER_BYTE);
break;
default:
throw new DataFormatException(BAD_FORMAT);
}
int index = row * (width << 2) + (col << 2);
image[index++] = colour;
image[index++] = colour;
image[index++] = colour;
image[index++] = (byte) transparentGrey;
}
/**
* Decode a true colour pixel with no transparency.
* @param coder the decode containing the image data.
* @param row the row number of the pixel in the image.
* @param col the column number of the pixel in the image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the pixel data cannot be decoded.
*/
private void decodeTrueColour(final BigDecoder coder, final int row,
final int col) throws IOException, DataFormatException {
int pixel = 0;
byte colour = 0;
final int index = row * (width << 2) + (col << 2);
for (int i = 0; i < colorComponents; i++) {
if (bitDepth == DEPTH_8) {
pixel = coder.readByte();
colour = (byte) pixel;
} else if (bitDepth == DEPTH_16) {
pixel = coder.readUnsignedShort();
colour = (byte) (pixel >> Coder.TO_LOWER_BYTE);
} else {
throw new DataFormatException(BAD_FORMAT);
}
image[index + i] = colour;
}
image[index + ALPHA] = (byte) transparentRed;
}
/**
* Decode an index colour pixel.
* @param coder the decode containing the image data.
* @param row the row number of the pixel in the image.
* @param col the column number of the pixel in the image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the pixel data cannot be decoded.
*/
private void decodeIndexedColour(final BigDecoder coder, final int row,
final int col) throws IOException, DataFormatException {
int index = 0;
switch (bitDepth) {
case DEPTH_1:
index = coder.readBits(bitDepth, false);
break;
case DEPTH_2:
index = coder.readBits(bitDepth, false);
break;
case DEPTH_4:
index = coder.readBits(bitDepth, false);
break;
case DEPTH_8:
index = coder.readByte();
break;
case DEPTH_16:
index = coder.readUnsignedShort();
break;
default:
throw new DataFormatException(BAD_FORMAT);
}
image[row * width + col] = (byte) index;
}
/**
* Decode a grey-scale pixel with transparency.
* @param coder the decode containing the image data.
* @param row the row number of the pixel in the image.
* @param col the column number of the pixel in the image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the pixel data cannot be decoded.
*/
private void decodeAlphaGreyscale(final BigDecoder coder, final int row,
final int col) throws IOException, DataFormatException {
int pixel = 0;
byte colour = 0;
int alpha = 0;
switch (bitDepth) {
case DEPTH_1:
pixel = coder.readBits(bitDepth, false);
colour = (byte) MONOCHROME[pixel];
alpha = coder.readBits(bitDepth, false);
break;
case DEPTH_2:
pixel = coder.readBits(bitDepth, false);
colour = (byte) GREYCSALE2[pixel];
alpha = coder.readBits(bitDepth, false);
break;
case DEPTH_4:
pixel = coder.readBits(bitDepth, false);
colour = (byte) GREYCSALE4[pixel];
alpha = coder.readBits(bitDepth, false);
break;
case DEPTH_8:
pixel = coder.readByte();
colour = (byte) pixel;
alpha = coder.readByte();
break;
case DEPTH_16:
pixel = coder.readUnsignedShort();
colour = (byte) (pixel >> Coder.TO_LOWER_BYTE);
alpha = coder.readUnsignedShort() >> Coder.TO_LOWER_BYTE;
break;
default:
throw new DataFormatException(BAD_FORMAT);
}
int index = row * (width << 2) + (col << 2);
image[index++] = colour;
image[index++] = colour;
image[index++] = colour;
image[index] = (byte) alpha;
}
/**
* Decode a true colour pixel with transparency.
* @param coder the decode containing the image data.
* @param row the row number of the pixel in the image.
* @param col the column number of the pixel in the image.
* @throws IOException if there is an error decoding the data.
* @throws DataFormatException if the pixel data cannot be decoded.
*/
private void decodeAlphaTrueColour(final BigDecoder coder, final int row,
final int col) throws IOException, DataFormatException {
int pixel = 0;
byte colour = 0;
final int index = row * (width << 2) + (col << 2);
for (int i = 0; i < colorComponents; i++) {
if (bitDepth == DEPTH_8) {
pixel = coder.readByte();
colour = (byte) pixel;
} else if (bitDepth == DEPTH_16) {
pixel = coder.readUnsignedShort();
colour = (byte) (pixel >> Coder.TO_LOWER_BYTE);
} else {
throw new DataFormatException(BAD_FORMAT);
}
image[index + i] = colour;
}
}
/**
* Uncompress the image using the ZIP format.
* @param bytes the compressed image data.
* @return the uncompressed image.
* @throws DataFormatException if the compressed image is not in the ZIP
* format or cannot be uncompressed.
*/
private byte[] unzip(final byte[] bytes) throws DataFormatException {
final byte[] data = new byte[width * height * 8];
int count = 0;
final Inflater inflater = new Inflater();
inflater.setInput(bytes);
count = inflater.inflate(data);
final byte[] uncompressedData = new byte[count];
System.arraycopy(data, 0, uncompressedData, 0, count);
return uncompressedData;
}
/**
* Compress the image using the ZIP format.
* @param img the image data.
* @return the compressed image.
*/
private byte[] zip(final byte[] img) {
final Deflater deflater = new Deflater();
deflater.setInput(img);
deflater.finish();
final byte[] compressedData = new byte[img.length * 2];
final int bytesCompressed = deflater.deflate(compressedData);
final byte[] newData = Arrays.copyOf(compressedData, bytesCompressed);
return newData;
}
/** {@inheritDoc} */
@Override
public int getWidth() {
return width;
}
/** {@inheritDoc} */
@Override
public int getHeight() {
return height;
}
/** {@inheritDoc} */
@Override
public byte[] getImage() {
byte[] copy;
switch (format) {
case IDX8:
case IDXA:
copy = new byte[image.length * RGBA_CHANNELS];
int tableIndex;
for (int i = 0, index = 0; i < image.length; i++) {
tableIndex = image[i] * RGBA_CHANNELS;
copy[index++] = table[tableIndex + RED];
copy[index++] = table[tableIndex + GREEN];
copy[index++] = table[tableIndex + BLUE];
copy[index++] = table[tableIndex + ALPHA];
}
break;
case RGB5:
case RGB8:
case RGBA:
copy = Arrays.copyOf(image, image.length);
break;
default:
throw new AssertionError(BAD_FORMAT);
}
return copy;
}
}