/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.internal.image;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
final class LZWCodec {
int bitsPerPixel, blockSize, blockIndex, currentByte, bitsLeft,
codeSize, clearCode, endCode, newCodes, topSlot, currentSlot,
imageWidth, imageHeight, imageX, imageY, pass, line, codeMask;
byte[] block, lineArray;
int[] stack, suffix, prefix;
LZWNode[] nodeStack;
LEDataInputStream inputStream;
LEDataOutputStream outputStream;
ImageData image;
ImageLoader loader;
boolean interlaced;
static final int[] MASK_TABLE = new int[] {
0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F,
0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF
};
/**
* Decode the input.
*/
void decode() {
int code;
int oc = 0;
int fc = 0;
byte[] buf = new byte[imageWidth];
int stackIndex = 0;
int bufIndex = 0;
int c;
while ((c = nextCode()) != endCode) {
if (c == clearCode) {
codeSize = bitsPerPixel + 1;
codeMask = MASK_TABLE[bitsPerPixel];
currentSlot = newCodes;
topSlot = 1 << codeSize;
while ((c = nextCode()) == clearCode) {}
if (c != endCode) {
oc = fc = c;
buf[bufIndex] = (byte)c;
bufIndex++;
if (bufIndex == imageWidth) {
nextPutPixels(buf);
bufIndex = 0;
}
}
} else {
code = c;
if (code >= currentSlot) {
code = oc;
stack[stackIndex] = fc;
stackIndex++;
}
while (code >= newCodes) {
stack[stackIndex] = suffix[code];
stackIndex++;
code = prefix[code];
}
stack[stackIndex] = code;
stackIndex++;
if (currentSlot < topSlot) {
fc = code;
suffix[currentSlot] = fc;
prefix[currentSlot] = oc;
currentSlot++;
oc = c;
}
if (currentSlot >= topSlot) {
if (codeSize < 12) {
codeMask = MASK_TABLE[codeSize];
codeSize++;
topSlot = topSlot + topSlot;
}
}
while (stackIndex > 0) {
stackIndex--;
buf[bufIndex] = (byte)stack[stackIndex];
bufIndex++;
if (bufIndex == imageWidth) {
nextPutPixels(buf);
bufIndex = 0;
}
}
}
}
if (bufIndex != 0 && line < imageHeight) {
nextPutPixels(buf);
}
}
/**
* Decode the LZW-encoded bytes in the given byte stream
* into the given DeviceIndependentImage.
*/
public void decode(LEDataInputStream inputStream, ImageLoader loader, ImageData image, boolean interlaced, int depth) {
this.inputStream = inputStream;
this.loader = loader;
this.image = image;
this.interlaced = interlaced;
this.bitsPerPixel = depth;
initializeForDecoding();
decode();
}
/**
* Encode the image.
*/
void encode() {
nextPutCode(clearCode);
int lastPrefix = encodeLoop();
nextPutCode(lastPrefix);
nextPutCode(endCode);
// Write out last partial block
if (bitsLeft == 8) {
block[0] = (byte)(blockIndex - 1); // Nothing in last byte
} else {
block[0] = (byte)(blockIndex); // Last byte has data
}
writeBlock();
// Write out empty block to indicate the end (if needed)
if (block[0] != 0) {
block[0] = 0;
writeBlock();
}
}
/**
* Encode the bytes into the given byte stream
* from the given DeviceIndependentImage.
*/
public void encode(LEDataOutputStream byteStream, ImageData image) {
this.outputStream = byteStream;
this.image = image;
initializeForEncoding();
encode();
}
/**
* Encoding loop broken out to allow early return.
*/
int encodeLoop() {
int pixel = nextPixel();
boolean found;
LZWNode node;
while (true) {
int currentPrefix = pixel;
node = nodeStack[currentPrefix];
found = true;
pixel = nextPixel();
if (pixel < 0)
return currentPrefix;
while (found && (node.children != null)) {
node = node.children;
while (found && (node.suffix != pixel)) {
if (pixel < node.suffix) {
if (node.left == null) {
node.left = new LZWNode();
found = false;
}
node = node.left;
} else {
if (node.right == null) {
node.right = new LZWNode();
found = false;
}
node = node.right;
}
}
if (found) {
currentPrefix = node.code;
pixel = nextPixel();
if (pixel < 0)
return currentPrefix;
}
}
if (found) {
node.children = new LZWNode();
node = node.children;
}
node.children = null;
node.left = null;
node.right = null;
node.code = currentSlot;
node.prefix = currentPrefix;
node.suffix = pixel;
nextPutCode(currentPrefix);
currentSlot++;
// Off by one?
if (currentSlot < 4096) {
if (currentSlot > topSlot) {
codeSize++;
codeMask = MASK_TABLE[codeSize - 1];
topSlot *= 2;
}
} else {
nextPutCode(clearCode);
for (int i = 0; i < nodeStack.length; i++)
nodeStack[i].children = null;
codeSize = bitsPerPixel + 1;
codeMask = MASK_TABLE[codeSize - 1];
currentSlot = newCodes;
topSlot = 1 << codeSize;
}
}
}
/**
* Initialize the receiver for decoding the given
* byte array.
*/
void initializeForDecoding() {
pass = 1;
line = 0;
codeSize = bitsPerPixel + 1;
topSlot = 1 << codeSize;
clearCode = 1 << bitsPerPixel;
endCode = clearCode + 1;
newCodes = currentSlot = endCode + 1;
currentByte = -1;
blockSize = bitsLeft = 0;
blockIndex = 0;
codeMask = MASK_TABLE[codeSize - 1];
stack = new int[4096];
suffix = new int[4096];
prefix = new int[4096];
block = new byte[256];
imageWidth = image.width;
imageHeight = image.height;
}
/**
* Initialize the receiver for encoding the given
* byte array.
*/
void initializeForEncoding() {
interlaced = false;
bitsPerPixel = image.depth;
codeSize = bitsPerPixel + 1;
topSlot = 1 << codeSize;
clearCode = 1 << bitsPerPixel;
endCode = clearCode + 1;
newCodes = currentSlot = endCode + 1;
bitsLeft = 8;
currentByte = 0;
blockIndex = 1;
blockSize = 255;
block = new byte[blockSize];
block[0] = (byte)(blockSize - 1);
nodeStack = new LZWNode[1 << bitsPerPixel];
for (int i = 0; i < nodeStack.length; i++) {
LZWNode node = new LZWNode();
node.code = i + 1;
node.prefix = -1;
node.suffix = i + 1;
nodeStack[i] = node;
}
imageWidth = image.width;
imageHeight = image.height;
imageY = -1;
lineArray = new byte[imageWidth];
imageX = imageWidth + 1; // Force a read
}
/**
* Answer the next code from the input byte array.
*/
int nextCode() {
int code;
if (bitsLeft == 0) {
if (blockIndex >= blockSize) {
blockSize = readBlock();
blockIndex = 0;
if (blockSize == 0) return endCode;
}
blockIndex++;
currentByte = block[blockIndex] & 0xFF;
bitsLeft = 8;
code = currentByte;
} else {
int shift = bitsLeft - 8;
if (shift < 0)
code = currentByte >> (0 - shift);
else
code = currentByte << shift;
}
while (codeSize > bitsLeft) {
if (blockIndex >= blockSize) {
blockSize = readBlock();
blockIndex = 0;
if (blockSize == 0) return endCode;
}
blockIndex++;
currentByte = block[blockIndex] & 0xFF;
code += currentByte << bitsLeft;
bitsLeft += 8;
}
bitsLeft -= codeSize;
return code & codeMask;
}
/**
* Answer the next pixel to encode in the image
*/
int nextPixel() {
imageX++;
if (imageX > imageWidth) {
imageY++;
if (imageY >= imageHeight) {
return -1;
} else {
nextPixels(lineArray, imageWidth);
}
imageX = 1;
}
return this.lineArray[imageX - 1] & 0xFF;
}
/**
* Copy a row of pixel values from the image.
*/
void nextPixels(byte[] buf, int lineWidth) {
if (image.depth == 8) {
System.arraycopy(image.data, imageY * image.bytesPerLine, buf, 0, lineWidth);
} else {
image.getPixels(0, imageY, lineWidth, buf, 0);
}
}
/**
* Output aCode to the output stream.
*/
void nextPutCode(int aCode) {
int codeToDo = aCode;
int codeBitsToDo = codeSize;
// Fill in the remainder of the current byte with the
// *high-order* bits of the code.
int c = codeToDo & MASK_TABLE[bitsLeft - 1];
currentByte = currentByte | (c << (8 - bitsLeft));
block[blockIndex] = (byte)currentByte;
codeBitsToDo -= bitsLeft;
if (codeBitsToDo < 1) {
// The whole code fit in the first byte, so we are done.
bitsLeft -= codeSize;
if (bitsLeft == 0) {
// We used the whole last byte, so get ready
// for the next one.
bitsLeft = 8;
blockIndex++;
if (blockIndex >= blockSize) {
writeBlock();
blockIndex = 1;
}
currentByte = 0;
}
return;
}
codeToDo = codeToDo >> bitsLeft;
// Fill in any remaining whole bytes (i.e. not the last one!)
blockIndex++;
if (blockIndex >= blockSize) {
writeBlock();
blockIndex = 1;
}
while (codeBitsToDo >= 8) {
currentByte = codeToDo & 0xFF;
block[blockIndex] = (byte)currentByte;
codeToDo = codeToDo >> 8;
codeBitsToDo -= 8;
blockIndex++;
if (blockIndex >= blockSize) {
writeBlock();
blockIndex = 1;
}
}
// Fill the *low-order* bits of the last byte with the remainder
bitsLeft = 8 - codeBitsToDo;
currentByte = codeToDo;
block[blockIndex] = (byte)currentByte;
}
/**
* Copy a row of pixel values to the image.
*/
void nextPutPixels(byte[] buf) {
if (image.depth == 8) {
// Slight optimization for depth = 8.
int start = line * image.bytesPerLine;
for (int i = 0; i < imageWidth; i++)
image.data[start + i] = buf[i];
} else {
image.setPixels(0, line, imageWidth, buf, 0);
}
if (interlaced) {
if (pass == 1) {
copyRow(buf, 7);
line += 8;
} else if (pass == 2) {
copyRow(buf, 3);
line += 8;
} else if (pass == 3) {
copyRow(buf, 1);
line += 4;
} else if (pass == 4) {
line += 2;
} else if (pass == 5) {
line += 0;
}
if (line >= imageHeight) {
pass++;
if (pass == 2) line = 4;
else if (pass == 3) line = 2;
else if (pass == 4) line = 1;
else if (pass == 5) line = 0;
if (pass < 5) {
if (loader.hasListeners()) {
ImageData imageCopy = (ImageData) image.clone();
loader.notifyListeners(
new ImageLoaderEvent(loader, imageCopy, pass - 2, false));
}
}
}
if (line >= imageHeight) line = 0;
} else {
line++;
}
}
/**
* Copy duplicate rows of pixel values to the image.
* This is to fill in rows if the image is interlaced.
*/
void copyRow(byte[] buf, int copies) {
for (int i = 1; i <= copies; i++) {
if (line + i < imageHeight) {
image.setPixels(0, line + i, imageWidth, buf, 0);
}
}
}
/**
* Read a block from the byte stream.
* Return the number of bytes read.
* Throw an exception if the block could not be read.
*/
int readBlock() {
int size = -1;
try {
size = inputStream.read();
if (size == -1) {
SWT.error(SWT.ERROR_INVALID_IMAGE);
}
block[0] = (byte)size;
size = inputStream.read(block, 1, size);
if (size == -1) {
SWT.error(SWT.ERROR_INVALID_IMAGE);
}
} catch (Exception e) {
SWT.error(SWT.ERROR_IO, e);
}
return size;
}
/**
* Write a block to the byte stream.
* Throw an exception if the block could not be written.
*/
void writeBlock() {
try {
outputStream.write(block, 0, (block[0] & 0xFF) + 1);
} catch (Exception e) {
SWT.error(SWT.ERROR_IO, e);
}
}
}