BitPackingOutputStream.java

  1. package net.morimekta.io;

  2. import java.io.IOException;
  3. import java.io.OutputStream;

  4. /**
  5.  * Output stream that writes individual bits consecutively to the output stream.
  6.  * The bits will be grouped into bytes and each written when completed. The bits
  7.  * themselves will be assigned from most to least significant per byte.
  8.  *
  9.  * <pre>{@code
  10.  * |---|---|---|---|---|---|---|---|
  11.  * | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
  12.  * |---|---|---|---|---|---|---|---|
  13.  * | a | b | c | d | e | f | g | h |
  14.  * | i | j | k | l | m | n | o | p |
  15.  * |---|---|---|---|---|---|---|---|
  16.  * }</pre>
  17.  */
  18. public class BitPackingOutputStream extends OutputStream {
  19.     private OutputStream out;
  20.     private int          pendingBitsCount;
  21.     private int          pendingBits;
  22.     private int          writtenBits;

  23.     /**
  24.      * Create a bit packing output stream.
  25.      *
  26.      * @param out Output stream to wrote bits to.
  27.      */
  28.     public BitPackingOutputStream(OutputStream out) {
  29.         this.out = out;
  30.         this.pendingBits = 0;
  31.         this.pendingBitsCount = 0;
  32.         this.writtenBits = 0;
  33.     }

  34.     /**
  35.      * Write a number of bits to the stream, and make the next written bits
  36.      * written just after the last bits with no regard to byte border
  37.      * alignment.
  38.      *
  39.      * @param bitCount Number of bits to be written.
  40.      * @param bitData  Data for bits to be written.
  41.      * @throws IOException              If unable to write to stream.
  42.      * @throws IllegalArgumentException On invalid input.
  43.      */
  44.     public void writeBits(int bitCount, int bitData) throws IOException {
  45.         if (bitCount < 1 || bitCount > 31) {
  46.             throw new IllegalArgumentException("Illegal writing bit count " + bitCount);
  47.         } else if (bitData < 0) {
  48.             throw new IllegalArgumentException("Writing negative bit data: " + bitData);
  49.         }

  50.         if (out == null) {
  51.             throw new IOException("Writing to closed stream");
  52.         }

  53.         if (bitCount + pendingBitsCount <= 8) {
  54.             // just store away bits.
  55.             pendingBits = pendingBits | ((bitData & (0xff >>> (8 - bitCount))) << (8 - (bitCount + pendingBitsCount)));
  56.             pendingBitsCount = pendingBitsCount + bitCount;
  57.             writtenBits = writtenBits + bitCount;
  58.             if (pendingBitsCount == 8) {
  59.                 out.write(pendingBits);

  60.                 pendingBits = 0;
  61.                 pendingBitsCount = 0;
  62.             }
  63.         } else {
  64.             int writeBits = 8 - pendingBitsCount;
  65.             writeBits(writeBits, bitData >>> (bitCount - writeBits));
  66.             writeBits(bitCount - writeBits, bitData);
  67.         }
  68.     }

  69.     /**
  70.      * Force the next bit to be on a byte (octet) boundary.
  71.      * Write all pending bits to the stream as if the remainder was written with 0 bits.
  72.      *
  73.      * @throws IOException If unable to write to stream.
  74.      */
  75.     public void align() throws IOException {
  76.         if (pendingBitsCount > 0) {
  77.             out.write(pendingBits);
  78.             pendingBitsCount = 0;
  79.             pendingBits = 0;
  80.         }
  81.     }

  82.     /**
  83.      * Number of bits written, including those not written to stream
  84.      * yet, as the byte is not completed yet.
  85.      *
  86.      * @return Number of bits written.
  87.      */
  88.     public int getWrittenBits() {
  89.         return writtenBits;
  90.     }

  91.     @Override
  92.     public void write(int octet) throws IOException {
  93.         writeBits(8, octet);
  94.     }

  95.     @Override
  96.     public void flush() throws IOException {
  97.         if (out != null) {
  98.             out.flush();
  99.         }
  100.     }

  101.     @Override
  102.     public void close() throws IOException {
  103.         if (out != null) {
  104.             try {
  105.                 if (pendingBitsCount > 0) {
  106.                     out.write(pendingBits);
  107.                 }
  108.                 out.close();
  109.             } finally {
  110.                 pendingBitsCount = 0;
  111.                 pendingBits = 0;
  112.                 out = null;
  113.             }
  114.         }
  115.     }
  116. }