This adds NuFX LZW/1 decompression support.

This commit is contained in:
Robert Greene 2008-06-23 04:03:42 +00:00
parent 0511fbfce5
commit 58af449378
5 changed files with 217 additions and 13 deletions

View File

@ -0,0 +1,146 @@
package com.webcodepro.shrinkit.io;
import java.io.IOException;
import java.io.InputStream;
import com.webcodepro.shrinkit.CRC16;
/**
* The <code>Lzw1InputStream</code> reads a data fork or
* resource fork written in the NuFX LZW/1 format.
* <p>
* The layout of the LZW/1 data is as follows:
* <table border="0">
* <tr>
* <th colspan="3">"Fork" Header</th>
* </tr><tr>
* <td>+0</td>
* <td>Word</td>
* <td>CRC-16 of the uncompressed data within the thread</td>
* </tr><tr>
* <td>+2</td>
* <td>Byte</td>
* <td>Low-level volume number use to format 5.25" disks</td>
* </tr><tr>
* <td>+3</td>
* <td>Byte</td>
* <td>RLE character used to decode this thread</td>
* </tr><tr>
* <th colspan="3">Each subsequent 4K chunk of data</th>
* </tr><tr>
* <td>+0</td>
* <td>Word</td>
* <td>Length after RLE compression (if RLE is not used, length
* will be 4096</td>
* </tr><tr>
* <td>+2</td>
* <td>Byte</td>
* <td>A $01 indicates LZW applied to this chunk; $00 that LZW
* <b>was not</b> applied to this chunk</td>
* </tr>
* <table>
* <p>
* Note that the LZW string table is <em>cleared</em> after
* every chunk.
*
* @author robgreene@users.sourceforge.net
*/
public class Lzw1InputStream extends InputStream {
/** This is the raw data stream with all markers and compressed data. */
private LittleEndianByteInputStream dataStream;
/** Used for an LZW-only <code>InputStream</code>. */
private LzwInputStream lzwStream;
/** Used for an RLE-only <code>InputStream</code>. */
private RleInputStream rleStream;
/** Used for an LZW+RLE <code>InputStream</code>. */
private InputStream lzwRleStream;
/** This is the generic decompression stream from which we read. */
private InputStream decompressionStream;
/** Counts the number of bytes in the 4096 byte chunk. */
private int bytesLeftInChunk;
/** This is the CRC-16 for the uncompressed fork. */
private int givenCrc = -1;
/** This is the volume number for 5.25" disks. */
private int volumeNumber;
/** This is the RLE character to use. */
private int rleCharacter;
/** Used to track the CRC of data we've extracted */
private CRC16 dataCrc = new CRC16();
/**
* Create the LZW/1 input stream.
*/
public Lzw1InputStream(LittleEndianByteInputStream dataStream) {
this.dataStream = dataStream;
}
/**
* Read the next byte in the decompressed data stream.
*/
public int read() throws IOException {
if (givenCrc == -1) { // read the data or resource fork header
givenCrc = dataStream.readWord();
volumeNumber = dataStream.readByte();
rleCharacter = dataStream.readByte();
lzwStream = new LzwInputStream(new BitInputStream(dataStream, 9));
rleStream = new RleInputStream(dataStream);
lzwRleStream = new RleInputStream(lzwStream);
}
if (bytesLeftInChunk == 0) { // read the chunk header
bytesLeftInChunk = 4096; // NuFX always reads 4096 bytes
lzwStream.clearDictionary(); // Always clear dictionary
int length = dataStream.readWord();
int lzwFlag = dataStream.readByte();
int flag = lzwFlag + (length == 4096 ? 0 : 2);
switch (flag) {
case 0: decompressionStream = dataStream;
break;
case 1: decompressionStream = lzwStream;
break;
case 2: decompressionStream = rleStream;
break;
case 3: decompressionStream = lzwRleStream;
break;
default: throw new IOException("Unknown type of decompression, flag = " + flag);
}
}
// Now we can read a data byte
int b = decompressionStream.read();
dataCrc.update(b);
return b;
}
/**
* Indicates if the computed CRC matches the CRC given in the data stream.
*/
public boolean isCrcValid() {
return givenCrc == dataCrc.getValue();
}
// GENERATED CODE
public int getGivenCrc() {
return givenCrc;
}
public void setGivenCrc(int givenCrc) {
this.givenCrc = givenCrc;
}
public int getVolumeNumber() {
return volumeNumber;
}
public void setVolumeNumber(int volumeNumber) {
this.volumeNumber = volumeNumber;
}
public int getRleCharacter() {
return rleCharacter;
}
public void setRleCharacter(int rleCharacter) {
this.rleCharacter = rleCharacter;
}
public CRC16 getDataCrc() {
return dataCrc;
}
public void setDataCrc(CRC16 dataCrc) {
this.dataCrc = dataCrc;
}
}

View File

