mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
#704 Tiny performance improvement + code clean-up
(cherry picked from commit 61424f33b646a31ab49364d06c5ac630c3417ee1)
This commit is contained in:
parent
38192ae835
commit
6ed858a4ca
@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputStream;
|
|||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LSBBitReader
|
* LSBBitReader
|
||||||
*
|
*
|
||||||
@ -45,18 +47,17 @@ public final class LSBBitReader {
|
|||||||
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
|
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
|
||||||
|
|
||||||
private final ImageInputStream imageInput;
|
private final ImageInputStream imageInput;
|
||||||
private int bitOffset = 64;
|
int bitOffset = 64;
|
||||||
private long streamPosition = -1;
|
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).
|
* 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;
|
private long buffer;
|
||||||
|
|
||||||
public LSBBitReader(ImageInputStream imageInput) {
|
public LSBBitReader(ImageInputStream imageInput) {
|
||||||
this.imageInput = imageInput;
|
this.imageInput = notNull(imageInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,20 +90,16 @@ public final class LSBBitReader {
|
|||||||
if (bits > 56) {
|
if (bits > 56) {
|
||||||
throw new IllegalArgumentException("Tried peeking over 56");
|
throw new IllegalArgumentException("Tried peeking over 56");
|
||||||
}
|
}
|
||||||
|
|
||||||
return readBits(bits, true);
|
return readBits(bits, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Driver
|
|
||||||
private long readBits(int bits, boolean peek) throws IOException {
|
private long readBits(int bits, boolean peek) throws IOException {
|
||||||
if (bits <= 56) {
|
if (bits <= 56) {
|
||||||
|
// Could eliminate if we never read from the underlying InputStream
|
||||||
/*
|
// outside this class after the object is created
|
||||||
Could eliminate if we never read from the underlying InputStream outside this class after the object is
|
if (streamPosition != imageInput.getStreamPosition()) {
|
||||||
created
|
// Need to reset buffer as stream was read in the meantime
|
||||||
*/
|
|
||||||
long inputStreamPosition = imageInput.getStreamPosition();
|
|
||||||
if (streamPosition != inputStreamPosition) {
|
|
||||||
//Need to reset buffer as stream was read in the meantime
|
|
||||||
resetBuffer();
|
resetBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,21 +107,23 @@ public final class LSBBitReader {
|
|||||||
|
|
||||||
if (!peek) {
|
if (!peek) {
|
||||||
bitOffset += bits;
|
bitOffset += bits;
|
||||||
refillBuffer();
|
|
||||||
|
if (bitOffset >= 8) {
|
||||||
|
refillBuffer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//FIXME Untested
|
// Peek always false in this case
|
||||||
long lower = readBits(56);
|
long lower = readBits(56);
|
||||||
return (readBits(bits - 56) << (56)) | lower;
|
return (readBits(bits - 56) << (56)) | lower;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refillBuffer() throws IOException {
|
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);
|
imageInput.seek(streamPosition + 8);
|
||||||
for (; bitOffset >= 8; bitOffset -= 8) {
|
for (; bitOffset >= 8; bitOffset -= 8) {
|
||||||
try {
|
try {
|
||||||
@ -138,17 +137,16 @@ public final class LSBBitReader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
Reset to guarantee stream position consistent with returned bytes
|
// 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
|
// Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
|
||||||
this class after the object is created.
|
// this class after the object is created.
|
||||||
*/
|
|
||||||
imageInput.seek(streamPosition);
|
imageInput.seek(streamPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetBuffer() throws IOException {
|
private void resetBuffer() throws IOException {
|
||||||
|
|
||||||
long inputStreamPosition = imageInput.getStreamPosition();
|
long inputStreamPosition = imageInput.getStreamPosition();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
buffer = imageInput.readLong();
|
buffer = imageInput.readLong();
|
||||||
bitOffset = 0;
|
bitOffset = 0;
|
||||||
@ -156,7 +154,7 @@ public final class LSBBitReader {
|
|||||||
imageInput.seek(inputStreamPosition);
|
imageInput.seek(inputStreamPosition);
|
||||||
}
|
}
|
||||||
catch (EOFException e) {
|
catch (EOFException e) {
|
||||||
//Retry byte by byte
|
// Retry byte by byte
|
||||||
streamPosition = inputStreamPosition - 8;
|
streamPosition = inputStreamPosition - 8;
|
||||||
bitOffset = 64;
|
bitOffset = 64;
|
||||||
refillBuffer();
|
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 {
|
public int readBit() throws IOException {
|
||||||
return (int) readBits(1);
|
return (int) readBits(1);
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,9 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||||
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||||
|
|
||||||
lsbBitReader = new LSBBitReader(imageInput);
|
if (imageInput != null) {
|
||||||
|
lsbBitReader = new LSBBitReader(imageInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readHeader(int imageIndex) throws IOException {
|
private void readHeader(int imageIndex) throws IOException {
|
||||||
@ -272,19 +274,19 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RsV|I|L|E|X|A|R
|
// RsV|I|L|E|X|A|R
|
||||||
int reserved = (int) imageInput.readBits(2);
|
int reserved = lsbBitReader.readBit();
|
||||||
if (reserved != 0) {
|
if (reserved != 0) {
|
||||||
// Spec says SHOULD be 0
|
// Spec says SHOULD be 0
|
||||||
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
|
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
|
||||||
}
|
}
|
||||||
|
|
||||||
header.containsICCP = imageInput.readBit() == 1;
|
header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
|
||||||
header.containsALPH = imageInput.readBit() == 1; // L -> aLpha
|
header.containsXMP_ = lsbBitReader.readBit() == 1;
|
||||||
header.containsEXIF = imageInput.readBit() == 1;
|
header.containsEXIF = lsbBitReader.readBit() == 1;
|
||||||
header.containsXMP_ = imageInput.readBit() == 1;
|
header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
|
||||||
header.containsANIM = imageInput.readBit() == 1; // A -> Anim
|
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) {
|
if (reserved != 0) {
|
||||||
// Spec says SHOULD be 0
|
// Spec says SHOULD be 0
|
||||||
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
|
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 {
|
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) {
|
if (reserved != 0) {
|
||||||
// Spec says SHOULD be 0
|
// Spec says SHOULD be 0
|
||||||
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
||||||
}
|
}
|
||||||
|
|
||||||
int preProcessing = (int) imageInput.readBits(2);
|
int preProcessing = (int) lsbBitReader.readBits(2);
|
||||||
int filtering = (int) imageInput.readBits(2);
|
int filtering = (int) lsbBitReader.readBits(2);
|
||||||
int compression = (int) imageInput.readBits(2);
|
int compression = (int) lsbBitReader.readBits(2);
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("preProcessing: " + preProcessing);
|
System.out.println("preProcessing: " + preProcessing);
|
||||||
|
@ -55,7 +55,7 @@ final class ColorIndexingTransform implements Transform {
|
|||||||
byte[] rgba = new byte[4];
|
byte[] rgba = new byte[4];
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
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--) {
|
for (int x = width - 1; x >= 0; x--) {
|
||||||
|
|
||||||
int componentSize = 8 >> bits;
|
int componentSize = 8 >> bits;
|
||||||
@ -67,7 +67,7 @@ final class ColorIndexingTransform implements Transform {
|
|||||||
|
|
||||||
int index = sample >> componentOffset & ((1 << componentSize) - 1);
|
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);
|
System.arraycopy(colorTable, index * 4, rgba, 0, 4);
|
||||||
raster.setDataElements(x, y, rgba);
|
raster.setDataElements(x, y, rgba);
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import java.awt.image.*;
|
|||||||
* @author Simon Kammermeier
|
* @author Simon Kammermeier
|
||||||
*/
|
*/
|
||||||
final class HuffmanInfo {
|
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;
|
public int metaCodeBits;
|
||||||
|
|
||||||
|
@ -83,7 +83,6 @@ final class HuffmanTable {
|
|||||||
* @throws IOException when reading produces an exception
|
* @throws IOException when reading produces an exception
|
||||||
*/
|
*/
|
||||||
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
|
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
|
||||||
|
|
||||||
boolean simpleLengthCode = lsbBitReader.readBit() == 1;
|
boolean simpleLengthCode = lsbBitReader.readBit() == 1;
|
||||||
|
|
||||||
if (simpleLengthCode) {
|
if (simpleLengthCode) {
|
||||||
@ -104,11 +103,9 @@ final class HuffmanTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/*
|
// code lengths also huffman coded
|
||||||
code lengths also huffman coded
|
// first read the "first stage" code lengths
|
||||||
first read the "first stage" code lengths
|
// In the following this is called the L-Code (for length code)
|
||||||
In the following this is called the L-Code (for length code)
|
|
||||||
*/
|
|
||||||
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
|
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
|
||||||
short[] lCodeLengths = new short[L_CODE_ORDER.length];
|
short[] lCodeLengths = new short[L_CODE_ORDER.length];
|
||||||
int numPosCodeLens = 0;
|
int numPosCodeLens = 0;
|
||||||
@ -116,16 +113,15 @@ final class HuffmanTable {
|
|||||||
for (int i = 0; i < numLCodeLengths; i++) {
|
for (int i = 0; i < numLCodeLengths; i++) {
|
||||||
short len = (short) lsbBitReader.readBits(3);
|
short len = (short) lsbBitReader.readBits(3);
|
||||||
lCodeLengths[L_CODE_ORDER[i]] = len;
|
lCodeLengths[L_CODE_ORDER[i]] = len;
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
numPosCodeLens++;
|
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);
|
short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
|
||||||
|
|
||||||
|
|
||||||
buildFromLengths(codeLengths);
|
buildFromLengths(codeLengths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,25 +138,21 @@ final class HuffmanTable {
|
|||||||
buildFromLengths(codeLengths, numPosCodeLens);
|
buildFromLengths(codeLengths, numPosCodeLens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper methods to allow reusing in different constructors
|
||||||
/*
|
|
||||||
Helper methods to allow reusing in different constructors
|
|
||||||
*/
|
|
||||||
|
|
||||||
private void buildFromLengths(short[] codeLengths) {
|
private void buildFromLengths(short[] codeLengths) {
|
||||||
int numPosCodeLens = 0;
|
int numPosCodeLens = 0;
|
||||||
|
|
||||||
for (short codeLength : codeLengths) {
|
for (short codeLength : codeLengths) {
|
||||||
if (codeLength != 0) {
|
if (codeLength != 0) {
|
||||||
numPosCodeLens++;
|
numPosCodeLens++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFromLengths(codeLengths, numPosCodeLens);
|
buildFromLengths(codeLengths, numPosCodeLens);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildFromLengths(short[] codeLengths, int 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[] lengthsAndSymbols = new int[numPosCodeLens];
|
||||||
|
|
||||||
int index = 0;
|
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) {
|
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);
|
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);
|
Arrays.sort(lengthsAndSymbols);
|
||||||
|
|
||||||
/*
|
// The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
|
||||||
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.
|
||||||
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.
|
||||||
Example: code 0..010 (length 2) would appear as 0..001.
|
|
||||||
*/
|
|
||||||
int code = 0;
|
int code = 0;
|
||||||
|
|
||||||
//Used for level2 lookup
|
// Used for level2 lookup
|
||||||
int rootEntry = -1;
|
int rootEntry = -1;
|
||||||
int[] currentTable = null;
|
int[] currentTable = null;
|
||||||
|
|
||||||
for (int i = 0; i < lengthsAndSymbols.length; i++) {
|
for (int i = 0; i < lengthsAndSymbols.length; i++) {
|
||||||
|
|
||||||
int lengthAndSymbol = lengthsAndSymbols[i];
|
int lengthAndSymbol = lengthsAndSymbols[i];
|
||||||
|
|
||||||
int length = lengthAndSymbol >>> 16;
|
int length = lengthAndSymbol >>> 16;
|
||||||
@ -202,16 +191,15 @@ final class HuffmanTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//Existing level2 table not fitting
|
// Existing level2 table not fitting
|
||||||
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
|
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
|
||||||
/*
|
// Figure out needed table size.
|
||||||
Figure out needed table size.
|
// Start at current symbol and length.
|
||||||
Start at current symbol and length.
|
// Every symbol uses 1 slot at the current bit length.
|
||||||
Every symbol uses 1 slot at the current bit length.
|
// Going up 1 bit in length multiplies the slots by 2.
|
||||||
Going up 1 bit in length multiplies the slots by 2.
|
// No more open slots indicate the table size to be big enough.
|
||||||
No more open slots indicate the table size to be big enough.
|
|
||||||
*/
|
|
||||||
int maxLength = length;
|
int maxLength = length;
|
||||||
|
|
||||||
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
|
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
|
||||||
j < lengthsAndSymbols.length && openSlots > 0;
|
j < lengthsAndSymbols.length && openSlots > 0;
|
||||||
j++, openSlots--) {
|
j++, openSlots--) {
|
||||||
@ -230,11 +218,11 @@ final class HuffmanTable {
|
|||||||
rootEntry = code & ((1 << LEVEL1_BITS) - 1);
|
rootEntry = code & ((1 << LEVEL1_BITS) - 1);
|
||||||
level2.add(currentTable);
|
level2.add(currentTable);
|
||||||
|
|
||||||
//Set root table indirection
|
// Set root table indirection
|
||||||
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1);
|
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)) {
|
for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
|
||||||
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
|
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
|
||||||
}
|
}
|
||||||
@ -256,12 +244,12 @@ final class HuffmanTable {
|
|||||||
private int nextCode(int code, int length) {
|
private int nextCode(int code, int length) {
|
||||||
int a = (~code) & ((1 << length) - 1);
|
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)
|
// 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
|
// I.e. the lowest 0-bit in the value code represents
|
||||||
int step = Integer.highestOneBit(a);
|
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
|
// 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
|
// This corresponds to adding 1 to the value
|
||||||
return (code & (step - 1)) | step;
|
return (code & (step - 1)) | step;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +258,7 @@ final class HuffmanTable {
|
|||||||
|
|
||||||
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens);
|
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;
|
int codedSymbols;
|
||||||
if (lsbBitReader.readBit() == 1) {
|
if (lsbBitReader.readBit() == 1) {
|
||||||
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
|
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
|
||||||
@ -282,13 +270,13 @@ final class HuffmanTable {
|
|||||||
|
|
||||||
short[] codeLengths = new short[alphabetSize];
|
short[] codeLengths = new short[alphabetSize];
|
||||||
|
|
||||||
//Default code for repeating
|
// Default code for repeating
|
||||||
short prevLength = 8;
|
short prevLength = 8;
|
||||||
|
|
||||||
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
|
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
|
||||||
short len = huffmanTable.readSymbol(lsbBitReader);
|
short len = huffmanTable.readSymbol(lsbBitReader);
|
||||||
|
|
||||||
if (len < 16) { //Literal length
|
if (len < 16) { // Literal length
|
||||||
codeLengths[i] = len;
|
codeLengths[i] = len;
|
||||||
if (len != 0) {
|
if (len != 0) {
|
||||||
prevLength = len;
|
prevLength = len;
|
||||||
@ -300,16 +288,16 @@ final class HuffmanTable {
|
|||||||
int repeatOffset;
|
int repeatOffset;
|
||||||
|
|
||||||
switch (len) {
|
switch (len) {
|
||||||
case 16: //Repeat previous
|
case 16: // Repeat previous
|
||||||
repeatSymbol = prevLength;
|
repeatSymbol = prevLength;
|
||||||
extraBits = 2;
|
extraBits = 2;
|
||||||
repeatOffset = 3;
|
repeatOffset = 3;
|
||||||
break;
|
break;
|
||||||
case 17: //Repeat 0 short
|
case 17: // Repeat 0 short
|
||||||
extraBits = 3;
|
extraBits = 3;
|
||||||
repeatOffset = 3;
|
repeatOffset = 3;
|
||||||
break;
|
break;
|
||||||
case 18: //Repeat 0 long
|
case 18: // Repeat 0 long
|
||||||
extraBits = 7;
|
extraBits = 7;
|
||||||
repeatOffset = 11;
|
repeatOffset = 11;
|
||||||
break;
|
break;
|
||||||
@ -319,7 +307,6 @@ final class HuffmanTable {
|
|||||||
|
|
||||||
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
|
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
|
||||||
|
|
||||||
|
|
||||||
if (i + repeatCount > alphabetSize) {
|
if (i + repeatCount > alphabetSize) {
|
||||||
throw new IIOException(
|
throw new IIOException(
|
||||||
String.format(
|
String.format(
|
||||||
@ -330,11 +317,9 @@ final class HuffmanTable {
|
|||||||
|
|
||||||
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
|
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
|
||||||
i += repeatCount - 1;
|
i += repeatCount - 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return codeLengths;
|
return codeLengths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,21 +331,20 @@ final class HuffmanTable {
|
|||||||
* @throws IOException when the reader throws one reading a symbol
|
* @throws IOException when the reader throws one reading a symbol
|
||||||
*/
|
*/
|
||||||
public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
|
public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
|
||||||
|
|
||||||
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
|
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
|
||||||
int lengthAndSymbol = level1[index];
|
int lengthAndSymbol = level1[index];
|
||||||
|
|
||||||
int length = lengthAndSymbol >>> 16;
|
int length = lengthAndSymbol >>> 16;
|
||||||
|
|
||||||
if (length > LEVEL1_BITS) {
|
if (length > LEVEL1_BITS) {
|
||||||
//Lvl2 lookup
|
// Lvl2 lookup
|
||||||
lsbBitReader.readBits(LEVEL1_BITS); //Consume bits of first level
|
lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
|
||||||
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); //Peek remaining required bits
|
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
|
||||||
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
|
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
|
||||||
length = lengthAndSymbol >>> 16;
|
length = lengthAndSymbol >>> 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
lsbBitReader.readBits(length); //Consume bits
|
lsbBitReader.readBits(length); // Consume bits
|
||||||
|
|
||||||
return (short) (lengthAndSymbol & 0xffff);
|
return (short) (lengthAndSymbol & 0xffff);
|
||||||
}
|
}
|
||||||
|
@ -51,25 +51,23 @@ final class PredictorTransform implements Transform {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyInverse(WritableRaster raster) {
|
public void applyInverse(WritableRaster raster) {
|
||||||
|
|
||||||
int width = raster.getWidth();
|
int width = raster.getWidth();
|
||||||
int height = raster.getHeight();
|
int height = raster.getHeight();
|
||||||
|
|
||||||
byte[] rgba = new byte[4];
|
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);
|
raster.getDataElements(0, 0, rgba);
|
||||||
rgba[3] += 0xff;
|
rgba[3] += 0xff;
|
||||||
raster.setDataElements(0, 0, rgba);
|
raster.setDataElements(0, 0, rgba);
|
||||||
|
|
||||||
|
|
||||||
byte[] predictor = new byte[4];
|
byte[] predictor = new byte[4];
|
||||||
byte[] predictor2 = new byte[4];
|
byte[] predictor2 = new byte[4];
|
||||||
byte[] predictor3 = new byte[4];
|
byte[] predictor3 = new byte[4];
|
||||||
|
|
||||||
//(x,0) L predict
|
// (x,0) L predict
|
||||||
for (int x = 1; x < width; x++) {
|
for (int x = 1; x < width; x++) {
|
||||||
raster.getDataElements(x, 0, rgba);
|
raster.getDataElements(x, 0, rgba);
|
||||||
raster.getDataElements(x - 1, 0, predictor);
|
raster.getDataElements(x - 1, 0, predictor);
|
||||||
@ -78,7 +76,7 @@ final class PredictorTransform implements Transform {
|
|||||||
raster.setDataElements(x, 0, rgba);
|
raster.setDataElements(x, 0, rgba);
|
||||||
}
|
}
|
||||||
|
|
||||||
//(0,y) T predict
|
// (0,y) T predict
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
raster.getDataElements(0, y, rgba);
|
raster.getDataElements(0, y, rgba);
|
||||||
raster.getDataElements(0, y - 1, predictor);
|
raster.getDataElements(0, y - 1, predictor);
|
||||||
@ -89,16 +87,14 @@ final class PredictorTransform implements Transform {
|
|||||||
|
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
for (int x = 1; x < width; x++) {
|
for (int x = 1; x < width; x++) {
|
||||||
|
|
||||||
int transformType = data.getSample(x >> bits, y >> bits, 1);
|
int transformType = data.getSample(x >> bits, y >> bits, 1);
|
||||||
|
|
||||||
raster.getDataElements(x, y, rgba);
|
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 trX = x == width - 1 ? 0 : x + 1;
|
||||||
int trY = x == width - 1 ? y : tY;
|
int trY = x == width - 1 ? y : tY;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ package com.twelvemonkeys.imageio.plugins.webp.lossless;
|
|||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
*/
|
*/
|
||||||
// Hmm.. Why doesn't SUBTRACT_GREEN follow the convention?
|
// Hmm... Why doesn't SUBTRACT_GREEN follow the convention?
|
||||||
interface TransformType {
|
interface TransformType {
|
||||||
int PREDICTOR_TRANSFORM = 0;
|
int PREDICTOR_TRANSFORM = 0;
|
||||||
int COLOR_TRANSFORM = 1;
|
int COLOR_TRANSFORM = 1;
|
||||||
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user