diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java index a5a4e333..47afa666 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java @@ -33,21 +33,22 @@ import com.twelvemonkeys.io.enc.Encoder; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.Map; -import java.util.TreeMap; - -import static com.twelvemonkeys.imageio.plugins.tiff.LZWDecoder.LZWString; +import java.util.Arrays; /** * LZWEncoder + *

+ * Inspired by LZWTreeEncoder by Wen Yu and the + * algorithm described by Bob Montgomery + * which + * "[...] uses a tree method to search if a new string is already in the table, + * which is much simpler, faster, and easier to understand than hashing." * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: LZWEncoder.java,v 1.0 02.12.13 14:13 haraldk Exp$ */ final class LZWEncoder implements Encoder { - // TODO: Consider extracting LZWStringTable from LZWDecoder - /** Clear: Re-initialize tables. */ static final int CLEAR_CODE = 256; /** End of Information. */ @@ -58,16 +59,18 @@ final class LZWEncoder implements Encoder { private static final int TABLE_SIZE = 1 << MAX_BITS; - private final LZWString[] table = new LZWString[TABLE_SIZE]; - private final Map reverseTable = new TreeMap<>(); // This is foobar -// private final Map reverseTable = new HashMap<>(TABLE_SIZE); // This is foobar - private int tableLength; - LZWString omega = LZWString.EMPTY; + // A child is made up of a parent (or prefix) code plus a suffix byte + // and siblings are strings with a common parent(or prefix) and different + // suffix bytes + private final short[] CHILDREN = new short[TABLE_SIZE]; + private final short[] SIBLINGS = new short[TABLE_SIZE]; + private final short[] SUFFIXES = new short[TABLE_SIZE]; - int bitsPerCode; - private int oldCode = CLEAR_CODE; - private int maxCode; - int bitMask; + // Initial setup + private int parent = -1; + private int bitsPerCode = MIN_BITS; + private int nextValidCode = EOI_CODE + 1; + private int maxCode = maxValue(bitsPerCode); // Buffer for partial codes private int bits = 0; @@ -76,120 +79,116 @@ final class LZWEncoder implements Encoder { // Keep track of how many bytes we will write, to make sure we write EOI at correct position private long remaining; - protected LZWEncoder(final int length) { - this.remaining = length; - - // First 258 entries of table is always fixed - for (int i = 0; i < 256; i++) { - table[i] = new LZWString((byte) i); - } - - init(); - } - - private void init() { - tableLength = 258; - bitsPerCode = MIN_BITS; - bitMask = bitmaskFor(bitsPerCode); - maxCode = maxCode(); - reverseTable.clear(); + LZWEncoder(final long length) { + remaining = length; } + @Override public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException { -// InitializeStringTable(); -// WriteCode(ClearCode); -// Ω = the empty string; -// for each character in the strip { -// K = GetNextCharacter(); -// if Ω+K is in the string table { -// Ω = Ω+K;/* string concatenation */ -// } -// else{ -// WriteCode (CodeFromString( Ω)); -// AddTableEntry(Ω+K); -// Ω=K; -// } }/*end of for loop*/ -// WriteCode (CodeFromString(Ω)); -// WriteCode (EndOfInformation); + encodeBytes(stream, buffer); - if (remaining < 0) { - throw new IOException("Write past end of stream"); - } - - // TODO: Write 9 bit clear code ONLY first time! - if (oldCode == CLEAR_CODE) { - writeCode(stream, CLEAR_CODE); - } - - int len = buffer.remaining(); - - while (buffer.hasRemaining()) { - byte k = buffer.get(); - - LZWString string = omega.concatenate(k); - - int tableIndex = isInTable(string); - if (tableIndex >= 0) { - omega = string; - oldCode = tableIndex; - } - else { - writeCode(stream, oldCode); - addStringToTable(string); - oldCode = k & 0xff; - omega = table[k & 0xff]; - - // Handle table (almost) full - if (tableLength >= TABLE_SIZE - 2) { - writeCode(stream, CLEAR_CODE); - init(); - } - } - } - - remaining -= len; - - // Write EOI when er are done (the API isn't very supportive of this) if (remaining <= 0) { - writeCode(stream, oldCode); + // Write EOI when er are done (the API isn't very supportive of this at the moment) + writeCode(stream, parent); writeCode(stream, EOI_CODE); + + // Flush partial codes by writing 0 pad if (bitPos > 0) { writeCode(stream, 0); } } } - private int isInTable(final LZWString string) { - if (string.length == 1) { - return string.value & 0xff; + void encodeBytes(final OutputStream stream, final ByteBuffer buffer) throws IOException { + int length = buffer.remaining(); + + if (length == 0) { + return; } - Integer index = reverseTable.get(string); - return index != null ? index : -1; + if (parent == -1) { + // Init stream + writeCode(stream, CLEAR_CODE); + parent = buffer.get() & 0xff; + } + + while (buffer.hasRemaining()) { + int value = buffer.get() & 0xff; + int child = CHILDREN[parent]; + + if (child > 0) { + if (SUFFIXES[child] == value) { + parent = child; + } + else { + int sibling = child; + + while (true) { + if (SIBLINGS[sibling] > 0) { + sibling = SIBLINGS[sibling]; + + if (SUFFIXES[sibling] == value) { + parent = sibling; + break; + } + } + else { + SIBLINGS[sibling] = (short) nextValidCode; + SUFFIXES[nextValidCode] = (short) value; + writeCode(stream, parent); + parent = value; + nextValidCode++; + + increaseCodeSizeOrResetIfNeeded(stream); + + break; + } + } + } + } + else { + CHILDREN[parent] = (short) nextValidCode; + SUFFIXES[nextValidCode] = (short) value; + writeCode(stream, parent); + parent = value; + nextValidCode++; + + increaseCodeSizeOrResetIfNeeded(stream); + } + } + + remaining -= length; } - private int addStringToTable(final LZWString string) { - final int index = tableLength++; - table[index] = string; - reverseTable.put(string, index); + private void increaseCodeSizeOrResetIfNeeded(final OutputStream stream) throws IOException { + if (nextValidCode > maxCode) { + if (bitsPerCode == MAX_BITS) { + // Reset stream by writing Clear code + writeCode(stream, CLEAR_CODE); - if (tableLength > maxCode) { - bitsPerCode++; - - if (bitsPerCode > MAX_BITS) { - throw new IllegalStateException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS)); + // Reset tables + resetTables(); + } + else { + // Increase code size + bitsPerCode++; + maxCode = maxValue(bitsPerCode); } - - bitMask = bitmaskFor(bitsPerCode); - maxCode = maxCode(); } + } - return index; + private void resetTables() { + Arrays.fill(CHILDREN, (short) 0); + Arrays.fill(SIBLINGS, (short) 0); + + bitsPerCode = MIN_BITS; + maxCode = maxValue(bitsPerCode); + nextValidCode = EOI_CODE + 1; } private void writeCode(final OutputStream stream, final int code) throws IOException { // System.err.printf("LZWEncoder.writeCode: 0x%04x\n", code); - bits = (bits << bitsPerCode) | (code & bitMask); + bits = (bits << bitsPerCode) | (code & maxCode); bitPos += bitsPerCode; while (bitPos >= 8) { @@ -202,11 +201,11 @@ final class LZWEncoder implements Encoder { bits &= bitmaskFor(bitPos); } - private static int bitmaskFor(final int bits) { - return (1 << bits) - 1; + private static int maxValue(final int codeLen) { + return (1 << codeLen) - 1; } - protected int maxCode() { - return bitMask; + private static int bitmaskFor(final int bits) { + return maxValue(bits); } -} +} \ No newline at end of file