Support ImageReadParam Settings limiting Raster size

On animation frames dimension has to be passed as it is not guaranteed
the same as in the file header.
This commit is contained in:
Simon Kammermeier 2022-09-09 00:42:55 +02:00
parent 67b48ce1e3
commit c858454c5a
2 changed files with 107 additions and 21 deletions

View File

@ -427,7 +427,7 @@ final class WebPImageReader extends ImageReaderBase {
AnimationFrame frame = frames.get(imageIndex);
imageInput.seek(frame.offset + 16);
opaqueAlpha(destination.getAlphaRaster());
readVP8Extended(destination, param, frame.offset + frame.length);
readVP8Extended(destination, param, frame.offset + frame.length, frame.bounds.width, frame.bounds.height);
}
else {
imageInput.seek(header.offset + header.length);
@ -453,6 +453,11 @@ final class WebPImageReader extends ImageReaderBase {
}
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd) throws IOException {
readVP8Extended(destination, param, streamEnd, header.width, header.height);
}
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width,
final int height) throws IOException {
while (imageInput.getStreamPosition() < streamEnd) {
int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt();
@ -466,7 +471,7 @@ final class WebPImageReader extends ImageReaderBase {
switch (nextChunk) {
case WebP.CHUNK_ALPH:
readAlpha(destination, param);
readAlpha(destination, param, width, height);
break;
@ -476,7 +481,7 @@ final class WebPImageReader extends ImageReaderBase {
break;
case WebP.CHUNK_VP8L:
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param);
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param, width, height);
break;
case WebP.CHUNK_ANIM:
@ -500,7 +505,7 @@ final class WebPImageReader extends ImageReaderBase {
}
}
private void readAlpha(BufferedImage destination, ImageReadParam param) throws IOException {
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
int reserved = (int) imageInput.readBits(2);
if (reserved != 0) {
// Spec says SHOULD be 0
@ -525,10 +530,11 @@ final class WebPImageReader extends ImageReaderBase {
break;
case 1:
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
destination.getWidth(), destination.getHeight(), 4, null);
destination.getWidth(), destination.getHeight(), 4,
destination.getRaster().getBounds().getLocation());
//Simulate header
imageInput.seek(imageInput.getStreamPosition() - 5);
readVP8Lossless(tempRaster, param);
readVP8Lossless(tempRaster, param, width, height);
//Copy from green (band 1) in temp to alpha in destination
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(),
tempRaster.getHeight(), 0, 0, new int[] {1}));
@ -638,8 +644,13 @@ final class WebPImageReader extends ImageReaderBase {
}
private void readVP8Lossless(final WritableRaster raster, final ImageReadParam param) throws IOException {
readVP8Lossless(raster, param, header.width, header.height);
}
private void readVP8Lossless(final WritableRaster raster, final ImageReadParam param,
final int width, final int height) throws IOException {
VP8LDecoder decoder = new VP8LDecoder(imageInput, DEBUG);
decoder.readVP8Lossless(raster, true);
decoder.readVP8Lossless(raster, true, param, width, height);
}
private void readVP8(final WritableRaster raster, final ImageReadParam param) throws IOException {

View File

@ -42,7 +42,9 @@ import com.twelvemonkeys.imageio.plugins.webp.lossless.transform.Transform;
import com.twelvemonkeys.imageio.plugins.webp.lossless.transform.TransformType;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.ArrayList;
@ -84,7 +86,8 @@ public final class VP8LDecoder {
lsbBitReader = new LSBBitReader(imageInput);
}
public void readVP8Lossless(final WritableRaster raster, final boolean topLevel) throws IOException {
public void readVP8Lossless(final WritableRaster raster, final boolean topLevel, ImageReadParam param, int width,
int height) throws IOException {
//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
@ -92,13 +95,12 @@ public final class VP8LDecoder {
imageInput.seek(imageInput.getStreamPosition() + 5);
}
int xSize = raster.getWidth();
int ySize = raster.getHeight();
int xSize = width;
// Read transforms
ArrayList<Transform> transforms = new ArrayList<>();
while (topLevel && lsbBitReader.readBit() == 1) {
xSize = readTransform(xSize, ySize, transforms);
xSize = readTransform(xSize, height, transforms);
}
// Read color cache size
@ -111,7 +113,7 @@ public final class VP8LDecoder {
}
// Read Huffman codes
HuffmanInfo huffmanInfo = readHuffmanCodes(xSize, ySize, colorCacheBits, topLevel);
HuffmanInfo huffmanInfo = readHuffmanCodes(xSize, height, colorCacheBits, topLevel);
ColorCache colorCache = null;
@ -119,16 +121,89 @@ public final class VP8LDecoder {
colorCache = new ColorCache(colorCacheBits);
}
//If multiple indices packed into one pixel xSize is different from raster width
WritableRaster writableChild = raster.createWritableChild(0, 0, xSize, ySize, 0, 0, null);
WritableRaster fullSizeRaster;
WritableRaster decodeRaster;
if (topLevel) {
// Use the Huffman trees to decode the LZ77 encoded data.
decodeImage(writableChild, huffmanInfo, colorCache);
Rectangle bounds = new Rectangle(width, height);
fullSizeRaster = getRasterForDecoding(raster, param, bounds);
for (Transform transform : transforms) {
transform.applyInverse(raster);
//If multiple indices packed into one pixel xSize is different from raster width
decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null);
}
else {
//All recursive calls have Rasters of the correct sizes with origin (0, 0)
decodeRaster = fullSizeRaster = raster;
}
// Use the Huffman trees to decode the LZ77 encoded data.
decodeImage(decodeRaster, huffmanInfo, colorCache);
for (Transform transform : transforms) {
transform.applyInverse(fullSizeRaster);
}
if (fullSizeRaster != raster && param != null) {
//Copy into destination raster with settings applied
Rectangle sourceRegion = param.getSourceRegion();
int sourceXSubsampling = param.getSourceXSubsampling();
int sourceYSubsampling = param.getSourceYSubsampling();
int subsamplingXOffset = param.getSubsamplingXOffset();
int subsamplingYOffset = param.getSubsamplingYOffset();
Point destinationOffset = param.getDestinationOffset();
if (sourceRegion == null) {
sourceRegion = raster.getBounds();
}
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
//Only apply offset (and limit to requested region)
raster.setRect(destinationOffset.x, destinationOffset.y, fullSizeRaster);
}
else {
//Manual copy, more efficient way might exist
byte[] rgba = new byte[4];
int xEnd = raster.getWidth() + raster.getMinX();
int yEnd = raster.getHeight() + raster.getMinY();
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset;
xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset;
yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
fullSizeRaster.getDataElements(xSrc, ySrc, rgba);
raster.setDataElements(xDst, yDst, rgba);
}
}
}
}
}
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
// Raster or subsampling is requested, we need a temporary Raster as we can only decode the whole image at once
boolean originSet = false;
if (param != null) {
if (param.getSourceRegion() != null && !param.getSourceRegion().contains(bounds) ||
param.getSourceXSubsampling() != 1 || param.getSourceYSubsampling() != 1) {
//Can't reuse existing
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height,
4 * bounds.width, 4, new int[] {0, 1, 2, 3}, null);
}
else {
bounds.setLocation(param.getDestinationOffset());
originSet = true;
}
}
if (!raster.getBounds().contains(bounds)) {
//Can't reuse existing
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height, 4 * bounds.width,
4, new int[] {0, 1, 2, 3}, null);
}
return originSet ?
//Recenter to (0, 0)
raster.createWritableChild(bounds.x, bounds.y, bounds.width, bounds.height, 0, 0, null) :
raster;
}
private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException {
@ -296,7 +371,7 @@ public final class VP8LDecoder {
WritableRaster raster =
Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, blockWidth, blockHeight, 4 * blockWidth, 4,
new int[] {0, 1, 2, 3}, null);
readVP8Lossless(raster, false);
readVP8Lossless(raster, false, null, blockWidth, blockHeight);
//Keep data as raster for convenient (x,y) indexing
if (transformType == TransformType.PREDICTOR_TRANSFORM) {
@ -342,7 +417,7 @@ public final class VP8LDecoder {
new DataBufferByte(colorTable, colorTableSize * 4),
colorTableSize, 1, colorTableSize * 4,
4, new int[] {0, 1, 2, 3}, null)
, false);
, false, null, colorTableSize, 1);
//resolve subtraction code
@ -388,7 +463,7 @@ public final class VP8LDecoder {
//Raster with elements as BARG (only the RG components encode the meta group)
WritableRaster packedRaster = Raster.createPackedRaster(DataBuffer.TYPE_INT, huffmanXSize, huffmanYSize,
new int[] {0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000}, null);
readVP8Lossless(asByteRaster(packedRaster), false);
readVP8Lossless(asByteRaster(packedRaster), false, null, huffmanXSize, huffmanYSize);
int[] data = ((DataBufferInt) packedRaster.getDataBuffer()).getData();
//Max metaGroup is number of meta groups