Hi I've been hoping to steal commons-compress's cleaner and faster LZW decompressor, and use it in commons-imaging for TIFF and GIF files, and I've finally managed to make a patch to that effect.
This requires moving LZWInputStream to a org.apache.commons.compress.compressors.lzw package and making it public. Any objections to this? Also imaging would have to depend on a SNAPSHOT of compress to be able to import LZWInputStream, at least until the next release. Which is when? I am attaching patches in case anyone wants to have a look, but I can commit them myself if there are no objections. Thank you Damjan
Index: src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java =================================================================== --- src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java (revision 1632885) +++ src/main/java/org/apache/commons/compress/archivers/zip/UnshrinkingInputStream.java (working copy) @@ -20,8 +20,9 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.ByteOrder; -import org.apache.commons.compress.compressors.z._internal_.InternalLZWInputStream; +import org.apache.commons.compress.compressors.lzw.LZWInputStream; /** * Input stream that decompresses ZIP method 1 (unshrinking). A variation of the LZW algorithm, with some twists. @@ -28,13 +29,13 @@ * @NotThreadSafe * @since 1.7 */ -class UnshrinkingInputStream extends InternalLZWInputStream { +class UnshrinkingInputStream extends LZWInputStream { private static final int MAX_CODE_SIZE = 13; private static final int MAX_TABLE_SIZE = 1 << MAX_CODE_SIZE; private final boolean[] isUsed; public UnshrinkingInputStream(InputStream inputStream) throws IOException { - super(inputStream); + super(inputStream, ByteOrder.LITTLE_ENDIAN); setClearCode(codeSize); initializeTables(MAX_CODE_SIZE); isUsed = new boolean[prefixes.length]; Index: src/main/java/org/apache/commons/compress/compressors/lzw/BitInputStream.java =================================================================== --- src/main/java/org/apache/commons/compress/compressors/lzw/BitInputStream.java (revision 0) +++ src/main/java/org/apache/commons/compress/compressors/lzw/BitInputStream.java (working copy) @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.commons.compress.compressors.lzw; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; + +public class BitInputStream implements Closeable { + private final InputStream in; + private final ByteOrder byteOrder; + private int bitsCached = 0; + private int bitsCachedSize = 0; + + public BitInputStream(final InputStream in, final ByteOrder byteOrder) { + this.in = in; + this.byteOrder = byteOrder; + } + + public void close() throws IOException { + in.close(); + } + + public void clearBitCache() { + bitsCached = 0; + bitsCachedSize = 0; + } + + public int readBits(final int count) throws IOException { + while (bitsCachedSize < count) { + final int nextByte = in.read(); + if (nextByte < 0) { + return nextByte; + } + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + bitsCached |= (nextByte << bitsCachedSize); + } else { + bitsCached <<= 8; + bitsCached |= nextByte; + } + bitsCachedSize += 8; + } + + final int mask = (1 << count) - 1; + final int bitsOut; + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + bitsOut = (bitsCached & mask); + bitsCached >>>= count; + } else { + bitsOut = (bitsCached >> (bitsCachedSize - count)) & mask; + } + bitsCachedSize -= count; + return bitsOut; + } +} Property changes on: src/main/java/org/apache/commons/compress/compressors/lzw/BitInputStream.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java =================================================================== --- src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java (revision 0) +++ src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java (working copy) @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.commons.compress.compressors.lzw; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; + +import org.apache.commons.compress.compressors.CompressorInputStream; + +/** + * <p>Generic LZW implementation. It is used internally for + * the Z compressor and the Unshrinking Zip file compression method, + * but may be useful for third-party projects in implementing their own LZW variations.</p> + * + * @NotThreadSafe + * @since 1.7 + */ +public abstract class LZWInputStream extends CompressorInputStream { + private final byte[] oneByte = new byte[1]; + + protected final BitInputStream in; + protected int clearCode = -1; + protected int codeSize = 9; + protected int previousCode = -1; + protected int tableSize = 0; + protected int[] prefixes; + protected byte[] characters; + private byte[] outputStack; + private int outputStackLocation; + + protected LZWInputStream(final InputStream inputStream, final ByteOrder byteOrder) { + this.in = new BitInputStream(inputStream, byteOrder); + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public int read() throws IOException { + int ret = read(oneByte); + if (ret < 0) { + return ret; + } + return 0xff & oneByte[0]; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int bytesRead = readFromStack(b, off, len); + while (len - bytesRead > 0) { + int result = decompressNextSymbol(); + if (result < 0) { + if (bytesRead > 0) { + count(bytesRead); + return bytesRead; + } + return result; + } + bytesRead += readFromStack(b, off + bytesRead, len - bytesRead); + } + count(bytesRead); + return bytesRead; + } + + /** + * Read the next code and expand it. + */ + protected abstract int decompressNextSymbol() throws IOException; + + /** + * Add a new entry to the dictionary. + */ + protected abstract int addEntry(int previousCode, byte character) + throws IOException; + + /** + * Sets the clear code based on the code size. + */ + protected void setClearCode(int codeSize) { + clearCode = (1 << (codeSize - 1)); + } + + /** + * Initializes the arrays based on the maximum code size. + */ + protected void initializeTables(int maxCodeSize) { + final int maxTableSize = 1 << maxCodeSize; + prefixes = new int[maxTableSize]; + characters = new byte[maxTableSize]; + outputStack = new byte[maxTableSize]; + outputStackLocation = maxTableSize; + final int max = 1 << 8; + for (int i = 0; i < max; i++) { + prefixes[i] = -1; + characters[i] = (byte) i; + } + } + + /** + * Reads the next code from the stream. + */ + protected int readNextCode() throws IOException { + return in.readBits(codeSize); + } + + /** + * Adds a new entry if the maximum table size hasn't been exceeded + * and returns the new index. + */ + protected int addEntry(int previousCode, byte character, int maxTableSize) { + if (tableSize < maxTableSize) { + prefixes[tableSize] = previousCode; + characters[tableSize] = character; + return tableSize++; + } + return -1; + } + + /** + * Add entry for repeat of previousCode we haven't added, yet. + */ + protected int addRepeatOfPreviousCode() throws IOException { + if (previousCode == -1) { + // can't have a repeat for the very first code + throw new IOException("The first code can't be a reference to its preceding code"); + } + byte firstCharacter = 0; + for (int last = previousCode; last >= 0; last = prefixes[last]) { + firstCharacter = characters[last]; + } + return addEntry(previousCode, firstCharacter); + } + + /** + * Expands the entry with index code to the output stack and may + * create a new entry + */ + protected int expandCodeToOutputStack(int code, boolean addedUnfinishedEntry) + throws IOException { + for (int entry = code; entry >= 0; entry = prefixes[entry]) { + outputStack[--outputStackLocation] = characters[entry]; + } + if (previousCode != -1 && !addedUnfinishedEntry) { + addEntry(previousCode, outputStack[outputStackLocation]); + } + previousCode = code; + return outputStackLocation; + } + + private int readFromStack(byte[] b, int off, int len) { + int remainingInStack = outputStack.length - outputStackLocation; + if (remainingInStack > 0) { + int maxLength = Math.min(remainingInStack, len); + System.arraycopy(outputStack, outputStackLocation, b, off, maxLength); + outputStackLocation += maxLength; + return maxLength; + } + return 0; + } +} Property changes on: src/main/java/org/apache/commons/compress/compressors/lzw/LZWInputStream.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: src/main/java/org/apache/commons/compress/compressors/lzw/package.html =================================================================== --- src/main/java/org/apache/commons/compress/compressors/lzw/package.html (revision 0) +++ src/main/java/org/apache/commons/compress/compressors/lzw/package.html (working copy) @@ -0,0 +1,23 @@ +<html> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> + <body> + <p>Generic LZW implementation.</p> + </body> +</html> Property changes on: src/main/java/org/apache/commons/compress/compressors/lzw/package.html ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java =================================================================== --- src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java (revision 1632885) +++ src/main/java/org/apache/commons/compress/compressors/z/ZCompressorInputStream.java (working copy) @@ -20,14 +20,16 @@ import java.io.IOException; import java.io.InputStream; -import org.apache.commons.compress.compressors.z._internal_.InternalLZWInputStream; +import java.nio.ByteOrder; +import org.apache.commons.compress.compressors.lzw.LZWInputStream; + /** * Input stream that decompresses .Z files. * @NotThreadSafe * @since 1.7 */ -public class ZCompressorInputStream extends InternalLZWInputStream { +public class ZCompressorInputStream extends LZWInputStream { private static final int MAGIC_1 = 0x1f; private static final int MAGIC_2 = 0x9d; private static final int BLOCK_MODE_MASK = 0x80; @@ -37,10 +39,10 @@ private long totalCodesRead = 0; public ZCompressorInputStream(InputStream inputStream) throws IOException { - super(inputStream); - int firstByte = in.read(); - int secondByte = in.read(); - int thirdByte = in.read(); + super(inputStream, ByteOrder.LITTLE_ENDIAN); + int firstByte = in.readBits(8); + int secondByte = in.readBits(8); + int thirdByte = in.readBits(8); if (firstByte != MAGIC_1 || secondByte != MAGIC_2 || thirdByte < 0) { throw new IOException("Input is not in .Z format"); } @@ -87,8 +89,7 @@ for (long i = 0; i < codeReadsToThrowAway; i++) { readNextCode(); } - bitsCached = 0; - bitsCachedSize = 0; + in.clearBitCache(); } /** Index: src/main/java/org/apache/commons/compress/compressors/z/_internal_/InternalLZWInputStream.java =================================================================== --- src/main/java/org/apache/commons/compress/compressors/z/_internal_/InternalLZWInputStream.java (revision 1632885) +++ src/main/java/org/apache/commons/compress/compressors/z/_internal_/InternalLZWInputStream.java (working copy) @@ -1,197 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.commons.compress.compressors.z._internal_; - -import java.io.IOException; -import java.io.InputStream; - -import org.apache.commons.compress.compressors.CompressorInputStream; - -/** - * <strong>This class is only public for technical reasons and is not - * part of Commons Compress' published API - it may change or - * disappear without warning.</strong> - * - * <p>Base-class for traditional Unix ".Z" compression and the - * Unshrinking method of ZIP archive.</p> - * - * @NotThreadSafe - * @since 1.7 - */ -public abstract class InternalLZWInputStream extends CompressorInputStream { - private final byte[] oneByte = new byte[1]; - - protected final InputStream in; - protected int clearCode = -1; - protected int codeSize = 9; - protected int bitsCached = 0; - protected int bitsCachedSize = 0; - protected int previousCode = -1; - protected int tableSize = 0; - protected int[] prefixes; - protected byte[] characters; - private byte[] outputStack; - private int outputStackLocation; - - protected InternalLZWInputStream(InputStream inputStream) { - this.in = inputStream; - } - - @Override - public void close() throws IOException { - in.close(); - } - - @Override - public int read() throws IOException { - int ret = read(oneByte); - if (ret < 0) { - return ret; - } - return 0xff & oneByte[0]; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int bytesRead = readFromStack(b, off, len); - while (len - bytesRead > 0) { - int result = decompressNextSymbol(); - if (result < 0) { - if (bytesRead > 0) { - count(bytesRead); - return bytesRead; - } - return result; - } - bytesRead += readFromStack(b, off + bytesRead, len - bytesRead); - } - count(bytesRead); - return bytesRead; - } - - /** - * Read the next code and expand it. - */ - protected abstract int decompressNextSymbol() throws IOException; - - /** - * Add a new entry to the dictionary. - */ - protected abstract int addEntry(int previousCode, byte character) - throws IOException; - - /** - * Sets the clear code based on the code size. - */ - protected void setClearCode(int codeSize) { - clearCode = (1 << (codeSize - 1)); - } - - /** - * Initializes the arrays based on the maximum code size. - */ - protected void initializeTables(int maxCodeSize) { - final int maxTableSize = 1 << maxCodeSize; - prefixes = new int[maxTableSize]; - characters = new byte[maxTableSize]; - outputStack = new byte[maxTableSize]; - outputStackLocation = maxTableSize; - final int max = 1 << 8; - for (int i = 0; i < max; i++) { - prefixes[i] = -1; - characters[i] = (byte) i; - } - } - - /** - * Reads the next code from the stream. - */ - protected int readNextCode() throws IOException { - while (bitsCachedSize < codeSize) { - final int nextByte = in.read(); - if (nextByte < 0) { - return nextByte; - } - bitsCached |= (nextByte << bitsCachedSize); - bitsCachedSize += 8; - } - final int mask = (1 << codeSize) - 1; - final int code = (bitsCached & mask); - bitsCached >>>= codeSize; - bitsCachedSize -= codeSize; - return code; - } - - /** - * Adds a new entry if the maximum table size hasn't been exceeded - * and returns the new index. - */ - protected int addEntry(int previousCode, byte character, int maxTableSize) { - if (tableSize < maxTableSize) { - final int index = tableSize; - prefixes[tableSize] = previousCode; - characters[tableSize] = character; - tableSize++; - return index; - } - return -1; - } - - /** - * Add entry for repeat of previousCode we haven't added, yet. - */ - protected int addRepeatOfPreviousCode() throws IOException { - if (previousCode == -1) { - // can't have a repeat for the very first code - throw new IOException("The first code can't be a reference to its preceding code"); - } - byte firstCharacter = 0; - for (int last = previousCode; last >= 0; last = prefixes[last]) { - firstCharacter = characters[last]; - } - return addEntry(previousCode, firstCharacter); - } - - /** - * Expands the entry with index code to the output stack and may - * create a new entry - */ - protected int expandCodeToOutputStack(int code, boolean addedUnfinishedEntry) - throws IOException { - for (int entry = code; entry >= 0; entry = prefixes[entry]) { - outputStack[--outputStackLocation] = characters[entry]; - } - if (previousCode != -1 && !addedUnfinishedEntry) { - addEntry(previousCode, outputStack[outputStackLocation]); - } - previousCode = code; - return outputStackLocation; - } - - private int readFromStack(byte[] b, int off, int len) { - int remainingInStack = outputStack.length - outputStackLocation; - if (remainingInStack > 0) { - int maxLength = Math.min(remainingInStack, len); - System.arraycopy(outputStack, outputStackLocation, b, off, maxLength); - outputStackLocation += maxLength; - return maxLength; - } - return 0; - } -} Index: src/main/java/org/apache/commons/compress/compressors/z/_internal_/package.html =================================================================== --- src/main/java/org/apache/commons/compress/compressors/z/_internal_/package.html (revision 1632885) +++ src/main/java/org/apache/commons/compress/compressors/z/_internal_/package.html (working copy) @@ -1,25 +0,0 @@ -<html> -<!-- - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ---> - <body> - <p><strong>This package is not part of Commons Compress' published - API. It may change without warning.</strong> Contains classes - used by Commons Compress internally.</p> - </body> -</html>
Index: pom.xml =================================================================== --- pom.xml (revision 1632877) +++ pom.xml (working copy) @@ -95,6 +95,29 @@ </configuration> </plugin> <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <minimizeJar>true</minimizeJar> + <relocations> + <relocation> + <pattern>org.apache.commons.compress</pattern> + <shadedPattern>org.apache.commons.compress.SHADED</shadedPattern> + </relocation> + </relocations> + </configuration> + </execution> + </executions> + </plugin> + <plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> @@ -207,6 +230,12 @@ <version>2.4</version> <scope>test</scope> </dependency> + <dependency> + <!-- maven-shade-plugin takes it out later: --> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + <version>1.10-SNAPSHOT</version> + </dependency> </dependencies> <reporting> Index: src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java =================================================================== --- src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java (revision 1632875) +++ src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java (working copy) @@ -313,7 +313,11 @@ } public static byte[] getStreamBytes(final InputStream is) throws IOException { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); + return getStreamBytes(is, 32); + } + + public static byte[] getStreamBytes(final InputStream is, int expectedSize) throws IOException { + final ByteArrayOutputStream os = new ByteArrayOutputStream(expectedSize); copyStreamToStream(is, os); return os.toByteArray(); } Index: src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java =================================================================== --- src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java (revision 1632875) +++ src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java (working copy) @@ -37,6 +37,7 @@ import org.apache.commons.imaging.ImageParser; import org.apache.commons.imaging.ImageReadException; import org.apache.commons.imaging.ImageWriteException; +import org.apache.commons.imaging.common.BinaryFunctions; import org.apache.commons.imaging.common.BinaryOutputStream; import org.apache.commons.imaging.common.IImageMetadata; import org.apache.commons.imaging.common.ImageBuilder; @@ -47,6 +48,8 @@ import org.apache.commons.imaging.palette.PaletteFactory; import org.apache.commons.imaging.util.IoUtils; +import sun.net.www.content.image.gif; + import static org.apache.commons.imaging.ImagingConstants.*; import static org.apache.commons.imaging.common.BinaryFunctions.*; @@ -383,9 +386,12 @@ final InputStream bais = new ByteArrayInputStream(bytes); final int size = imageWidth * imageHeight; - final MyLzwDecompressor myLzwDecompressor = new MyLzwDecompressor( - lzwMinimumCodeSize, ByteOrder.LITTLE_ENDIAN); - imageData = myLzwDecompressor.decompress(bais, size); + GifLzwInputStream gifLzwInputStream = new GifLzwInputStream(bais, lzwMinimumCodeSize); + try { + imageData = BinaryFunctions.getStreamBytes(gifLzwInputStream, size); + } finally { + IoUtils.closeQuietly(false, gifLzwInputStream); + } } else { final int LZWMinimumCodeSize = is.read(); if (getDebug()) { Index: src/main/java/org/apache/commons/imaging/formats/gif/GifLzwInputStream.java =================================================================== --- src/main/java/org/apache/commons/imaging/formats/gif/GifLzwInputStream.java (revision 0) +++ src/main/java/org/apache/commons/imaging/formats/gif/GifLzwInputStream.java (working copy) @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.gif; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import org.apache.commons.compress.compressors.lzw.LZWInputStream; + +public class GifLzwInputStream extends LZWInputStream { + private static final int MAX_CODE_SIZE = 12; + private static final int MAX_TABLE_SIZE = 1 << MAX_CODE_SIZE; + + private final int initialCodeSize; + private final int eoiCode; + + public GifLzwInputStream(final InputStream in, final int initialCodeSize) { + super(in, ByteOrder.LITTLE_ENDIAN); + initializeTables(MAX_CODE_SIZE); + this.initialCodeSize = initialCodeSize + 1; + codeSize = initialCodeSize + 1; + clearCode = 1 << initialCodeSize; + eoiCode = clearCode + 1; + tableSize = eoiCode + 1; + } + + private void clearEntries() { + tableSize = eoiCode + 1; + } + + @Override + protected int addEntry(int previousCode, byte character) throws IOException { + final int maxTableSize = 1 << codeSize; + int r = addEntry(previousCode, character, maxTableSize); + if (tableSize == maxTableSize && codeSize < MAX_CODE_SIZE) { + codeSize++; + } + return r; + } + + @Override + protected int decompressNextSymbol() throws IOException { + // + // table entry table entry + // _____________ _____ + // table entry / \ / \ + // ____________/ \ \ + // / / \ / \ \ + // +---+---+---+---+---+---+---+---+---+---+ + // | . | . | . | . | . | . | . | . | . | . | + // +---+---+---+---+---+---+---+---+---+---+ + // |<--------->|<------------->|<----->|<->| + // symbol symbol symbol symbol + // + final int code = readNextCode(); + if (code < 0) { + return -1; + } else if (code == clearCode) { + clearEntries(); + codeSize = initialCodeSize; + previousCode = -1; + return 0; + } else if (code == eoiCode) { + return -1; + } else { + boolean addedUnfinishedEntry = false; + if (code >= tableSize) { + addRepeatOfPreviousCode(); + addedUnfinishedEntry = true; + } + return expandCodeToOutputStack(code, addedUnfinishedEntry); + } + } +} Property changes on: src/main/java/org/apache/commons/imaging/formats/gif/GifLzwInputStream.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java =================================================================== --- src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java (revision 1632875) +++ src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReader.java (working copy) @@ -16,25 +16,34 @@ */ package org.apache.commons.imaging.formats.tiff.datareaders; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_2D; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_FILL; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T4_OPTIONS_UNCOMPRESSED_MODE; +import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE; + import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.ByteOrder; import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.common.BinaryFunctions; import org.apache.commons.imaging.common.ImageBuilder; import org.apache.commons.imaging.common.PackBits; import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression; -import org.apache.commons.imaging.common.mylzw.MyLzwDecompressor; import org.apache.commons.imaging.formats.tiff.TiffDirectory; import org.apache.commons.imaging.formats.tiff.TiffField; import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; +import org.apache.commons.imaging.util.IoUtils; -import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.*; - public abstract class DataReader { protected final TiffDirectory directory; protected final PhotometricInterpreter photometricInterpreter; @@ -181,15 +190,12 @@ case TIFF_COMPRESSION_LZW: // LZW { final InputStream is = new ByteArrayInputStream(compressed); - - final int lzwMinimumCodeSize = 8; - - final MyLzwDecompressor myLzwDecompressor = new MyLzwDecompressor( - lzwMinimumCodeSize, ByteOrder.BIG_ENDIAN); - - myLzwDecompressor.setTiffLZWMode(); - - return myLzwDecompressor.decompress(is, expectedSize); + final TiffLzwInputStream tiffLzwInputStream = new TiffLzwInputStream(is); + try { + return BinaryFunctions.getStreamBytes(tiffLzwInputStream, expectedSize); + } finally { + IoUtils.closeQuietly(false, tiffLzwInputStream); + } } case TIFF_COMPRESSION_PACKBITS: // Packbits Index: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/TiffLzwInputStream.java =================================================================== --- src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/TiffLzwInputStream.java (revision 0) +++ src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/TiffLzwInputStream.java (working copy) @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.imaging.formats.tiff.datareaders; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import org.apache.commons.compress.compressors.lzw.LZWInputStream; + +public class TiffLzwInputStream extends LZWInputStream { + private static final int MAX_CODE_SIZE = 12; + private static final int MAX_TABLE_SIZE = 1 << MAX_CODE_SIZE; + + private final int eoiCode; + + public TiffLzwInputStream(final InputStream in) { + super(in, ByteOrder.BIG_ENDIAN); + initializeTables(MAX_CODE_SIZE); + setClearCode(codeSize); + eoiCode = clearCode + 1; + tableSize = eoiCode + 1; + } + + private void clearEntries() { + tableSize = eoiCode + 1; + } + + @Override + protected int addEntry(int previousCode, byte character) throws IOException { + final int maxTableSize = 1 << codeSize; + int r = addEntry(previousCode, character, maxTableSize); + // early change: + if (tableSize == (maxTableSize - 1) && codeSize < MAX_CODE_SIZE) { + codeSize++; + } + return r; + } + + @Override + protected int decompressNextSymbol() throws IOException { + // + // table entry table entry + // _____________ _____ + // table entry / \ / \ + // ____________/ \ \ + // / / \ / \ \ + // +---+---+---+---+---+---+---+---+---+---+ + // | . | . | . | . | . | . | . | . | . | . | + // +---+---+---+---+---+---+---+---+---+---+ + // |<--------->|<------------->|<----->|<->| + // symbol symbol symbol symbol + // + final int code = readNextCode(); + if (code < 0) { + return -1; + } else if (code == clearCode) { + clearEntries(); + codeSize = 9; + previousCode = -1; + return 0; + } else if (code == eoiCode) { + return -1; + } else { + boolean addedUnfinishedEntry = false; + // Unbelievably, TIFF *really* needs us to treat every out of range code as + // the "concatenation of previousCode with its own first character" case!!! + if (code >= tableSize) { + addRepeatOfPreviousCode(); + addedUnfinishedEntry = true; + } + return expandCodeToOutputStack(code, addedUnfinishedEntry); + } + } +} Property changes on: src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/TiffLzwInputStream.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: src/test/java/org/apache/commons/imaging/formats/tiff/TiffRoundtripTest.java =================================================================== --- src/test/java/org/apache/commons/imaging/formats/tiff/TiffRoundtripTest.java (revision 1632875) +++ src/test/java/org/apache/commons/imaging/formats/tiff/TiffRoundtripTest.java (working copy) @@ -65,8 +65,21 @@ params); final BufferedImage image2 = Imaging.getBufferedImage(tempFile); assertNotNull(image2); + compareImages(image, image2); } } } + + private void compareImages(BufferedImage image1, BufferedImage image2) { + int[] row1 = new int[image1.getWidth()]; + int[] row2 = new int[image1.getWidth()]; + for (int y = 0; y < image1.getHeight(); y++) { + image1.getRGB(0, y, image1.getWidth(), 1, row1, 0, image1.getWidth()); + image2.getRGB(0, y, image1.getWidth(), 1, row2, 0, image1.getWidth()); + for (int x = 0; x < row1.length; x++) { + assertEquals(row1[x], row2[x]); + } + } + } }
--------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org For additional commands, e-mail: dev-h...@commons.apache.org