mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
#702 Fix NPE while reading an WebP animation without alpha
+ bonus cleanup
This commit is contained in:
parent
29dca0f124
commit
0160fb70f8
@ -31,29 +31,6 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.webp;
|
package com.twelvemonkeys.imageio.plugins.webp;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.color.ICC_ColorSpace;
|
|
||||||
import java.awt.color.ICC_Profile;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.ColorConvertOp;
|
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.DataBuffer;
|
|
||||||
import java.awt.image.Raster;
|
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.ImageReadParam;
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||||
@ -68,6 +45,25 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
|||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.imageio.util.RasterUtils;
|
import com.twelvemonkeys.imageio.util.RasterUtils;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebPImageReader
|
* WebPImageReader
|
||||||
*/
|
*/
|
||||||
@ -216,7 +212,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
switch (chunk) {
|
switch (chunk) {
|
||||||
case WebP.CHUNK_VP8_:
|
case WebP.CHUNK_VP8_:
|
||||||
//https://tools.ietf.org/html/rfc6386#section-9.1
|
// https://tools.ietf.org/html/rfc6386#section-9.1
|
||||||
int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = inter frame (not used in WebP)
|
int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = inter frame (not used in WebP)
|
||||||
|
|
||||||
if (frameType != 0) {
|
if (frameType != 0) {
|
||||||
@ -436,7 +432,6 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
if (header.containsANIM) {
|
if (header.containsANIM) {
|
||||||
AnimationFrame frame = frames.get(imageIndex);
|
AnimationFrame frame = frames.get(imageIndex);
|
||||||
imageInput.seek(frame.offset + 16);
|
imageInput.seek(frame.offset + 16);
|
||||||
opaqueAlpha(destination.getAlphaRaster());
|
|
||||||
readVP8Extended(destination, param, frame.offset + frame.length, frame.bounds.width, frame.bounds.height);
|
readVP8Extended(destination, param, frame.offset + frame.length, frame.bounds.width, frame.bounds.height);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -466,8 +461,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
readVP8Extended(destination, param, streamEnd, header.width, header.height);
|
readVP8Extended(destination, param, streamEnd, header.width, header.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width,
|
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width, final int height) throws IOException {
|
||||||
final int height) throws IOException {
|
|
||||||
while (imageInput.getStreamPosition() < streamEnd) {
|
while (imageInput.getStreamPosition() < streamEnd) {
|
||||||
int nextChunk = imageInput.readInt();
|
int nextChunk = imageInput.readInt();
|
||||||
long chunkLength = imageInput.readUnsignedInt();
|
long chunkLength = imageInput.readUnsignedInt();
|
||||||
@ -482,12 +476,11 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
switch (nextChunk) {
|
switch (nextChunk) {
|
||||||
case WebP.CHUNK_ALPH:
|
case WebP.CHUNK_ALPH:
|
||||||
readAlpha(destination, param, width, height);
|
readAlpha(destination, param, width, height);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WebP.CHUNK_VP8_:
|
case WebP.CHUNK_VP8_:
|
||||||
readVP8(RasterUtils.asByteRaster(destination.getRaster())
|
readVP8(RasterUtils.asByteRaster(destination.getRaster())
|
||||||
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[]{ 0, 1, 2}), param);
|
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[] {0, 1, 2}), param);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WebP.CHUNK_VP8L:
|
case WebP.CHUNK_VP8L:
|
||||||
@ -519,8 +512,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
int reserved = (int) imageInput.readBits(2);
|
int reserved = (int) imageInput.readBits(2);
|
||||||
if (reserved != 0) {
|
if (reserved != 0) {
|
||||||
// Spec says SHOULD be 0
|
// Spec says SHOULD be 0
|
||||||
processWarningOccurred(
|
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
||||||
String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int preProcessing = (int) imageInput.readBits(2);
|
int preProcessing = (int) imageInput.readBits(2);
|
||||||
@ -539,15 +531,14 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
readUncompressedAlpha(alphaRaster);
|
readUncompressedAlpha(alphaRaster);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
// Simulate header
|
||||||
destination.getWidth(), destination.getHeight(), 4,
|
|
||||||
destination.getRaster().getBounds().getLocation());
|
|
||||||
//Simulate header
|
|
||||||
imageInput.seek(imageInput.getStreamPosition() - 5);
|
imageInput.seek(imageInput.getStreamPosition() - 5);
|
||||||
|
|
||||||
|
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, destination.getWidth(), destination.getHeight(), 4, null);
|
||||||
readVP8Lossless(tempRaster, param, width, height);
|
readVP8Lossless(tempRaster, param, width, height);
|
||||||
//Copy from green (band 1) in temp to alpha in destination
|
|
||||||
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(),
|
// Copy from green (band 1) in temp to alpha in destination
|
||||||
tempRaster.getHeight(), 0, 0, new int[] {1}));
|
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[] {1}));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
processWarningOccurred("Unknown WebP alpha compression: " + compression);
|
processWarningOccurred("Unknown WebP alpha compression: " + compression);
|
||||||
@ -571,34 +562,21 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
return 0;
|
return 0;
|
||||||
case AlphaFiltering.HORIZONTAL:
|
case AlphaFiltering.HORIZONTAL:
|
||||||
if (x == 0) {
|
if (x == 0) {
|
||||||
if (y == 0) {
|
return y == 0 ? 0 : alphaRaster.getSample(0, y - 1, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return alphaRaster.getSample(0, y - 1, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return alphaRaster.getSample(x - 1, y, 0);
|
return alphaRaster.getSample(x - 1, y, 0);
|
||||||
}
|
}
|
||||||
case AlphaFiltering.VERTICAL:
|
case AlphaFiltering.VERTICAL:
|
||||||
if (y == 0) {
|
if (y == 0) {
|
||||||
if (x == 0) {
|
return x == 0 ? 0 : alphaRaster.getSample(x - 1, 0, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return alphaRaster.getSample(x - 1, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return alphaRaster.getSample(x, y - 1, 0);
|
return alphaRaster.getSample(x, y - 1, 0);
|
||||||
}
|
}
|
||||||
case AlphaFiltering.GRADIENT:
|
case AlphaFiltering.GRADIENT:
|
||||||
if (x == 0 && y == 0) {
|
if (x == 0) {
|
||||||
return 0;
|
return y == 0 ? 0 : alphaRaster.getSample(0, y - 1, 0);
|
||||||
}
|
|
||||||
else if (x == 0) {
|
|
||||||
return alphaRaster.getSample(0, y - 1, 0);
|
|
||||||
}
|
}
|
||||||
else if (y == 0) {
|
else if (y == 0) {
|
||||||
return alphaRaster.getSample(x - 1, 0, 0);
|
return alphaRaster.getSample(x - 1, 0, 0);
|
||||||
@ -608,7 +586,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
int top = alphaRaster.getSample(x, y - 1, 0);
|
int top = alphaRaster.getSample(x, y - 1, 0);
|
||||||
int topLeft = alphaRaster.getSample(x - 1, y - 1, 0);
|
int topLeft = alphaRaster.getSample(x - 1, y - 1, 0);
|
||||||
|
|
||||||
return Math.max(0, Math.min(left + top - topLeft, 255));
|
return max(0, min(left + top - topLeft, 255));
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
processWarningOccurred("Unknown WebP alpha filtering: " + filtering);
|
processWarningOccurred("Unknown WebP alpha filtering: " + filtering);
|
||||||
@ -647,6 +625,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows")
|
||||||
private void readUncompressedAlpha(final WritableRaster alphaRaster) throws IOException {
|
private void readUncompressedAlpha(final WritableRaster alphaRaster) throws IOException {
|
||||||
// Hardly used in practice, need to find a sample file
|
// Hardly used in practice, need to find a sample file
|
||||||
processWarningOccurred("Uncompressed WebP alpha not implemented");
|
processWarningOccurred("Uncompressed WebP alpha not implemented");
|
||||||
|
@ -74,16 +74,13 @@ public final class VP8LDecoder {
|
|||||||
private final ImageInputStream imageInput;
|
private final ImageInputStream imageInput;
|
||||||
private final LSBBitReader lsbBitReader;
|
private final LSBBitReader lsbBitReader;
|
||||||
|
|
||||||
public VP8LDecoder(final ImageInputStream imageInput, final boolean debug) {
|
public VP8LDecoder(final ImageInputStream imageInput, @SuppressWarnings("unused") final boolean debug) {
|
||||||
this.imageInput = imageInput;
|
this.imageInput = imageInput;
|
||||||
lsbBitReader = new LSBBitReader(imageInput);
|
this.lsbBitReader = new LSBBitReader(imageInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readVP8Lossless(final WritableRaster raster, final boolean topLevel, ImageReadParam param, int width,
|
public void readVP8Lossless(final WritableRaster raster, final boolean topLevel, ImageReadParam param, int width, int height) throws IOException {
|
||||||
int height) throws IOException {
|
// Skip past already read parts of header (signature, width, height, alpha, version) 5 Bytes in total
|
||||||
//https://github.com/webmproject/libwebp/blob/666bd6c65483a512fe4c2eb63fbc198b6fb4fae4/src/dec/vp8l_dec.c#L1114
|
|
||||||
|
|
||||||
//Skip past already read parts of header (signature, width, height, alpha, version) 5 Bytes in total
|
|
||||||
if (topLevel) {
|
if (topLevel) {
|
||||||
imageInput.seek(imageInput.getStreamPosition() + 5);
|
imageInput.seek(imageInput.getStreamPosition() + 5);
|
||||||
}
|
}
|
||||||
@ -100,6 +97,7 @@ public final class VP8LDecoder {
|
|||||||
int colorCacheBits = 0;
|
int colorCacheBits = 0;
|
||||||
if (lsbBitReader.readBit() == 1) {
|
if (lsbBitReader.readBit() == 1) {
|
||||||
colorCacheBits = (int) lsbBitReader.readBits(4);
|
colorCacheBits = (int) lsbBitReader.readBits(4);
|
||||||
|
|
||||||
if (colorCacheBits < 1 || colorCacheBits > 11) {
|
if (colorCacheBits < 1 || colorCacheBits > 11) {
|
||||||
throw new IIOException("Corrupt WebP stream, colorCacheBits < 1 || > 11: " + colorCacheBits);
|
throw new IIOException("Corrupt WebP stream, colorCacheBits < 1 || > 11: " + colorCacheBits);
|
||||||
}
|
}
|
||||||
@ -116,16 +114,16 @@ public final class VP8LDecoder {
|
|||||||
|
|
||||||
WritableRaster fullSizeRaster;
|
WritableRaster fullSizeRaster;
|
||||||
WritableRaster decodeRaster;
|
WritableRaster decodeRaster;
|
||||||
if (topLevel) {
|
|
||||||
|
|
||||||
|
if (topLevel) {
|
||||||
Rectangle bounds = new Rectangle(width, height);
|
Rectangle bounds = new Rectangle(width, height);
|
||||||
fullSizeRaster = getRasterForDecoding(raster, param, bounds);
|
fullSizeRaster = getRasterForDecoding(raster, param, bounds);
|
||||||
|
|
||||||
//If multiple indices packed into one pixel xSize is different from raster width
|
// If multiple indices packed into one pixel xSize is different from raster width
|
||||||
decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null);
|
decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//All recursive calls have Rasters of the correct sizes with origin (0, 0)
|
// All recursive calls have Rasters of the correct sizes with origin (0, 0)
|
||||||
decodeRaster = fullSizeRaster = raster;
|
decodeRaster = fullSizeRaster = raster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +135,7 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fullSizeRaster != raster && param != null) {
|
if (fullSizeRaster != raster && param != null) {
|
||||||
//Copy into destination raster with settings applied
|
// Copy into destination raster with settings applied
|
||||||
Rectangle sourceRegion = param.getSourceRegion();
|
Rectangle sourceRegion = param.getSourceRegion();
|
||||||
int sourceXSubsampling = param.getSourceXSubsampling();
|
int sourceXSubsampling = param.getSourceXSubsampling();
|
||||||
int sourceYSubsampling = param.getSourceYSubsampling();
|
int sourceYSubsampling = param.getSourceYSubsampling();
|
||||||
@ -150,18 +148,17 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
|
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
|
||||||
//Only apply offset (and limit to requested region)
|
// Only apply offset (and limit to requested region)
|
||||||
raster.setRect(destinationOffset.x, destinationOffset.y, fullSizeRaster);
|
raster.setRect(destinationOffset.x, destinationOffset.y, fullSizeRaster);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//Manual copy, more efficient way might exist
|
// Manual copy, more efficient way might exist
|
||||||
byte[] rgba = new byte[4];
|
byte[] rgba = new byte[4];
|
||||||
int xEnd = raster.getWidth() + raster.getMinX();
|
int xEnd = raster.getWidth() + raster.getMinX();
|
||||||
int yEnd = raster.getHeight() + raster.getMinY();
|
int yEnd = raster.getHeight() + raster.getMinY();
|
||||||
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset;
|
|
||||||
xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
|
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset; xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
|
||||||
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset;
|
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset; yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
|
||||||
yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
|
|
||||||
fullSizeRaster.getDataElements(xSrc, ySrc, rgba);
|
fullSizeRaster.getDataElements(xSrc, ySrc, rgba);
|
||||||
raster.setDataElements(xDst, yDst, rgba);
|
raster.setDataElements(xDst, yDst, rgba);
|
||||||
}
|
}
|
||||||
@ -171,14 +168,14 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private WritableRaster getRasterForDecoding(WritableRaster raster, ImageReadParam param, Rectangle bounds) {
|
private WritableRaster getRasterForDecoding(WritableRaster raster, ImageReadParam param, Rectangle bounds) {
|
||||||
//If the ImageReadParam requires only a subregion of the image, and if the whole image does not fit into the
|
// If the ImageReadParam requires only a subregion of the image, and if the whole image does not fit into the
|
||||||
// Raster or subsampling is requested, we need a temporary Raster as we can only decode the whole image at once
|
// Raster or subsampling is requested, we need a temporary Raster as we can only decode the whole image at once
|
||||||
|
|
||||||
boolean originSet = false;
|
boolean originSet = false;
|
||||||
|
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
if (param.getSourceRegion() != null && !param.getSourceRegion().contains(bounds) ||
|
if (param.getSourceRegion() != null && !param.getSourceRegion().contains(bounds) ||
|
||||||
param.getSourceXSubsampling() != 1 || param.getSourceYSubsampling() != 1) {
|
param.getSourceXSubsampling() != 1 || param.getSourceYSubsampling() != 1) {
|
||||||
//Can't reuse existing
|
// Can't reuse existing
|
||||||
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height,
|
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height,
|
||||||
4 * bounds.width, 4, new int[] {0, 1, 2, 3}, null);
|
4 * bounds.width, 4, new int[] {0, 1, 2, 3}, null);
|
||||||
}
|
}
|
||||||
@ -189,12 +186,13 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!raster.getBounds().contains(bounds)) {
|
if (!raster.getBounds().contains(bounds)) {
|
||||||
//Can't reuse existing
|
// Can't reuse existing
|
||||||
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height, 4 * bounds.width,
|
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height, 4 * bounds.width,
|
||||||
4, new int[] {0, 1, 2, 3}, null);
|
4, new int[] {0, 1, 2, 3}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return originSet ?
|
return originSet ?
|
||||||
//Recenter to (0, 0)
|
// Recenter to (0, 0)
|
||||||
raster.createWritableChild(bounds.x, bounds.y, bounds.width, bounds.height, 0, 0, null) :
|
raster.createWritableChild(bounds.x, bounds.y, bounds.width, bounds.height, 0, 0, null) :
|
||||||
raster;
|
raster;
|
||||||
}
|
}
|
||||||
@ -210,47 +208,39 @@ public final class VP8LDecoder {
|
|||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
|
|
||||||
if ((x & huffmanMask) == 0 && huffmanInfo.huffmanMetaCodes != null) {
|
if ((x & huffmanMask) == 0 && huffmanInfo.huffmanMetaCodes != null) {
|
||||||
//Crossed border into new metaGroup
|
// Crossed border into new metaGroup
|
||||||
int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0);
|
int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0);
|
||||||
curCodeGroup = huffmanInfo.huffmanGroups[index];
|
curCodeGroup = huffmanInfo.huffmanGroups[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
short code = curCodeGroup.mainCode.readSymbol(lsbBitReader);
|
short code = curCodeGroup.mainCode.readSymbol(lsbBitReader);
|
||||||
|
|
||||||
if (code < 256) { //Literal
|
if (code < 256) { // Literal
|
||||||
decodeLiteral(raster, colorCache, curCodeGroup, rgba, y, x, code);
|
decodeLiteral(raster, colorCache, curCodeGroup, rgba, y, x, code);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (code < 256 + 24) { //backward reference
|
else if (code < 256 + 24) { // backward reference
|
||||||
|
|
||||||
int length = decodeBwRef(raster, colorCache, width, curCodeGroup, rgba, code, x, y);
|
int length = decodeBwRef(raster, colorCache, width, curCodeGroup, rgba, code, x, y);
|
||||||
|
|
||||||
//Decrement one because for loop already increments by one
|
// Decrement one because for loop already increments by one
|
||||||
x--;
|
x--;
|
||||||
y = y + ((x + length) / width);
|
y = y + ((x + length) / width);
|
||||||
x = (x + length) % width;
|
x = (x + length) % width;
|
||||||
|
|
||||||
|
// Reset Huffman meta group
|
||||||
//Reset Huffman meta group
|
|
||||||
if (y < height && x < width && huffmanInfo.huffmanMetaCodes != null) {
|
if (y < height && x < width && huffmanInfo.huffmanMetaCodes != null) {
|
||||||
int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0);
|
int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0);
|
||||||
curCodeGroup = huffmanInfo.huffmanGroups[index];
|
curCodeGroup = huffmanInfo.huffmanGroups[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else { //colorCache
|
else { // colorCache
|
||||||
decodeCached(raster, colorCache, rgba, y, x, code);
|
decodeCached(raster, colorCache, rgba, y, x, code);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeCached(WritableRaster raster, ColorCache colorCache, byte[] rgba, int y, int x, short code) {
|
private void decodeCached(WritableRaster raster, ColorCache colorCache, byte[] rgba, int y, int x, short code) {
|
||||||
|
|
||||||
int argb = colorCache.lookup(code - 256 - 24);
|
int argb = colorCache.lookup(code - 256 - 24);
|
||||||
|
|
||||||
rgba[0] = (byte) ((argb >> 16) & 0xff);
|
rgba[0] = (byte) ((argb >> 16) & 0xff);
|
||||||
@ -265,11 +255,13 @@ public final class VP8LDecoder {
|
|||||||
byte red = (byte) curCodeGroup.redCode.readSymbol(lsbBitReader);
|
byte red = (byte) curCodeGroup.redCode.readSymbol(lsbBitReader);
|
||||||
byte blue = (byte) curCodeGroup.blueCode.readSymbol(lsbBitReader);
|
byte blue = (byte) curCodeGroup.blueCode.readSymbol(lsbBitReader);
|
||||||
byte alpha = (byte) curCodeGroup.alphaCode.readSymbol(lsbBitReader);
|
byte alpha = (byte) curCodeGroup.alphaCode.readSymbol(lsbBitReader);
|
||||||
|
|
||||||
rgba[0] = red;
|
rgba[0] = red;
|
||||||
rgba[1] = (byte) code;
|
rgba[1] = (byte) code;
|
||||||
rgba[2] = blue;
|
rgba[2] = blue;
|
||||||
rgba[3] = alpha;
|
rgba[3] = alpha;
|
||||||
raster.setDataElements(x, y, rgba);
|
raster.setDataElements(x, y, rgba);
|
||||||
|
|
||||||
if (colorCache != null) {
|
if (colorCache != null) {
|
||||||
colorCache.insert((alpha & 0xff) << 24 | (red & 0xff) << 16 | (code & 0xff) << 8 | (blue & 0xff));
|
colorCache.insert((alpha & 0xff) << 24 | (red & 0xff) << 16 | (code & 0xff) << 8 | (blue & 0xff));
|
||||||
}
|
}
|
||||||
@ -284,16 +276,15 @@ public final class VP8LDecoder {
|
|||||||
int xSrc, ySrc;
|
int xSrc, ySrc;
|
||||||
|
|
||||||
if (distanceCode > 120) {
|
if (distanceCode > 120) {
|
||||||
//Linear distance
|
// Linear distance
|
||||||
int distance = distanceCode - 120;
|
int distance = distanceCode - 120;
|
||||||
ySrc = y - (distance / width);
|
ySrc = y - (distance / width);
|
||||||
xSrc = x - (distance % width);
|
xSrc = x - (distance % width);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//See comment of distances array
|
// See comment of distances array
|
||||||
xSrc = x - (8 - (DISTANCES[distanceCode - 1] & 0xf));
|
xSrc = x - (8 - (DISTANCES[distanceCode - 1] & 0xf));
|
||||||
ySrc = y - (DISTANCES[distanceCode - 1] >> 4);
|
ySrc = y - (DISTANCES[distanceCode - 1] >> 4);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xSrc < 0) {
|
if (xSrc < 0) {
|
||||||
@ -306,14 +297,15 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int l = length; l > 0; x++, l--) {
|
for (int l = length; l > 0; x++, l--) {
|
||||||
//Check length and xSrc, ySrc not falling outside raster? (Should not occur if image is correct)
|
// Check length and xSrc, ySrc not falling outside raster? (Should not occur if image is correct)
|
||||||
|
|
||||||
if (x == width) {
|
if (x == width) {
|
||||||
x = 0;
|
x = 0;
|
||||||
y++;
|
y++;
|
||||||
}
|
}
|
||||||
|
|
||||||
raster.getDataElements(xSrc++, ySrc, rgba);
|
raster.getDataElements(xSrc++, ySrc, rgba);
|
||||||
raster.setDataElements(x, y, rgba);
|
raster.setDataElements(x, y, rgba);
|
||||||
|
|
||||||
if (xSrc == width) {
|
if (xSrc == width) {
|
||||||
xSrc = 0;
|
xSrc = 0;
|
||||||
ySrc++;
|
ySrc++;
|
||||||
@ -322,22 +314,21 @@ public final class VP8LDecoder {
|
|||||||
colorCache.insert((rgba[3] & 0xff) << 24 | (rgba[0] & 0xff) << 16 | (rgba[1] & 0xff) << 8 | (rgba[2] & 0xff));
|
colorCache.insert((rgba[3] & 0xff) << 24 | (rgba[0] & 0xff) << 16 | (rgba[1] & 0xff) << 8 | (rgba[2] & 0xff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int lz77decode(int prefixCode) throws IOException {
|
private int lz77decode(int prefixCode) throws IOException {
|
||||||
//According to specification
|
// According to specification
|
||||||
|
|
||||||
if (prefixCode < 4) {
|
if (prefixCode < 4) {
|
||||||
return prefixCode + 1;
|
return prefixCode + 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int extraBits = (prefixCode - 2) >> 1;
|
int extraBits = (prefixCode - 2) >> 1;
|
||||||
int offset = (2 + (prefixCode & 1)) << extraBits;
|
int offset = (2 + (prefixCode & 1)) << extraBits;
|
||||||
|
|
||||||
return offset + (int) lsbBitReader.readBits(extraBits) + 1;
|
return offset + (int) lsbBitReader.readBits(extraBits) + 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readTransform(int xSize, int ySize, List<Transform> transforms) throws IOException {
|
private int readTransform(int xSize, int ySize, List<Transform> transforms) throws IOException {
|
||||||
@ -347,7 +338,7 @@ public final class VP8LDecoder {
|
|||||||
|
|
||||||
switch (transformType) {
|
switch (transformType) {
|
||||||
case TransformType.PREDICTOR_TRANSFORM:
|
case TransformType.PREDICTOR_TRANSFORM:
|
||||||
//Intentional Fallthrough
|
// Intentional Fallthrough
|
||||||
case TransformType.COLOR_TRANSFORM: {
|
case TransformType.COLOR_TRANSFORM: {
|
||||||
// The two first transforms contains the exact same data, can be combined
|
// The two first transforms contains the exact same data, can be combined
|
||||||
|
|
||||||
@ -360,7 +351,7 @@ public final class VP8LDecoder {
|
|||||||
new int[] {0, 1, 2, 3}, null);
|
new int[] {0, 1, 2, 3}, null);
|
||||||
readVP8Lossless(raster, false, null, blockWidth, blockHeight);
|
readVP8Lossless(raster, false, null, blockWidth, blockHeight);
|
||||||
|
|
||||||
//Keep data as raster for convenient (x,y) indexing
|
// Keep data as raster for convenient (x,y) indexing
|
||||||
if (transformType == TransformType.PREDICTOR_TRANSFORM) {
|
if (transformType == TransformType.PREDICTOR_TRANSFORM) {
|
||||||
transforms.add(0, new PredictorTransform(raster, sizeBits));
|
transforms.add(0, new PredictorTransform(raster, sizeBits));
|
||||||
}
|
}
|
||||||
@ -376,7 +367,6 @@ public final class VP8LDecoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransformType.COLOR_INDEXING_TRANSFORM: {
|
case TransformType.COLOR_INDEXING_TRANSFORM: {
|
||||||
|
|
||||||
// 8 bit value for color table size
|
// 8 bit value for color table size
|
||||||
int colorTableSize = ((int) lsbBitReader.readBits(8)) + 1; // 1-256
|
int colorTableSize = ((int) lsbBitReader.readBits(8)) + 1; // 1-256
|
||||||
|
|
||||||
@ -387,7 +377,6 @@ public final class VP8LDecoder {
|
|||||||
colorTableSize > 4 ? 16 :
|
colorTableSize > 4 ? 16 :
|
||||||
colorTableSize > 2 ? 4 : 2;
|
colorTableSize > 2 ? 4 : 2;
|
||||||
|
|
||||||
|
|
||||||
byte[] colorTable = new byte[safeColorTableSize * 4];
|
byte[] colorTable = new byte[safeColorTableSize * 4];
|
||||||
|
|
||||||
// The color table can be obtained by reading an image,
|
// The color table can be obtained by reading an image,
|
||||||
@ -398,12 +387,10 @@ public final class VP8LDecoder {
|
|||||||
readVP8Lossless(
|
readVP8Lossless(
|
||||||
Raster.createInterleavedRaster(
|
Raster.createInterleavedRaster(
|
||||||
new DataBufferByte(colorTable, colorTableSize * 4),
|
new DataBufferByte(colorTable, colorTableSize * 4),
|
||||||
colorTableSize, 1, colorTableSize * 4,
|
colorTableSize, 1, colorTableSize * 4, 4, new int[] {0, 1, 2, 3}, null),
|
||||||
4, new int[] {0, 1, 2, 3}, null)
|
false, null, colorTableSize, 1);
|
||||||
, false, null, colorTableSize, 1);
|
|
||||||
|
|
||||||
|
// resolve subtraction code
|
||||||
//resolve subtraction code
|
|
||||||
for (int i = 4; i < colorTable.length; i++) {
|
for (int i = 4; i < colorTable.length; i++) {
|
||||||
colorTable[i] += colorTable[i - 4];
|
colorTable[i] += colorTable[i - 4];
|
||||||
}
|
}
|
||||||
@ -428,8 +415,7 @@ public final class VP8LDecoder {
|
|||||||
return xSize;
|
return xSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HuffmanInfo readHuffmanCodes(int xSize, int ySize, int colorCacheBits, boolean readMetaCodes)
|
private HuffmanInfo readHuffmanCodes(int xSize, int ySize, int colorCacheBits, boolean readMetaCodes) throws IOException {
|
||||||
throws IOException {
|
|
||||||
int huffmanGroupNum = 1;
|
int huffmanGroupNum = 1;
|
||||||
int huffmanXSize;
|
int huffmanXSize;
|
||||||
int huffmanYSize;
|
int huffmanYSize;
|
||||||
@ -437,32 +423,30 @@ public final class VP8LDecoder {
|
|||||||
int metaCodeBits = 0;
|
int metaCodeBits = 0;
|
||||||
|
|
||||||
WritableRaster huffmanMetaCodes = null;
|
WritableRaster huffmanMetaCodes = null;
|
||||||
|
|
||||||
if (readMetaCodes && lsbBitReader.readBit() == 1) {
|
if (readMetaCodes && lsbBitReader.readBit() == 1) {
|
||||||
//read in meta codes
|
// read in meta codes
|
||||||
metaCodeBits = (int) lsbBitReader.readBits(3) + 2;
|
metaCodeBits = (int) lsbBitReader.readBits(3) + 2;
|
||||||
huffmanXSize = subSampleSize(xSize, metaCodeBits);
|
huffmanXSize = subSampleSize(xSize, metaCodeBits);
|
||||||
huffmanYSize = subSampleSize(ySize, metaCodeBits);
|
huffmanYSize = subSampleSize(ySize, metaCodeBits);
|
||||||
|
|
||||||
//Raster with elements as BARG (only the RG components encode the meta group)
|
// Raster with elements as BARG (only the RG components encode the meta group)
|
||||||
WritableRaster packedRaster = Raster.createPackedRaster(DataBuffer.TYPE_INT, huffmanXSize, huffmanYSize,
|
WritableRaster packedRaster = Raster.createPackedRaster(DataBuffer.TYPE_INT, huffmanXSize, huffmanYSize,
|
||||||
new int[] {0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000}, null);
|
new int[] {0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000}, null);
|
||||||
readVP8Lossless(asByteRaster(packedRaster), false, null, huffmanXSize, huffmanYSize);
|
readVP8Lossless(asByteRaster(packedRaster), false, null, huffmanXSize, huffmanYSize);
|
||||||
|
|
||||||
int[] data = ((DataBufferInt) packedRaster.getDataBuffer()).getData();
|
int[] data = ((DataBufferInt) packedRaster.getDataBuffer()).getData();
|
||||||
//Max metaGroup is number of meta groups
|
// Max metaGroup is number of meta groups
|
||||||
int maxCode = Integer.MIN_VALUE;
|
int maxCode = Integer.MIN_VALUE;
|
||||||
for (int code : data) {
|
for (int code : data) {
|
||||||
maxCode = max(maxCode, code & 0xffff);
|
maxCode = max(maxCode, code & 0xffff);
|
||||||
}
|
}
|
||||||
huffmanGroupNum = maxCode + 1;
|
huffmanGroupNum = maxCode + 1;
|
||||||
|
|
||||||
/*
|
// New Raster with just RG components exposed as single band
|
||||||
New Raster with just RG components exposed as single band allowing simple access of metaGroupIndex with
|
// allowing simple access of metaGroupIndex with x,y lookup
|
||||||
x,y lookup
|
|
||||||
*/
|
|
||||||
huffmanMetaCodes = Raster.createPackedRaster(packedRaster.getDataBuffer(), huffmanXSize, huffmanYSize,
|
huffmanMetaCodes = Raster.createPackedRaster(packedRaster.getDataBuffer(), huffmanXSize, huffmanYSize,
|
||||||
huffmanXSize, new int[] {0xffff}, null);
|
huffmanXSize, new int[] {0xffff}, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HuffmanCodeGroup[] huffmanGroups = new HuffmanCodeGroup[huffmanGroupNum];
|
HuffmanCodeGroup[] huffmanGroups = new HuffmanCodeGroup[huffmanGroupNum];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user