diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java index 277542d2..edaea979 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java @@ -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 { diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java index f88b04aa..12a818bd 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java @@ -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 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