diff --git a/src/com/webcodepro/shrinkit/io/BitConstants.java b/src/com/webcodepro/shrinkit/io/BitConstants.java new file mode 100644 index 0000000..ef37282 --- /dev/null +++ b/src/com/webcodepro/shrinkit/io/BitConstants.java @@ -0,0 +1,19 @@ +package com.webcodepro.shrinkit.io; + +/** + * This interface allows bit-related constants to be shared among + * classes. + * + * @author robgreene@users.sourceforge.net + */ +public interface BitConstants { + /** + * The low-tech way to compute a bit mask. Allowing up to 16 bits at this time. + */ + public static final int[] BIT_MASKS = new int[] { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, + 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, + 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, + 0x7fff, 0xffff + }; +} diff --git a/src/com/webcodepro/shrinkit/io/BitInputStream.java b/src/com/webcodepro/shrinkit/io/BitInputStream.java index 07b6641..51ef9bb 100644 --- a/src/com/webcodepro/shrinkit/io/BitInputStream.java +++ b/src/com/webcodepro/shrinkit/io/BitInputStream.java @@ -14,15 +14,7 @@ import java.io.InputStream; * * @author robgreene@users.sourceforge.net */ -public class BitInputStream extends InputStream { - /** The low-tech way to compute a bit mask. Allowing up to 16 bits at this time. */ - private static int[] BIT_MASKS = new int[] { - 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, - 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, - 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, - 0x7fff, 0xffff - }; - +public class BitInputStream extends InputStream implements BitConstants { /** Our source of data. */ private InputStream is; /** The number of bits to read for a request. This can be adjusted dynamically. */ @@ -75,7 +67,7 @@ public class BitInputStream extends InputStream { int b = is.read(); if (b == -1) return b; if (bitsOfData > 0) { - b <<= bitsOfData; + b <<= bitsOfData; // We're placing b on the high-bit side } data|= b; bitsOfData+= 8; diff --git a/src/com/webcodepro/shrinkit/io/BitOutputStream.java b/src/com/webcodepro/shrinkit/io/BitOutputStream.java new file mode 100644 index 0000000..ca81b7b --- /dev/null +++ b/src/com/webcodepro/shrinkit/io/BitOutputStream.java @@ -0,0 +1,98 @@ +package com.webcodepro.shrinkit.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * The BitOutputStream allows varying bit sizes to be written to the wrapped + * OutputStream. This is useful for LZW type compression algorithms + * where 9-12 bit codes are used instead of the 8-bit byte. + *
+ * Warning: The write(byte[])
and write(byte[], int, int)
+ * methods of OutputStream
will not work appropriately with any
+ * bit size > 8 bits.
+ *
+ * @author robgreene@users.sourceforge.net
+ */
+public class BitOutputStream extends OutputStream implements BitConstants {
+ /** Our data target. */
+ private OutputStream os;
+ /** The number of bits to write for a request. This can be adjusted dynamically. */
+ private int requestedNumberOfBits;
+ /** The current bit mask to use for a write(int)
request. */
+ private int bitMask;
+ /** The buffer containing our bits. */
+ private int data = 0;
+ /** Number of bits remaining in our buffer */
+ private int bitsOfData = 0;
+
+ /**
+ * Create a BitOutpuStream wrapping the given OutputStream
+ * and writing the number of bits specified.
+ */
+ public BitOutputStream(OutputStream os, int startingNumberOfBits) {
+ this.os = os;
+ setRequestedNumberOfBits(startingNumberOfBits);
+ }
+
+ /**
+ * Set the number of bits to be write with each call to write(int)
.
+ */
+ public void setRequestedNumberOfBits(int numberOfBits) {
+ this.requestedNumberOfBits = numberOfBits;
+ this.bitMask = BIT_MASKS[numberOfBits];
+ }
+
+ /**
+ * Increase the requested number of bits by one.
+ * This is the general usage and prevents client from needing to track
+ * the requested number of bits or from making various method calls.
+ */
+ public void increaseRequestedNumberOfBits() {
+ setRequestedNumberOfBits(requestedNumberOfBits + 1);
+ }
+
+ /**
+ * Answer with the current bit mask for the current bit size.
+ */
+ public int getBitMask() {
+ return bitMask;
+ }
+
+ /**
+ * Write the number of bits to the wrapped OutputStream.
+ */
+ public void write(int b) throws IOException {
+ b &= bitMask; // Ensure we don't have extra baggage
+ b <<= bitsOfData; // Move beyond existing bits of data
+ data|= b; // Add in the additional data
+ bitsOfData+= requestedNumberOfBits;
+ while (bitsOfData >= 8) {
+ os.write(data & 0xff);
+ data >>= 8;
+ bitsOfData-= 8;
+ }
+ }
+
+ /**
+ * When shifting from buffer to buffer, this OutputStream also should be reset.
+ * This allows the "left over" bits to be cleared.
+ */
+ public void clearRemainingBitsOfData() {
+ this.bitsOfData = 0;
+ this.data = 0;
+ }
+
+ /**
+ * Close the output stream and write any remaining byte to the output.
+ * Note that we may very well end up with extra bits if there are < 8
+ * bits remaining.
+ */
+ public void close() throws IOException {
+ if (bitsOfData > 0) {
+ write(0x00); // forces a flush of the remaining bits in the proper order
+ }
+ }
+
+}
+
diff --git a/test_src/com/webcodepro/shrinkit/io/BitOutputStreamTest.java b/test_src/com/webcodepro/shrinkit/io/BitOutputStreamTest.java
new file mode 100644
index 0000000..762146e
--- /dev/null
+++ b/test_src/com/webcodepro/shrinkit/io/BitOutputStreamTest.java
@@ -0,0 +1,33 @@
+package com.webcodepro.shrinkit.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Exercise the BitOutputStream.
+ *
+ * @author robgreene@users.sourceforge.net
+ */
+public class BitOutputStreamTest extends TestCaseHelper {
+ public void test1() throws IOException {
+ byte[] expected = new byte[] {
+ 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01,
+ 0x01
+ };
+ ByteArrayOutputStream actual = new ByteArrayOutputStream();
+ BitOutputStream os = new BitOutputStream(actual, 9);
+ // 9-bit groups: 100000001 010000000 001000000 000100000 000010000 000001000 000000100 000000010
+ // 8-bit groups: 00000001 00000001 00000001 00000001 00000001 00000001 00000001 00000001 00000001
+ os.write(0x101);
+ os.write(0x80);
+ os.write(0x40);
+ os.write(0x20);
+ os.write(0x10);
+ os.write(0x08);
+ os.write(0x04);
+ os.write(0x02);
+ os.close(); // VERY IMPORTANT!
+ assertEquals(expected, actual.toByteArray());
+ }
+}