mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
Implement buffering in LSBBitReader
This optimizes away the constant re-reading of bytes. Also allows peeking at coming bits without consuming them.
This commit is contained in:
parent
7ab627a754
commit
b3004a1227
@ -11,46 +11,127 @@ public final class LSBBitReader {
|
||||
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
|
||||
|
||||
private final ImageInputStream imageInput;
|
||||
int bitOffset = 0;
|
||||
private int bitOffset = 64;
|
||||
private long streamPosition = -1;
|
||||
|
||||
/**
|
||||
* Pre buffers up to the next 8 Bytes in input.
|
||||
* Contains valid bits in bits 63 to {@code bitOffset} (inclusive).
|
||||
* Should always be refilled to have at least 56 valid bits (if possible)
|
||||
*/
|
||||
private long buffer;
|
||||
|
||||
public LSBBitReader(ImageInputStream imageInput) {
|
||||
this.imageInput = imageInput;
|
||||
}
|
||||
|
||||
// TODO: Optimize this... Read many bits at once!
|
||||
/**
|
||||
* Reads the specified number of bits from the stream in an LSB-first way and advances the bitOffset.
|
||||
* The underlying ImageInputStream will be advanced to the first not (completely) read byte.
|
||||
* Requesting more than 64 bits will advance the reader by the correct amount and return the lowest 64 bits of
|
||||
* the read number
|
||||
*
|
||||
* @param bits the number of bits to read
|
||||
* @return a signed long built from the requested bits (truncated to the low 64 bits)
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see LSBBitReader#peekBits
|
||||
*/
|
||||
public long readBits(int bits) throws IOException {
|
||||
long result = 0;
|
||||
for (int i = 0; i < bits; i++) {
|
||||
result |= (long) readBit() << i;
|
||||
}
|
||||
|
||||
return result;
|
||||
return readBits(bits, false);
|
||||
}
|
||||
|
||||
// TODO: Optimize this...
|
||||
// TODO: Consider not reading value over and over....
|
||||
/**
|
||||
* Reads the specified number of bits from the buffer in an LSB-first way.
|
||||
* Does not advance the bitOffset or the underlying input stream.
|
||||
* As only 56 bits are buffered (in the worst case) peeking more is not possible without advancing the reader and
|
||||
* as such disallowed.
|
||||
*
|
||||
* @param bits the number of bits to peek (max 56)
|
||||
* @return a signed long built from the requested bits
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see LSBBitReader#readBits
|
||||
*/
|
||||
public long peekBits(int bits) throws IOException {
|
||||
if (bits > 56) {
|
||||
throw new IllegalArgumentException("Tried peeking over 56");
|
||||
}
|
||||
return readBits(bits, true);
|
||||
}
|
||||
|
||||
//Driver
|
||||
private long readBits(int bits, boolean peek) throws IOException {
|
||||
if (bits <= 56) {
|
||||
|
||||
/*
|
||||
Could eliminate if we never read from the underlying InputStream outside this class after the object is
|
||||
created
|
||||
*/
|
||||
long inputStreamPosition = imageInput.getStreamPosition();
|
||||
if (streamPosition != inputStreamPosition) {
|
||||
//Need to reset buffer as stream was read in the meantime
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
long ret = (buffer >>> bitOffset) & ((1L << bits) - 1);
|
||||
|
||||
if (!peek) {
|
||||
bitOffset += bits;
|
||||
refillBuffer();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
//FIXME Untested
|
||||
long lower = readBits(56);
|
||||
return (readBits(bits - 56) << (56)) | lower;
|
||||
}
|
||||
}
|
||||
|
||||
private void refillBuffer() throws IOException {
|
||||
|
||||
//Set to stream position consistent with buffered bytes
|
||||
imageInput.seek(streamPosition + 8);
|
||||
for (; bitOffset >= 8; bitOffset -= 8) {
|
||||
try {
|
||||
byte b = imageInput.readByte();
|
||||
buffer >>>= 8;
|
||||
streamPosition++;
|
||||
buffer |= ((long) b << 56);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
imageInput.seek(streamPosition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/*
|
||||
Reset to guarantee stream position consistent with returned bytes
|
||||
Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
|
||||
this class after the object is created.
|
||||
*/
|
||||
imageInput.seek(streamPosition);
|
||||
}
|
||||
|
||||
private void resetBuffer() throws IOException {
|
||||
|
||||
long inputStreamPosition = imageInput.getStreamPosition();
|
||||
try {
|
||||
buffer = imageInput.readLong();
|
||||
bitOffset = 0;
|
||||
streamPosition = inputStreamPosition;
|
||||
imageInput.seek(inputStreamPosition);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
//Retry byte by byte
|
||||
streamPosition = inputStreamPosition - 8;
|
||||
bitOffset = 64;
|
||||
refillBuffer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Left for backwards compatibility / Compatibility with ImageInputStream interface
|
||||
public int readBit() throws IOException {
|
||||
int bit = 7 - bitOffset;
|
||||
|
||||
imageInput.setBitOffset(bit);
|
||||
|
||||
// Compute final bit offset before we call read() and seek()
|
||||
int newBitOffset = (bitOffset + 1) & 0x7;
|
||||
|
||||
int val = imageInput.read();
|
||||
if (val == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
if (newBitOffset != 0) {
|
||||
// Move byte position back if in the middle of a byte
|
||||
// NOTE: RESETS bit offset!
|
||||
imageInput.seek(imageInput.getStreamPosition() - 1);
|
||||
}
|
||||
|
||||
bitOffset = newBitOffset;
|
||||
|
||||
// Shift the bit to be read to the rightmost position
|
||||
return (val >> (7 - bit)) & 0x1;
|
||||
return (int) readBits(1);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user