diff dwt/internal/image/LZWCodec.d @ 13:3d9bbe0a83a0

FileFormats
author Frank Benoit <benoit@tionex.de>
date Sun, 06 Jan 2008 22:54:14 +0100
parents
children fc2b263b8a3f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/LZWCodec.d	Sun Jan 06 22:54:14 2008 +0100
@@ -0,0 +1,485 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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
+ *******************************************************************************/
+module dwt.internal.image.LZWCodec;
+
+
+import dwt.internal.image.LZWNode;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.LEDataOutputStream;
+import dwt.SWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.ImageLoaderEvent;
+
+import tango.core.Exception;
+
+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;
+	bool interlaced;
+	static final int[] MASK_TABLE = [
+		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] = cast(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] = cast(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, bool 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] = cast(byte)(blockIndex - 1); // Nothing in last byte
+	} else {
+		block[0] = cast(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();
+	bool 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] = cast(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) {
+        int start = imageY * image.bytesPerLine;
+        buf[ 0 .. lineWidth ] = image.data[ start .. start + 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] = cast(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] = cast(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] = cast(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 = cast(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] = cast(byte)size;
+		size = inputStream.read(block, 1, size);
+		if (size == -1) {
+			SWT.error(SWT.ERROR_INVALID_IMAGE);
+		}
+	} catch (TracedException 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 (TracedException e) {
+		SWT.error(SWT.ERROR_IO, e);
+	}
+}
+}