From af85d7630699aa0aa6bc9dfa69ac3a78b7e61940 Mon Sep 17 00:00:00 2001 From: Robert Greene Date: Sat, 28 Jun 2008 15:01:26 +0000 Subject: [PATCH] Adding BitOutputStream; moved bit mask constants into BitConstants interface for both the input and the output stream. --- .../webcodepro/shrinkit/io/BitConstants.java | 19 ++++ .../shrinkit/io/BitInputStream.java | 12 +-- .../shrinkit/io/BitOutputStream.java | 98 +++++++++++++++++++ .../shrinkit/io/BitOutputStreamTest.java | 33 +++++++ 4 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 src/com/webcodepro/shrinkit/io/BitConstants.java create mode 100644 src/com/webcodepro/shrinkit/io/BitOutputStream.java create mode 100644 test_src/com/webcodepro/shrinkit/io/BitOutputStreamTest.java 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()); + } +}