mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
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:
parent
67b48ce1e3
commit
c858454c5a
@ -427,7 +427,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
AnimationFrame frame = frames.get(imageIndex);
|
AnimationFrame frame = frames.get(imageIndex);
|
||||||
imageInput.seek(frame.offset + 16);
|
imageInput.seek(frame.offset + 16);
|
||||||
opaqueAlpha(destination.getAlphaRaster());
|
opaqueAlpha(destination.getAlphaRaster());
|
||||||
readVP8Extended(destination, param, frame.offset + frame.length);
|
readVP8Extended(destination, param, frame.offset + frame.length, frame.bounds.width, frame.bounds.height);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
imageInput.seek(header.offset + header.length);
|
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 {
|
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) {
|
while (imageInput.getStreamPosition() < streamEnd) {
|
||||||
int nextChunk = imageInput.readInt();
|
int nextChunk = imageInput.readInt();
|
||||||
long chunkLength = imageInput.readUnsignedInt();
|
long chunkLength = imageInput.readUnsignedInt();
|
||||||
@ -466,7 +471,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
switch (nextChunk) {
|
switch (nextChunk) {
|
||||||
case WebP.CHUNK_ALPH:
|
case WebP.CHUNK_ALPH:
|
||||||
readAlpha(destination, param);
|
readAlpha(destination, param, width, height);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -476,7 +481,7 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WebP.CHUNK_VP8L:
|
case WebP.CHUNK_VP8L:
|
||||||
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param);
|
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param, width, height);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WebP.CHUNK_ANIM:
|
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);
|
int reserved = (int) imageInput.readBits(2);
|
||||||
if (reserved != 0) {
|
if (reserved != 0) {
|
||||||
// Spec says SHOULD be 0
|
// Spec says SHOULD be 0
|
||||||
@ -525,10 +530,11 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
||||||
destination.getWidth(), destination.getHeight(), 4, null);
|
destination.getWidth(), destination.getHeight(), 4,
|
||||||
|
destination.getRaster().getBounds().getLocation());
|
||||||
//Simulate header
|
//Simulate header
|
||||||
imageInput.seek(imageInput.getStreamPosition() - 5);
|
imageInput.seek(imageInput.getStreamPosition() - 5);
|
||||||
readVP8Lossless(tempRaster, param);
|
readVP8Lossless(tempRaster, param, width, height);
|
||||||
//Copy from green (band 1) in temp to alpha in destination
|
//Copy from green (band 1) in temp to alpha in destination
|
||||||
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(),
|
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(),
|
||||||
tempRaster.getHeight(), 0, 0, new int[] {1}));
|
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 {
|
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);
|
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 {
|
private void readVP8(final WritableRaster raster, final ImageReadParam param) throws IOException {
|
||||||
|
@ -42,7 +42,9 @@ import com.twelvemonkeys.imageio.plugins.webp.lossless.transform.Transform;
|
|||||||
import com.twelvemonkeys.imageio.plugins.webp.lossless.transform.TransformType;
|
import com.twelvemonkeys.imageio.plugins.webp.lossless.transform.TransformType;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -84,7 +86,8 @@ public final class VP8LDecoder {
|
|||||||
lsbBitReader = new LSBBitReader(imageInput);
|
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
|
//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
|
//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);
|
imageInput.seek(imageInput.getStreamPosition() + 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
int xSize = raster.getWidth();
|
int xSize = width;
|
||||||
int ySize = raster.getHeight();
|
|
||||||
|
|
||||||
// Read transforms
|
// Read transforms
|
||||||
ArrayList<Transform> transforms = new ArrayList<>();
|
ArrayList<Transform> transforms = new ArrayList<>();
|
||||||
while (topLevel && lsbBitReader.readBit() == 1) {
|
while (topLevel && lsbBitReader.readBit() == 1) {
|
||||||
xSize = readTransform(xSize, ySize, transforms);
|
xSize = readTransform(xSize, height, transforms);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read color cache size
|
// Read color cache size
|
||||||
@ -111,7 +113,7 @@ public final class VP8LDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read Huffman codes
|
// Read Huffman codes
|
||||||
HuffmanInfo huffmanInfo = readHuffmanCodes(xSize, ySize, colorCacheBits, topLevel);
|
HuffmanInfo huffmanInfo = readHuffmanCodes(xSize, height, colorCacheBits, topLevel);
|
||||||
|
|
||||||
ColorCache colorCache = null;
|
ColorCache colorCache = null;
|
||||||
|
|
||||||
@ -119,16 +121,89 @@ public final class VP8LDecoder {
|
|||||||
colorCache = new ColorCache(colorCacheBits);
|
colorCache = new ColorCache(colorCacheBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WritableRaster fullSizeRaster;
|
||||||
|
WritableRaster decodeRaster;
|
||||||
|
if (topLevel) {
|
||||||
|
|
||||||
|
Rectangle bounds = new Rectangle(width, height);
|
||||||
|
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
|
||||||
WritableRaster writableChild = raster.createWritableChild(0, 0, xSize, ySize, 0, 0, null);
|
decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null);
|
||||||
|
}
|
||||||
// Use the Huffman trees to decode the LZ77 encoded data.
|
else {
|
||||||
decodeImage(writableChild, huffmanInfo, colorCache);
|
//All recursive calls have Rasters of the correct sizes with origin (0, 0)
|
||||||
|
decodeRaster = fullSizeRaster = raster;
|
||||||
for (Transform transform : transforms) {
|
|
||||||
transform.applyInverse(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 {
|
private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException {
|
||||||
@ -296,7 +371,7 @@ public final class VP8LDecoder {
|
|||||||
WritableRaster raster =
|
WritableRaster raster =
|
||||||
Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, blockWidth, blockHeight, 4 * blockWidth, 4,
|
Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, blockWidth, blockHeight, 4 * blockWidth, 4,
|
||||||
new int[] {0, 1, 2, 3}, null);
|
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
|
//Keep data as raster for convenient (x,y) indexing
|
||||||
if (transformType == TransformType.PREDICTOR_TRANSFORM) {
|
if (transformType == TransformType.PREDICTOR_TRANSFORM) {
|
||||||
@ -342,7 +417,7 @@ public final class VP8LDecoder {
|
|||||||
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);
|
, false, null, colorTableSize, 1);
|
||||||
|
|
||||||
|
|
||||||
//resolve subtraction code
|
//resolve subtraction code
|
||||||
@ -388,7 +463,7 @@ public final class VP8LDecoder {
|
|||||||
//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);
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user