diff --git a/src/com/webcodepro/shrinkit/io/RleOutputStream.java b/src/com/webcodepro/shrinkit/io/RleOutputStream.java
new file mode 100644
index 0000000..b365245
--- /dev/null
+++ b/src/com/webcodepro/shrinkit/io/RleOutputStream.java
@@ -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 not 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();
+ }
+}
diff --git a/test_src/com/webcodepro/shrinkit/io/RleTest.java b/test_src/com/webcodepro/shrinkit/io/RleTest.java
index 7b99e6f..be4022c 100644
--- a/test_src/com/webcodepro/shrinkit/io/RleTest.java
+++ b/test_src/com/webcodepro/shrinkit/io/RleTest.java
@@ -1,5 +1,6 @@
package com.webcodepro.shrinkit.io;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -11,23 +12,59 @@ import java.io.OutputStream;
* @author robgreene@users.sourceforge.net
*/
public class RleTest extends TestCaseHelper {
+ /**
+ * Test the RleInputStream to verify decoding.
+ */
public void testInputStream() throws IOException {
- InputStream is = new RleInputStream(new LittleEndianByteInputStream(getPatternFileRle()));
+ InputStream is = new RleInputStream(new ByteArrayInputStream(getPatternFileRle()));
ByteArrayOutputStream os = new ByteArrayOutputStream();
copy(is,os);
byte[] expected = getPatternFileUncompressed();
byte[] actual = os.toByteArray();
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 always 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 {
int b = is.read();
while (b != -1) {
os.write(b);
b = is.read();
}
+ is.close();
+ os.close();
}
-
+
+ /**
+ * This the RLE compressed pattern file that happens to not compress via LZW.
+ */
private byte[] getPatternFileRle() {
return new byte[] {
(byte)0xdb, 0x01, (byte)0xfd,
@@ -49,6 +86,9 @@ public class RleTest extends TestCaseHelper {
(byte)0xdb, 0x11, (byte)0x97
};
}
+ /**
+ * This is the uncompressed pattern file that happens to not compress via LZW.
+ */
private byte[] getPatternFileUncompressed() {
byte[] data = new byte[4096];
int value = 0x01;