Skip to content

Commit

Permalink
#704 Tiny performance improvement + code clean-up
Browse files Browse the repository at this point in the history
(cherry picked from commit 61424f3)
  • Loading branch information
haraldk committed Oct 19, 2022
1 parent 38192ae commit 6ed858a
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import java.io.EOFException;
import java.io.IOException;

import static com.twelvemonkeys.lang.Validate.notNull;

/**
* LSBBitReader
*
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -89,42 +90,40 @@ 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
// 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();
}

long ret = (buffer >>> bitOffset) & ((1L << bits) - 1);

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 {
Expand All @@ -138,33 +137,32 @@ private void refillBuffer() throws IOException {
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;
streamPosition = inputStreamPosition;
imageInput.seek(inputStreamPosition);
}
catch (EOFException e) {
//Retry byte by byte
// Retry byte by byte
streamPosition = inputStreamPosition - 8;
bitOffset = 64;
refillBuffer();
}

}

//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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ protected void resetMembers() {
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 {
Expand Down Expand Up @@ -272,19 +274,19 @@ private void readHeader() throws IOException {
}

// 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));
Expand Down Expand Up @@ -509,15 +511,15 @@ private void readVP8Extended(BufferedImage destination, ImageReadParam param, lo
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void applyInverse(WritableRaster raster) {
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;
Expand All @@ -67,7 +67,7 @@ public void applyInverse(WritableRaster raster) {

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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* @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;

Expand Down
Loading

0 comments on commit 6ed858a

Please sign in to comment.