mirror of
https://github.com/AppleCommander/ShrinkItArchive.git
synced 2024-06-09 23:29:34 +00:00
LZW decompression stream. Tests aren't quite right yet.
This commit is contained in:
parent
264a5f6e53
commit
005b1f4e07
96
src/com/webcodepro/shrinkit/io/LzwOutputStream.java
Normal file
96
src/com/webcodepro/shrinkit/io/LzwOutputStream.java
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package com.webcodepro.shrinkit.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.webcodepro.shrinkit.CRC16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the generic Shrinkit LZW compression algorithm.
|
||||||
|
* It does not deal with the vagaries of the LZW/1 and LZW/2 data streams.
|
||||||
|
*
|
||||||
|
* @author robgreene@users.sourceforge.net
|
||||||
|
*/
|
||||||
|
public class LzwOutputStream extends OutputStream {
|
||||||
|
private BitOutputStream os;
|
||||||
|
private Map<ByteArray,Integer> dictionary = new HashMap<ByteArray,Integer>();
|
||||||
|
private int[] w = new int[0];
|
||||||
|
private int nextCode = 0x101;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This simple class can be used as a key into a Map.
|
||||||
|
*
|
||||||
|
* @author robgreene@users.sourceforge.net
|
||||||
|
*/
|
||||||
|
private class ByteArray {
|
||||||
|
/** Data being managed. */
|
||||||
|
private int[] data;
|
||||||
|
/** The computed hash code -- CRC-16 for lack of imagination. */
|
||||||
|
private int hashCode;
|
||||||
|
|
||||||
|
public ByteArray(int d) {
|
||||||
|
this(new int[] { d });
|
||||||
|
}
|
||||||
|
public ByteArray(int[] data) {
|
||||||
|
this.data = data;
|
||||||
|
CRC16 crc = new CRC16();
|
||||||
|
for (int b : data) crc.update(b);
|
||||||
|
hashCode = (int)crc.getValue();
|
||||||
|
}
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
ByteArray ba = (ByteArray)obj;
|
||||||
|
if (data.length != ba.data.length) return false;
|
||||||
|
for (int i=0; i<data.length; i++) {
|
||||||
|
if (data[i] != ba.data[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public int hashCode() {
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LzwOutputStream(BitOutputStream os) {
|
||||||
|
this.os = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int c) throws IOException {
|
||||||
|
if (dictionary.isEmpty()) {
|
||||||
|
for (int i=0; i<256; i++) dictionary.put(new ByteArray(i), i);
|
||||||
|
dictionary.put(new ByteArray(0x100), null); // just to mark its spot
|
||||||
|
}
|
||||||
|
c &= 0xff;
|
||||||
|
int[] wc = new int[w.length + 1];
|
||||||
|
if (w.length > 0) System.arraycopy(w, 0, wc, 0, w.length);
|
||||||
|
wc[wc.length-1]= c;
|
||||||
|
if (dictionary.containsKey(new ByteArray(wc))) {
|
||||||
|
w = wc;
|
||||||
|
} else {
|
||||||
|
dictionary.put(new ByteArray(wc), nextCode++);
|
||||||
|
os.write(dictionary.get(new ByteArray(w)));
|
||||||
|
w = new int[] { c };
|
||||||
|
}
|
||||||
|
// Exclusive-OR the current bitmask against the new dictionary size -- if all bits are
|
||||||
|
// on, we'll get 0. (That is, all 9 bits on is 0x01ff exclusive or bit mask of 0x01ff
|
||||||
|
// yields 0x0000.) This tells us we need to increase the number of bits we're writing
|
||||||
|
// to the bit stream.
|
||||||
|
if ((dictionary.size() ^ os.getBitMask()) == 0) {
|
||||||
|
os.increaseRequestedNumberOfBits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
os.write(dictionary.get(new ByteArray(w)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
flush();
|
||||||
|
os.flush();
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
package com.webcodepro.shrinkit.io;
|
package com.webcodepro.shrinkit.io;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exercise the LZW encoder and decoders.
|
* Exercise the LZW encoder and decoders.
|
||||||
*
|
*
|
||||||
* @author robgreene@users.sourceforge.net
|
* @author robgreene@users.sourceforge.net
|
||||||
*/
|
*/
|
||||||
public class LzwTest extends TestCase {
|
public class LzwTest extends TestCaseHelper {
|
||||||
public void testLzwDecoder() throws IOException {
|
public void testLzwDecoder() throws IOException {
|
||||||
LzwInputStream is = new LzwInputStream(new BitInputStream(new ByteArrayInputStream(getHgrColorsLzw1()), 9));
|
LzwInputStream is = new LzwInputStream(new BitInputStream(new ByteArrayInputStream(getHgrColorsLzw1()), 9));
|
||||||
int[] expected = getHgrColorsUncompressed();
|
int[] expected = getHgrColorsUncompressed();
|
||||||
|
@ -19,10 +17,19 @@ public class LzwTest extends TestCase {
|
||||||
int[] actual = new int[expected.length];
|
int[] actual = new int[expected.length];
|
||||||
for (int i=0; i<actual.length; i++) actual[i] = is.read();
|
for (int i=0; i<actual.length; i++) actual[i] = is.read();
|
||||||
|
|
||||||
assertEquals("Expecting end of stream", -1, is.read());
|
assertEquals("Expecting end of stream", -1, is.read());
|
||||||
for (int i=0; i<expected.length; i++) {
|
assertEquals(expected, actual);
|
||||||
assertEquals("Testing value #" + i, expected[i], actual[i]);
|
}
|
||||||
}
|
public void testLzwEncoder() throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
LzwOutputStream os = new LzwOutputStream(new BitOutputStream(baos, 9));
|
||||||
|
byte[] expected = getHgrColorsLzw1();
|
||||||
|
|
||||||
|
os.write(asBytes(getHgrColorsUncompressed()));
|
||||||
|
os.close();
|
||||||
|
byte[] actual = baos.toByteArray();
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLzwDecoder2() throws IOException {
|
public void testLzwDecoder2() throws IOException {
|
||||||
|
@ -51,6 +58,17 @@ public class LzwTest extends TestCase {
|
||||||
}
|
}
|
||||||
System.out.printf("** END **");
|
System.out.printf("** END **");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected byte[] asBytes(int[] source) {
|
||||||
|
byte[] array = new byte[source.length];
|
||||||
|
for (int i=0; i<source.length; i++) array[i] = (byte)(source[i] & 0xff);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
protected int[] asInts(byte[] source) {
|
||||||
|
int[] array = new int[source.length];
|
||||||
|
for (int i=0; i<source.length; i++) array[i] = source[i] & 0xff;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
protected byte[] getHgrColorsLzw1() {
|
protected byte[] getHgrColorsLzw1() {
|
||||||
return new byte[] {
|
return new byte[] {
|
||||||
|
@ -71,7 +89,7 @@ public class LzwTest extends TestCase {
|
||||||
0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff,
|
0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff,
|
||||||
0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00,
|
0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00,
|
||||||
0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb,
|
0xff, 0xdb, 0x00, 0xff, 0xdb, 0x00, 0xff, 0xdb,
|
||||||
0x00, 0xe7
|
0x00, 0xff, 0xdb, 0x00, 0xe7
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.webcodepro.shrinkit.io;
|
package com.webcodepro.shrinkit.io;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,9 +13,34 @@ public abstract class TestCaseHelper extends TestCase {
|
||||||
* Compare two byte arrays.
|
* Compare two byte arrays.
|
||||||
*/
|
*/
|
||||||
public void assertEquals(byte[] expected, byte[] actual) {
|
public void assertEquals(byte[] expected, byte[] actual) {
|
||||||
assertEquals("Length mismatch", expected.length, actual.length);
|
try {
|
||||||
for (int i=0; i<expected.length; i++) {
|
assertEquals("Length mismatch", expected.length, actual.length);
|
||||||
assertEquals("Byte mismatch at offset " + i, expected[i], actual[i]);
|
for (int i=0; i<expected.length; i++) {
|
||||||
|
assertEquals("Byte mismatch at offset " + i, expected[i], actual[i]);
|
||||||
|
}
|
||||||
|
} catch (AssertionFailedError err) {
|
||||||
|
int minvalue = Math.min(expected.length, actual.length);
|
||||||
|
for (int i=0; i<minvalue; i++) {
|
||||||
|
assertEquals(err.getMessage() + " -- Byte mismatch at offset " + i, expected[i], actual[i]);
|
||||||
|
}
|
||||||
|
fail(err.getMessage() + " -- all bytes that could be compared match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Compare two int arrays.
|
||||||
|
*/
|
||||||
|
public void assertEquals(int[] expected, int[] actual) {
|
||||||
|
try {
|
||||||
|
assertEquals("Length mismatch", expected.length, actual.length);
|
||||||
|
for (int i=0; i<expected.length; i++) {
|
||||||
|
assertEquals("int mismatch at offset " + i, expected[i], actual[i]);
|
||||||
|
}
|
||||||
|
} catch (AssertionFailedError err) {
|
||||||
|
int minvalue = Math.min(expected.length, actual.length);
|
||||||
|
for (int i=0; i<minvalue; i++) {
|
||||||
|
assertEquals(err.getMessage() + " -- Byte mismatch at offset " + i, expected[i], actual[i]);
|
||||||
|
}
|
||||||
|
fail(err.getMessage() + " -- all bytes that could be compared match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user