Added RleOutputStream for RLE compression.

This commit is contained in:
Robert Greene 2008-06-30 03:53:01 +00:00
parent c188bf6c2b
commit 006eadf410
2 changed files with 124 additions and 2 deletions

View File

@ -0,0 +1,82 @@
package com.webcodepro.shrinkit.io;
import java.io.IOException;
import java.io.OutputStream;
/**
* The RleOutputStream handles the NuFX RLE data stream.
* This data stream is byte oriented. If a repeat occurs,
* the data stream will contain the marker byte, byte to
* repeat, and the number of repeats (zero based; ie, $00=1,
* $01=2, ... $ff=256). The default marker is $DB.
*
* @author robgreene@users.sourceforge.net
*/
public class RleOutputStream extends OutputStream {
private OutputStream os;
private int escapeChar;
private int repeatedByte;
private int numBytes = -1;
/**
* Create an RLE output stream with the default marker byte.
*/
public RleOutputStream(OutputStream bs) {
this(bs, 0xdb);
}
/**
* Create an RLE output stream with the specified marker byte.
*/
public RleOutputStream(OutputStream os, int escapeChar) {
this.os = os;
this.escapeChar = escapeChar;
}
/**
* Write the next byte to the output stream.
*/
public void write(int b) throws IOException {
if (numBytes == -1) {
repeatedByte = b;
numBytes++;
} else if (repeatedByte == b) {
numBytes++;
if (numBytes > 255) {
flush();
}
} else {
flush();
repeatedByte = b;
numBytes++;
}
}
/**
* Flush out any remaining data.
* If we only have 1 byte and it is <em>not</em> the repeated
* byte, we can just dump that byte. Otherwise, we need to
* write out the escape character, the repeated byte, and
* the number of bytes.
*/
public void flush() throws IOException {
if (numBytes != -1) {
if (numBytes == 0 && escapeChar != repeatedByte) {
os.write(repeatedByte);
} else {
os.write(escapeChar);
os.write(repeatedByte);
os.write(numBytes);
}
numBytes = -1;
}
}
/**
* Close out the data stream. Makes sure the repeate buffer
* is flushed.
*/
public void close() throws IOException {
flush();
os.close();
}
}

View File

@ -1,5 +1,6 @@
package com.webcodepro.shrinkit.io; package com.webcodepro.shrinkit.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -11,23 +12,59 @@ import java.io.OutputStream;
* @author robgreene@users.sourceforge.net * @author robgreene@users.sourceforge.net
*/ */
public class RleTest extends TestCaseHelper { public class RleTest extends TestCaseHelper {
/**
* Test the RleInputStream to verify decoding.
*/
public void testInputStream() throws IOException { public void testInputStream() throws IOException {
InputStream is = new RleInputStream(new LittleEndianByteInputStream(getPatternFileRle())); InputStream is = new RleInputStream(new ByteArrayInputStream(getPatternFileRle()));
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
copy(is,os); copy(is,os);
byte[] expected = getPatternFileUncompressed(); byte[] expected = getPatternFileUncompressed();
byte[] actual = os.toByteArray(); byte[] actual = os.toByteArray();
assertEquals(expected, actual); assertEquals(expected, actual);
} }
/**
* Test the RleOutputStream to verify compression.
*/
public void testOutputStream() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
RleOutputStream os = new RleOutputStream(baos);
ByteArrayInputStream is = new ByteArrayInputStream(getPatternFileUncompressed());
copy(is,os);
byte[] expected = getPatternFileRle();
byte[] actual = baos.toByteArray();
assertEquals(expected, actual);
}
/**
* Test the RleOutputStream with the escape character to ensure it <em>always</em> is encoded.
* Note that a file with lots of 0xdb codes will grow.
*/
public void testOutputStreamEscapeCharacter() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
RleOutputStream os = new RleOutputStream(baos);
ByteArrayInputStream is = new ByteArrayInputStream(new byte[] { (byte)0xdb, (byte)0xdb, 0x00, (byte)0xdb });
copy(is,os);
byte[] expected = new byte[] { (byte)0xdb, (byte)0xdb, 0x01, 0x00, (byte)0xdb, (byte)0xdb, 0x00 };
byte[] actual = baos.toByteArray();
assertEquals(expected, actual);
}
/**
* Copy the input stream to the output stream.
*/
private void copy(InputStream is, OutputStream os) throws IOException { private void copy(InputStream is, OutputStream os) throws IOException {
int b = is.read(); int b = is.read();
while (b != -1) { while (b != -1) {
os.write(b); os.write(b);
b = is.read(); b = is.read();
} }
is.close();
os.close();
} }
/**
* This the RLE compressed pattern file that happens to not compress via LZW.
*/
private byte[] getPatternFileRle() { private byte[] getPatternFileRle() {
return new byte[] { return new byte[] {
(byte)0xdb, 0x01, (byte)0xfd, (byte)0xdb, 0x01, (byte)0xfd,
@ -49,6 +86,9 @@ public class RleTest extends TestCaseHelper {
(byte)0xdb, 0x11, (byte)0x97 (byte)0xdb, 0x11, (byte)0x97
}; };
} }
/**
* This is the uncompressed pattern file that happens to not compress via LZW.
*/
private byte[] getPatternFileUncompressed() { private byte[] getPatternFileUncompressed() {
byte[] data = new byte[4096]; byte[] data = new byte[4096];
int value = 0x01; int value = 0x01;