@ -14,7 +14,7 @@ import java.io.InputStream;
* @author robgreene@users.sourceforge.net
*/
public class RleInputStream extends InputStream {
private LittleEndianByteInputStream bs;
private InputStream bs;
private int escapeChar;
private int repeatedByte;
private int numBytes = -1;
@ -22,13 +22,13 @@ public class RleInputStream extends InputStream {
/**
* Create an RLE input stream with the default marker byte.
*/
public RleInputStream(LittleEndianByteInputStream bs) {
public RleInputStream(InputStream bs) {
this(bs, 0xdb);
}
/**
* Create an RLE input stream with the specified marker byte.
*/
public RleInputStream(LittleEndianByteInputStream bs, int escapeChar) {
public RleInputStream(InputStream bs, int escapeChar) {
this.bs = bs;
this.escapeChar = escapeChar;
}

View File

@ -0,0 +1,47 @@
package com.webcodepro.shrinkit.io;
import java.io.IOException;
/**
* Test some LZW/1 format streams.
*
* @author robgreene@users.sourceforge.net
*/
public class Lzw1Test extends TestCaseHelper {
public void testTextFile() throws IOException {
Lzw1InputStream is = new Lzw1InputStream(new LittleEndianByteInputStream(getTextFileLzw1StreamData()));
byte[] expected = getTextFileData();
byte[] actual = new byte[expected.length];
is.read(actual);
assertEquals(expected, actual);
assertTrue(is.isCrcValid());
}
private byte[] getTextFileLzw1StreamData() {
return new byte[] {
(byte)0xCA, 0x42, 0x00, (byte)0xDB, (byte)0xB7, 0x00, 0x01, 0x54,
(byte)0x90, 0x24, (byte)0x99, 0x02, 0x62, 0x20, (byte)0x88, (byte)0x80,
0x45, 0x40, 0x5C, 0x09, (byte)0x92, 0x45, 0x61, (byte)0xC2,
(byte)0x85, 0x53, (byte)0x90, (byte)0x80, 0x78, 0x52, 0x45, 0x0A,
(byte)0x88, 0x21, 0x4C, (byte)0x9E, 0x20, (byte)0x9C, (byte)0xC2, 0x42,
0x61, (byte)0x90, (byte)0x88, 0x13, 0x2B, 0x5E, (byte)0xCC, (byte)0xB8,
(byte)0xB1, 0x23, 0x44, (byte)0x89, 0x14, 0x2D, 0x62, (byte)0xD4,
(byte)0x88, (byte)0xA4, (byte)0xC8, 0x14, 0x17, 0x20, 0x0E, 0x0A,
0x24, 0x68, 0x10, (byte)0xA1, (byte)0xC7, (byte)0x86, 0x57, 0x1E,
0x7E, 0x44, 0x29, 0x72, 0x65, 0x49, 0x10, 0x53,
(byte)0x9E, (byte)0x80, 0x28, 0x12, 0x44, 0x0A, (byte)0x93, (byte)0x86,
0x49, (byte)0x9C, (byte)0xC8, 0x4C, (byte)0xD8, (byte)0xE4, (byte)0x89, 0x14,
0x27, 0x49, (byte)0x8F, (byte)0xB8, (byte)0xD8, 0x06, (byte)0xE0, 0x1F,
0x55, (byte)0xAB, 0x55, (byte)0xAF, 0x6A, (byte)0xCD, (byte)0xCA, 0x15,
(byte)0xAB, (byte)0xD7, (byte)0xAD, 0x5F, (byte)0xBB, 0x52, (byte)0xC5, 0x03,
0x00
};
}
private byte[] getTextFileData() {
byte[] data = new byte[4096]; // file was forced to be 4096 bytes long
String s = "THIS IS THE WAY WE WASH OUR CLOTHES, WASH OUR CLOTHES, WASH OUR CLOTHES. " +
"THIS IS THE WAY WE WASH OUR CLOTHES, SO EARLY IN THE MORNING.";
System.arraycopy(s.getBytes(), 0, data, 0, s.length());
return data;
}
}

View File

@ -5,15 +5,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import junit.framework.TestCase;
/**
* Exercise the RLE encoder and decoders.
*
* @author robgreene@users.sourceforge.net
*/
public class RleTest extends TestCase {
public class RleTest extends TestCaseHelper {
public void testInputStream() throws IOException {
InputStream is = new RleInputStream(new LittleEndianByteInputStream(getPatternFileRle()));
ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -23,12 +20,6 @@ public class RleTest extends TestCase {
assertEquals(expected, actual);
}
private void assertEquals(byte[] expected, byte[] actual) {
assertEquals(expected.length, actual.length);
for (int i=0; i<expected.length; i++) {
assertEquals("Byte mismatch at offset " + i, expected[i], actual[i]);
}
}
private void copy(InputStream is, OutputStream os) throws IOException {
int b = is.read();
while (b != -1) {

View File

@ -0,0 +1,20 @@
package com.webcodepro.shrinkit.io;
import junit.framework.TestCase;
/**
* Some commmon testing methods.
*
* @author robgreene@users.sourceforge.net
*/
public abstract class TestCaseHelper extends TestCase {
/**
* Compare two byte arrays.
*/
public void assertEquals(byte[] expected, byte[] actual) {
assertEquals(expected.length, actual.length);
for (int i=0; i<expected.length; i++) {
assertEquals("Byte mismatch at offset " + i, expected[i], actual[i]);
}
}
}