diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java
index 162be55e..8eb7e101 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java
@@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
+import static com.twelvemonkeys.lang.Validate.notNull;
+
/**
* LSBBitReader
*
@@ -45,18 +47,17 @@ public final class LSBBitReader {
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
private final ImageInputStream imageInput;
- private int bitOffset = 64;
- private long streamPosition = -1;
+ int bitOffset = 64;
+ long streamPosition = -1;
/**
- * Pre buffers up to the next 8 Bytes in input.
+ * 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;
+ this.imageInput = notNull(imageInput);
}
/**
@@ -89,20 +90,16 @@ public final class LSBBitReader {
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
+ // Could eliminate if we never read from the underlying InputStream
+ // outside this class after the object is created
+ if (streamPosition != imageInput.getStreamPosition()) {
+ // Need to reset buffer as stream was read in the meantime
resetBuffer();
}
@@ -110,21 +107,23 @@ public final class LSBBitReader {
if (!peek) {
bitOffset += bits;
- refillBuffer();
+
+ if (bitOffset >= 8) {
+ refillBuffer();
+ }
}
return ret;
}
else {
- //FIXME Untested
+ // Peek always false in this case
long lower = readBits(56);
return (readBits(bits - 56) << (56)) | lower;
}
}
private void refillBuffer() throws IOException {
-
- //Set to stream position consistent with buffered bytes
+ // Set to stream position consistent with buffered bytes
imageInput.seek(streamPosition + 8);
for (; bitOffset >= 8; bitOffset -= 8) {
try {
@@ -138,17 +137,16 @@ public final class LSBBitReader {
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.
- */
+
+ // 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;
@@ -156,7 +154,7 @@ public final class LSBBitReader {
imageInput.seek(inputStreamPosition);
}
catch (EOFException e) {
- //Retry byte by byte
+ // Retry byte by byte
streamPosition = inputStreamPosition - 8;
bitOffset = 64;
refillBuffer();
@@ -164,7 +162,7 @@ public final class LSBBitReader {
}
- //Left for backwards compatibility / Compatibility with ImageInputStream interface
+ // Left for backwards compatibility / Compatibility with ImageInputStream interface
public int readBit() throws IOException {
return (int) readBits(1);
}
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java
index dca166b5..fb4ab4c2 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java
@@ -96,7 +96,9 @@ final class WebPImageReader extends ImageReaderBase {
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
- lsbBitReader = new LSBBitReader(imageInput);
+ if (imageInput != null) {
+ lsbBitReader = new LSBBitReader(imageInput);
+ }
}
private void readHeader(int imageIndex) throws IOException {
@@ -272,19 +274,19 @@ final class WebPImageReader extends ImageReaderBase {
}
// RsV|I|L|E|X|A|R
- int reserved = (int) imageInput.readBits(2);
+ int reserved = lsbBitReader.readBit();
if (reserved != 0) {
// Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
}
- header.containsICCP = imageInput.readBit() == 1;
- header.containsALPH = imageInput.readBit() == 1; // L -> aLpha
- header.containsEXIF = imageInput.readBit() == 1;
- header.containsXMP_ = imageInput.readBit() == 1;
- header.containsANIM = imageInput.readBit() == 1; // A -> Anim
+ header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
+ header.containsXMP_ = lsbBitReader.readBit() == 1;
+ header.containsEXIF = lsbBitReader.readBit() == 1;
+ header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
+ header.containsICCP = lsbBitReader.readBit() == 1;
- reserved = (int) imageInput.readBits(25); // 1 + 24 bits reserved
+ reserved = (int) lsbBitReader.readBits(26); // 2 + 24 bits reserved
if (reserved != 0) {
// Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
@@ -509,15 +511,15 @@ final class WebPImageReader extends ImageReaderBase {
}
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
- int reserved = (int) imageInput.readBits(2);
+ int reserved = (int) lsbBitReader.readBits(2);
if (reserved != 0) {
// Spec says SHOULD be 0
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
}
- int preProcessing = (int) imageInput.readBits(2);
- int filtering = (int) imageInput.readBits(2);
- int compression = (int) imageInput.readBits(2);
+ int preProcessing = (int) lsbBitReader.readBits(2);
+ int filtering = (int) lsbBitReader.readBits(2);
+ int compression = (int) lsbBitReader.readBits(2);
if (DEBUG) {
System.out.println("preProcessing: " + preProcessing);
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorIndexingTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorIndexingTransform.java
index e92af39f..4d062b6c 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorIndexingTransform.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorIndexingTransform.java
@@ -55,7 +55,7 @@ final class ColorIndexingTransform implements Transform {
byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) {
- //Reversed so no used elements are overridden (in case of packing)
+ // Reversed so no used elements are overridden (in case of packing)
for (int x = width - 1; x >= 0; x--) {
int componentSize = 8 >> bits;
@@ -67,7 +67,7 @@ final class ColorIndexingTransform implements Transform {
int index = sample >> componentOffset & ((1 << componentSize) - 1);
- //Arraycopy for 4 elements might not be beneficial
+ // Arraycopy for 4 elements might not be beneficial
System.arraycopy(colorTable, index * 4, rgba, 0, 4);
raster.setDataElements(x, y, rgba);
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanInfo.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanInfo.java
index b0b2b7d9..24f14428 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanInfo.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanInfo.java
@@ -37,7 +37,7 @@ import java.awt.image.*;
* @author Simon Kammermeier
*/
final class HuffmanInfo {
- public Raster huffmanMetaCodes; //Raster allows intuitive lookup by x and y
+ public Raster huffmanMetaCodes; // Raster allows intuitive lookup by x and y
public int metaCodeBits;
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanTable.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanTable.java
index 590b8758..a1909278 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanTable.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/HuffmanTable.java
@@ -83,7 +83,6 @@ final class HuffmanTable {
* @throws IOException when reading produces an exception
*/
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
-
boolean simpleLengthCode = lsbBitReader.readBit() == 1;
if (simpleLengthCode) {
@@ -104,11 +103,9 @@ final class HuffmanTable {
}
}
else {
- /*
- code lengths also huffman coded
- first read the "first stage" code lengths
- In the following this is called the L-Code (for length code)
- */
+ // code lengths also huffman coded
+ // first read the "first stage" code lengths
+ // In the following this is called the L-Code (for length code)
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
short[] lCodeLengths = new short[L_CODE_ORDER.length];
int numPosCodeLens = 0;
@@ -116,16 +113,15 @@ final class HuffmanTable {
for (int i = 0; i < numLCodeLengths; i++) {
short len = (short) lsbBitReader.readBits(3);
lCodeLengths[L_CODE_ORDER[i]] = len;
+
if (len > 0) {
numPosCodeLens++;
}
-
}
- //Use L-Code to read the actual code lengths
+ // Use L-Code to read the actual code lengths
short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
-
buildFromLengths(codeLengths);
}
}
@@ -142,25 +138,21 @@ final class HuffmanTable {
buildFromLengths(codeLengths, numPosCodeLens);
}
-
- /*
- Helper methods to allow reusing in different constructors
- */
-
+ // Helper methods to allow reusing in different constructors
private void buildFromLengths(short[] codeLengths) {
int numPosCodeLens = 0;
+
for (short codeLength : codeLengths) {
if (codeLength != 0) {
numPosCodeLens++;
}
}
+
buildFromLengths(codeLengths, numPosCodeLens);
}
private void buildFromLengths(short[] codeLengths, int numPosCodeLens) {
-
- //Pack code length and corresponding symbols as described above
-
+ // Pack code length and corresponding symbols as described above
int[] lengthsAndSymbols = new int[numPosCodeLens];
int index = 0;
@@ -170,28 +162,25 @@ final class HuffmanTable {
}
}
- //Special case: Only 1 code value
+ // Special case: Only 1 code value
if (numPosCodeLens == 1) {
- //Length is 0 so mask to clear length bits
+ // Length is 0 so mask to clear length bits
Arrays.fill(level1, lengthsAndSymbols[0] & 0xffff);
}
- //Due to the layout of the elements this effectively first sorts by length and then symbol.
+ // Due to the layout of the elements this effectively first sorts by length and then symbol.
Arrays.sort(lengthsAndSymbols);
- /*
- The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
- Only the lowest bits (corresponding to the bit length of the code) are considered.
- Example: code 0..010 (length 2) would appear as 0..001.
- */
+ // The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
+ // Only the lowest bits (corresponding to the bit length of the code) are considered.
+ // Example: code 0..010 (length 2) would appear as 0..001.
int code = 0;
- //Used for level2 lookup
+ // Used for level2 lookup
int rootEntry = -1;
int[] currentTable = null;
for (int i = 0; i < lengthsAndSymbols.length; i++) {
-
int lengthAndSymbol = lengthsAndSymbols[i];
int length = lengthAndSymbol >>> 16;
@@ -202,16 +191,15 @@ final class HuffmanTable {
}
}
else {
- //Existing level2 table not fitting
+ // Existing level2 table not fitting
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
- /*
- Figure out needed table size.
- Start at current symbol and length.
- Every symbol uses 1 slot at the current bit length.
- Going up 1 bit in length multiplies the slots by 2.
- No more open slots indicate the table size to be big enough.
- */
+ // Figure out needed table size.
+ // Start at current symbol and length.
+ // Every symbol uses 1 slot at the current bit length.
+ // Going up 1 bit in length multiplies the slots by 2.
+ // No more open slots indicate the table size to be big enough.
int maxLength = length;
+
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
j < lengthsAndSymbols.length && openSlots > 0;
j++, openSlots--) {
@@ -230,11 +218,11 @@ final class HuffmanTable {
rootEntry = code & ((1 << LEVEL1_BITS) - 1);
level2.add(currentTable);
- //Set root table indirection
+ // Set root table indirection
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1);
}
- //Add to existing (or newly generated) 2nd level table
+ // Add to existing (or newly generated) 2nd level table
for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
}
@@ -256,12 +244,12 @@ final class HuffmanTable {
private int nextCode(int code, int length) {
int a = (~code) & ((1 << length) - 1);
- //This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
- //I.e. the lowest 0-bit in the value code represents
+ // This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
+ // I.e. the lowest 0-bit in the value code represents
int step = Integer.highestOneBit(a);
- //In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
- //This corresponds to adding 1 to the value
+ // In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
+ // This corresponds to adding 1 to the value
return (code & (step - 1)) | step;
}
@@ -270,7 +258,7 @@ final class HuffmanTable {
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens);
- //Not sure where this comes from. Just adapted from the libwebp implementation
+ // Not sure where this comes from. Just adapted from the libwebp implementation
int codedSymbols;
if (lsbBitReader.readBit() == 1) {
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
@@ -282,13 +270,13 @@ final class HuffmanTable {
short[] codeLengths = new short[alphabetSize];
- //Default code for repeating
+ // Default code for repeating
short prevLength = 8;
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
short len = huffmanTable.readSymbol(lsbBitReader);
- if (len < 16) { //Literal length
+ if (len < 16) { // Literal length
codeLengths[i] = len;
if (len != 0) {
prevLength = len;
@@ -300,16 +288,16 @@ final class HuffmanTable {
int repeatOffset;
switch (len) {
- case 16: //Repeat previous
+ case 16: // Repeat previous
repeatSymbol = prevLength;
extraBits = 2;
repeatOffset = 3;
break;
- case 17: //Repeat 0 short
+ case 17: // Repeat 0 short
extraBits = 3;
repeatOffset = 3;
break;
- case 18: //Repeat 0 long
+ case 18: // Repeat 0 long
extraBits = 7;
repeatOffset = 11;
break;
@@ -319,7 +307,6 @@ final class HuffmanTable {
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
-
if (i + repeatCount > alphabetSize) {
throw new IIOException(
String.format(
@@ -330,11 +317,9 @@ final class HuffmanTable {
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
i += repeatCount - 1;
-
}
}
-
return codeLengths;
}
@@ -346,21 +331,20 @@ final class HuffmanTable {
* @throws IOException when the reader throws one reading a symbol
*/
public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
-
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
int lengthAndSymbol = level1[index];
int length = lengthAndSymbol >>> 16;
if (length > LEVEL1_BITS) {
- //Lvl2 lookup
- lsbBitReader.readBits(LEVEL1_BITS); //Consume bits of first level
- int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); //Peek remaining required bits
+ // Lvl2 lookup
+ lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
+ int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
length = lengthAndSymbol >>> 16;
}
- lsbBitReader.readBits(length); //Consume bits
+ lsbBitReader.readBits(length); // Consume bits
return (short) (lengthAndSymbol & 0xffff);
}
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorTransform.java
index 6b023b40..042042a2 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorTransform.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorTransform.java
@@ -51,25 +51,23 @@ final class PredictorTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
-
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
- //Handle top and left border separately
+ // Handle top and left border separately
- //(0,0) Black (0x000000ff) predict
+ // (0,0) Black (0x000000ff) predict
raster.getDataElements(0, 0, rgba);
rgba[3] += 0xff;
raster.setDataElements(0, 0, rgba);
-
byte[] predictor = new byte[4];
byte[] predictor2 = new byte[4];
byte[] predictor3 = new byte[4];
- //(x,0) L predict
+ // (x,0) L predict
for (int x = 1; x < width; x++) {
raster.getDataElements(x, 0, rgba);
raster.getDataElements(x - 1, 0, predictor);
@@ -78,7 +76,7 @@ final class PredictorTransform implements Transform {
raster.setDataElements(x, 0, rgba);
}
- //(0,y) T predict
+ // (0,y) T predict
for (int y = 1; y < height; y++) {
raster.getDataElements(0, y, rgba);
raster.getDataElements(0, y - 1, predictor);
@@ -89,16 +87,14 @@ final class PredictorTransform implements Transform {
for (int y = 1; y < height; y++) {
for (int x = 1; x < width; x++) {
-
int transformType = data.getSample(x >> bits, y >> bits, 1);
raster.getDataElements(x, y, rgba);
- int lX = x - 1; //x for left
+ int lX = x - 1; // x for left
+ int tY = y - 1; // y for top
- int tY = y - 1; //y for top
-
- //top right is not (x+1, tY) if last pixel in line instead (0, y)
+ // top right is not (x+1, tY) if last pixel in line instead (0, y)
int trX = x == width - 1 ? 0 : x + 1;
int trY = x == width - 1 ? y : tY;
diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java
index 525a6fa8..a2d613be 100644
--- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java
+++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java
@@ -36,7 +36,7 @@ package com.twelvemonkeys.imageio.plugins.webp.lossless;
*
* @author Harald Kuhr
*/
-// Hmm.. Why doesn't SUBTRACT_GREEN follow the convention?
+// Hmm... Why doesn't SUBTRACT_GREEN follow the convention?
interface TransformType {
int PREDICTOR_TRANSFORM = 0;
int COLOR_TRANSFORM = 1;
diff --git a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReaderTest.java b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReaderTest.java
new file mode 100644
index 00000000..56191bbe
--- /dev/null
+++ b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReaderTest.java
@@ -0,0 +1,270 @@
+package com.twelvemonkeys.imageio.plugins.webp;
+
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
+
+import org.junit.Test;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * LSBBitReaderTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: LSBBitReaderTest.java,v 1.0 16/10/2022 haraldk Exp$
+ */
+public class LSBBitReaderTest {
+ @Test
+ public void testReadBit() throws IOException {
+ final LSBBitReader bitReader = createBitReader(new byte[] {
+ 0b00010010, 0b00100001, 0b00001000, 0b00000100,
+ /*TODO: Remove these, should not be needed... */ 0, 0, 0, 0
+ });
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(1, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(1, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(1, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(1, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(1, bitReader.readBit());
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(1, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+ assertEquals(0, bitReader.readBit());
+
+// assertThrows(EOFException.class, new ThrowingRunnable() {
+// @Override
+// public void run() throws Throwable {
+// bitReader.readBits(1);
+// }
+// });
+ }
+
+ @Test
+ public void testReadBits() throws IOException {
+ final LSBBitReader bitReader = createBitReader(new byte[] {
+ 0b00100101, 0b01000010, 0b00010000, 0b00001000,
+ 0b00001000, 0b00010000, 0b01000000, 0b00000000,
+ 0b00000010, 0b00100000, 0b00000000, 0b00000100,
+ 0b00000000, 0b00000001, (byte) 0b10000000,
+ });
+
+ assertEquals(1, bitReader.readBits(1));
+ assertEquals(2, bitReader.readBits(2));
+ assertEquals(4, bitReader.readBits(3));
+ assertEquals(8, bitReader.readBits(4));
+ assertEquals(16, bitReader.readBits(5));
+ assertEquals(32, bitReader.readBits(6));
+ assertEquals(64, bitReader.readBits(7));
+ assertEquals(128, bitReader.readBits(8));
+ assertEquals(256, bitReader.readBits(9));
+ assertEquals(512, bitReader.readBits(10));
+ assertEquals(1024, bitReader.readBits(11));
+ assertEquals(2048, bitReader.readBits(12));
+ assertEquals(4096, bitReader.readBits(13));
+ assertEquals(8192, bitReader.readBits(14));
+ assertEquals(16384, bitReader.readBits(15));
+
+// assertThrows(EOFException.class, new ThrowingRunnable() {
+// @Override
+// public void run() throws Throwable {
+// bitReader.readBits(1);
+// }
+// });
+ }
+
+ @Test
+ public void testPeekBits() throws IOException {
+ final LSBBitReader bitReader = createBitReader(new byte[] {
+ 0b00100101, 0b01000010, 0b00010000, 0b00001000,
+ 0b00001000, 0b00010000, 0b01000000, 0b00000000,
+ 0b00000010, 0b00100000, 0b00000000, 0b00000100,
+ 0b00000000, 0b00000001, (byte) 0b10000000
+ });
+
+ assertEquals(1, bitReader.peekBits(1));
+ assertEquals(1, bitReader.peekBits(1));
+ assertEquals(1, bitReader.readBits(1));
+
+ assertEquals(2, bitReader.peekBits(2));
+ assertEquals(2, bitReader.readBits(2));
+
+ assertEquals(4, bitReader.readBits(3));
+
+ assertEquals(8, bitReader.peekBits(4));
+ assertEquals(8, bitReader.readBits(4));
+
+ assertEquals(16, bitReader.peekBits(5));
+ assertEquals(16, bitReader.peekBits(5));
+ assertEquals(16, bitReader.readBits(5));
+
+ assertEquals(32, bitReader.peekBits(6));
+ assertEquals(32, bitReader.readBits(6));
+
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.readBits(7));
+
+ assertEquals(128, bitReader.peekBits(8));
+ assertEquals(128, bitReader.readBits(8));
+
+ assertEquals(256, bitReader.readBits(9));
+
+ assertEquals(512, bitReader.peekBits(10));
+ assertEquals(512, bitReader.readBits(10));
+
+ assertEquals(1024, bitReader.peekBits(11));
+ assertEquals(1024, bitReader.readBits(11));
+
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.readBits(12));
+
+ assertEquals(4096, bitReader.peekBits(13));
+ assertEquals(4096, bitReader.readBits(13));
+
+ assertEquals(8192, bitReader.readBits(14));
+
+ assertEquals(16384, bitReader.peekBits(15));
+ assertEquals(16384, bitReader.peekBits(15));
+ assertEquals(16384, bitReader.readBits(15));
+
+// assertThrows(EOFException.class, new ThrowingRunnable() {
+// @Override
+// public void run() throws Throwable {
+// bitReader.readBits(1);
+// }
+// });
+ }
+
+ @Test
+ public void testReadBetweenBits() throws IOException {
+ ImageInputStream stream = createStream(new byte[] {
+ 0b00100101, 0b01000010, 0b00010000, 0b00001000,
+ 0b00001000, 0b00010000, 0b01000000, 0b00000000,
+ 0b00000010, 0b00100000, 0b00000000, 0b00000100,
+ 0b00000000, 0b00000001, (byte) 0b10000000
+ });
+ final LSBBitReader bitReader = new LSBBitReader(stream);
+
+ assertEquals(1, bitReader.peekBits(1));
+ assertEquals(1, bitReader.peekBits(1));
+ assertEquals(1, bitReader.readBits(1));
+
+ assertEquals(2, bitReader.peekBits(2));
+ assertEquals(2, bitReader.readBits(2));
+
+ assertEquals(4, bitReader.readBits(3));
+
+ // We've read 6 bits, but still on the 1st byte
+ assertEquals(0b00100101, stream.readByte());
+
+ // Start reading from the second byte (10 == 2)
+ assertEquals(2, bitReader.readBits(2));
+
+ assertEquals(16, bitReader.peekBits(5));
+ assertEquals(16, bitReader.peekBits(5));
+ assertEquals(16, bitReader.readBits(5));
+
+ // We've now read 7 bits, but still on the second byte
+ assertEquals(1, stream.getStreamPosition());
+ assertEquals(0b01000010, stream.readByte());
+ assertEquals(2, stream.getStreamPosition());
+
+ assertEquals(16, bitReader.peekBits(11));
+
+ assertEquals(0b00010000, stream.readByte());
+ assertEquals(3, stream.getStreamPosition());
+ stream.seek(2);
+ assertEquals(2, stream.getStreamPosition());
+
+ // Start reading from the third byte (10000 == 16)
+ assertEquals(16, bitReader.peekBits(5));
+ assertEquals(16, bitReader.readBits(5));
+
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.peekBits(7));
+ assertEquals(64, bitReader.readBits(7));
+
+ assertEquals(128, bitReader.peekBits(8));
+ assertEquals(128, bitReader.readBits(8));
+
+ assertEquals(256, bitReader.readBits(9));
+
+ assertEquals(512, bitReader.peekBits(10));
+ assertEquals(512, bitReader.readBits(10));
+
+ assertEquals(1024, bitReader.peekBits(11));
+ assertEquals(1024, bitReader.readBits(11));
+
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.peekBits(12));
+ assertEquals(2048, bitReader.readBits(12));
+
+ assertEquals(4096, bitReader.peekBits(13));
+ assertEquals(4096, bitReader.readBits(13));
+
+ assertEquals(8192, bitReader.readBits(14));
+
+ assertEquals(16384, bitReader.peekBits(15));
+ assertEquals(16384, bitReader.peekBits(15));
+ assertEquals(16384, bitReader.readBits(15));
+
+// assertThrows(EOFException.class, new ThrowingRunnable() {
+// @Override
+// public void run() throws Throwable {
+// bitReader.readBits(1);
+// }
+// });
+ }
+
+ private static LSBBitReader createBitReader(final byte[] data) {
+ ImageInputStream stream = createStream(data);
+ return new LSBBitReader(stream);
+ }
+
+ private static ImageInputStream createStream(byte[] data) {
+ ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
+ stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ return stream;
+ }
+}
\ No newline at end of file