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

Reply via email to