From fafa58b7183f74fa34ea98ead16771e126ef8598 Mon Sep 17 00:00:00 2001 From: Simon Kammermeier Date: Mon, 29 Aug 2022 18:46:52 +0200 Subject: [PATCH] Implement actual decoding including resolving backward refs and cache --- .../plugins/webp/lossless/VP8LDecoder.java | 168 +++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) 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 08415316..3b4621e8 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 @@ -57,6 +57,25 @@ import static java.lang.Math.max; * @author Harald Kuhr */ public final class VP8LDecoder { + + /** + * Used for decoding backward references + * Upper 4Bits are y distance, lower 4 Bits are 8 minus x distance + */ + private final static byte[] DISTANCES = { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; private final ImageInputStream imageInput; private final LSBBitReader lsbBitReader; @@ -95,8 +114,155 @@ 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); + // Use the Huffman trees to decode the LZ77 encoded data. -// decodeImageData(raster, ) + decodeImage(writableChild, huffmanInfo, colorCache); + + } + + private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException { + int width = raster.getWidth(); + int height = raster.getHeight(); + + int huffmanMask = huffmanInfo.metaCodeBits == 0 ? -1 : ((1 << huffmanInfo.metaCodeBits) - 1); + HuffmanCodeGroup curCodeGroup = huffmanInfo.huffmanGroups[0]; + + byte[] rgba = new byte[4]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + if ((x & huffmanMask) == 0 && huffmanInfo.huffmanMetaCodes != null) { + //Crossed border into new metaGroup + int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0); + curCodeGroup = huffmanInfo.huffmanGroups[index]; + } + + short code = curCodeGroup.mainCode.readSymbol(lsbBitReader); + + if (code < 256) { //Literal + decodeLiteral(raster, colorCache, curCodeGroup, rgba, y, x, code); + + } + else if (code < 256 + 24) { //backward reference + + int length = decodeBwRef(raster, colorCache, width, curCodeGroup, rgba, code, x, y); + + //Decrement one because for loop already increments by one + x--; + y = y + ((x + length) / width); + x = (x + length) % width; + + + //Reset Huffman meta group + if (y < height && x < width && huffmanInfo.huffmanMetaCodes != null) { + int index = huffmanInfo.huffmanMetaCodes.getSample(x >> huffmanInfo.metaCodeBits, y >> huffmanInfo.metaCodeBits, 0); + curCodeGroup = huffmanInfo.huffmanGroups[index]; + } + + + } + else { //colorCache + //Color cache should never be null here + assert colorCache != null; + decodeCached(raster, colorCache, rgba, y, x, code); + + } + } + } + } + + private void decodeCached(WritableRaster raster, ColorCache colorCache, byte[] rgba, int y, int x, short code) { + + int argb = colorCache.lookup(code - 256 - 24); + + rgba[0] = (byte) ((argb >> 16) & 0xff); + rgba[1] = (byte) ((argb >> 8) & 0xff); + rgba[2] = (byte) (argb & 0xff); + rgba[3] = (byte) (argb >>> 24); + + raster.setDataElements(x, y, rgba); + } + + private void decodeLiteral(WritableRaster raster, ColorCache colorCache, HuffmanCodeGroup curCodeGroup, byte[] rgba, int y, int x, short code) throws IOException { + byte red = (byte) curCodeGroup.redCode.readSymbol(lsbBitReader); + byte blue = (byte) curCodeGroup.blueCode.readSymbol(lsbBitReader); + byte alpha = (byte) curCodeGroup.alphaCode.readSymbol(lsbBitReader); + rgba[0] = red; + rgba[1] = (byte) code; + rgba[2] = blue; + rgba[3] = alpha; + raster.setDataElements(x, y, rgba); + if (colorCache != null) { + colorCache.insert((alpha & 0xff) << 24 | (red & 0xff) << 16 | (code & 0xff) << 8 | (blue & 0xff)); + } + } + + private int decodeBwRef(WritableRaster raster, ColorCache colorCache, int width, HuffmanCodeGroup curCodeGroup, byte[] rgba, short code, int x, int y) throws IOException { + int length = lz77decode(code - 256); + + short distancePrefix = curCodeGroup.distanceCode.readSymbol(lsbBitReader); + int distanceCode = lz77decode(distancePrefix); + + int xSrc, ySrc; + + if (distanceCode > 120) { + //Linear distance + int distance = distanceCode - 120; + ySrc = y - (distance / width); + xSrc = x - (distance % width); + } + else { + //See comment of distances array + xSrc = x - (8 - (DISTANCES[distanceCode - 1] & 0xf)); + ySrc = y - (DISTANCES[distanceCode - 1] >> 4); + + } + + if (xSrc < 0) { + ySrc--; + xSrc += width; + } + else if (xSrc >= width) { + xSrc -= width; + ySrc++; + } + + for (int l = length; l > 0; x++, l--) { + //Check length and xSrc, ySrc not falling outside raster? (Should not occur if image is correct) + + if (x == width) { + x = 0; + y++; + } + raster.getDataElements(xSrc++, ySrc, rgba); + raster.setDataElements(x, y, rgba); + if (xSrc == width) { + xSrc = 0; + ySrc++; + } + if (colorCache != null) { + colorCache.insert((rgba[3] & 0xff) << 24 | (rgba[0] & 0xff) << 16 | (rgba[1] & 0xff) << 8 | (rgba[2] & 0xff)); + } + } + return length; + } + + private int lz77decode(int prefixCode) throws IOException { + //According to specification + + if (prefixCode < 4) { + return prefixCode + 1; + } + else { + int extraBits = (prefixCode - 2) >> 1; + int offset = (2 + (prefixCode & 1)) << extraBits; + return offset + (int) lsbBitReader.readBits(extraBits) + 1; + + } + } private int readTransform(int xSize, int ySize, List transforms) throws IOException {