From 2376d16ffd36944f237d933fad74d9cfb98b73cd Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 22 Nov 2020 13:07:58 +0100 Subject: [PATCH] WebP initial commit --- imageio/imageio-webp/pom.xml | 32 + .../imageio/plugins/webp/GenericChunk.java | 41 + .../imageio/plugins/webp/LSBBitReader.java | 56 + .../imageio/plugins/webp/RIFFChunk.java | 71 ++ .../imageio/plugins/webp/RasterUtils.java | 90 ++ .../imageio/plugins/webp/VP8xChunk.java | 68 + .../imageio/plugins/webp/WebP.java | 53 + .../plugins/webp/WebPImageMetadata.java | 184 +++ .../imageio/plugins/webp/WebPImageReader.java | 514 ++++++++ .../plugins/webp/WebPImageReaderSpi.java | 100 ++ .../plugins/webp/WebPProviderInfo.java | 57 + .../plugins/webp/lossless/ColorCache.java | 81 ++ .../plugins/webp/lossless/PredictorMode.java | 60 + .../plugins/webp/lossless/Transform.java | 55 + .../plugins/webp/lossless/TransformType.java | 45 + .../plugins/webp/lossless/VP8LDecoder.java | 365 ++++++ .../imageio/plugins/webp/vp8/BoolDecoder.java | 144 +++ .../imageio/plugins/webp/vp8/DeltaQ.java | 37 + .../imageio/plugins/webp/vp8/Globals.java | 822 ++++++++++++ .../imageio/plugins/webp/vp8/IDCT.java | 153 +++ .../imageio/plugins/webp/vp8/LoopFilter.java | 640 ++++++++++ .../imageio/plugins/webp/vp8/MacroBlock.java | 840 ++++++++++++ .../imageio/plugins/webp/vp8/Segment.java | 44 + .../plugins/webp/vp8/SegmentQuant.java | 122 ++ .../plugins/webp/vp8/SegmentQuants.java | 117 ++ .../imageio/plugins/webp/vp8/SubBlock.java | 629 +++++++++ .../imageio/plugins/webp/vp8/VP8Frame.java | 1131 +++++++++++++++++ .../services/javax.imageio.spi.ImageReaderSpi | 1 + .../plugins/webp/WebPImageMetadataTest.java | 317 +++++ .../plugins/webp/WebPImageReaderTest.java | 61 + .../plugins/webp/WebPProviderInfoTest.java | 18 + .../src/test/resources/webp/1.webp | Bin 0 -> 30320 bytes .../src/test/resources/webp/1_webp_a.webp | Bin 0 -> 18840 bytes .../src/test/resources/webp/1_webp_ll.webp | Bin 0 -> 81978 bytes .../src/test/resources/webp/5.webp | Bin 0 -> 82698 bytes .../webp/animated-webp-supported.webp | Bin 0 -> 37342 bytes .../src/test/resources/webp/bug3.webp | Bin 0 -> 954 bytes .../src/test/resources/webp/segment01.webp | Bin 0 -> 7658 bytes .../src/test/resources/webp/segment02.webp | Bin 0 -> 7112 bytes .../src/test/resources/webp/segment03.webp | Bin 0 -> 5470 bytes .../src/test/resources/webp/small_13x1.webp | Bin 0 -> 106 bytes .../src/test/resources/webp/small_1x1.webp | Bin 0 -> 94 bytes .../src/test/resources/webp/small_1x13.webp | Bin 0 -> 106 bytes .../src/test/resources/webp/small_31x13.webp | Bin 0 -> 262 bytes .../src/test/resources/webp/test.webp | Bin 0 -> 4928 bytes .../src/test/resources/webp/very_short.webp | Bin 0 -> 86 bytes imageio/pom.xml | 1 + 47 files changed, 6949 insertions(+) create mode 100644 imageio/imageio-webp/pom.xml create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/GenericChunk.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RIFFChunk.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RasterUtils.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/VP8xChunk.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebP.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderSpi.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfo.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorCache.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorMode.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/Transform.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/BoolDecoder.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/DeltaQ.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Globals.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/IDCT.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/LoopFilter.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/MacroBlock.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Segment.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuant.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuants.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SubBlock.java create mode 100644 imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/VP8Frame.java create mode 100644 imageio/imageio-webp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi create mode 100644 imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java create mode 100644 imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderTest.java create mode 100644 imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfoTest.java create mode 100644 imageio/imageio-webp/src/test/resources/webp/1.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/1_webp_a.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/1_webp_ll.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/5.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/animated-webp-supported.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/bug3.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/segment01.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/segment02.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/segment03.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/small_13x1.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/small_1x1.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/small_1x13.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/small_31x13.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/test.webp create mode 100644 imageio/imageio-webp/src/test/resources/webp/very_short.webp diff --git a/imageio/imageio-webp/pom.xml b/imageio/imageio-webp/pom.xml new file mode 100644 index 00000000..673448b8 --- /dev/null +++ b/imageio/imageio-webp/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + + com.twelvemonkeys.imageio + imageio + 3.7-SNAPSHOT + + + imageio-webp + TwelveMonkeys :: ImageIO :: WebP plugin + + ImageIO plugin for Google WebP File Format (WebP). + + + + + com.twelvemonkeys.imageio + imageio-core + + + com.twelvemonkeys.imageio + imageio-metadata + + + com.twelvemonkeys.imageio + imageio-core + test-jar + + + diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/GenericChunk.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/GenericChunk.java new file mode 100644 index 00000000..bff35010 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/GenericChunk.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +/** + * A generic RIFF chunk + */ +final class GenericChunk extends RIFFChunk { + GenericChunk(int fourCC, int length, long offset) { + super(fourCC, length, offset); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java new file mode 100644 index 00000000..7f03b092 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/LSBBitReader.java @@ -0,0 +1,56 @@ +package com.twelvemonkeys.imageio.plugins.webp; + +import javax.imageio.stream.ImageInputStream; +import java.io.EOFException; +import java.io.IOException; + +/** + * LSBBitReader + */ +public final class LSBBitReader { + // TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)? + + private final ImageInputStream imageInput; + int bitOffset = 0; + + public LSBBitReader(ImageInputStream imageInput) { + this.imageInput = imageInput; + } + + // TODO: Optimize this... Read many bits at once! + public long readBits(int bits) throws IOException { + long result = 0; + for (int i = 0; i < bits; i++) { + result |= readBit() << i; + } + + return result; + } + + // TODO: Optimize this... + // TODO: Consider not reading value over and over.... + public int readBit() throws IOException { + int bit = 7 - bitOffset; + + imageInput.setBitOffset(bit); + + // Compute final bit offset before we call read() and seek() + int newBitOffset = (bitOffset + 1) & 0x7; + + int val = imageInput.read(); + if (val == -1) { + throw new EOFException(); + } + + if (newBitOffset != 0) { + // Move byte position back if in the middle of a byte + // NOTE: RESETS bit offset! + imageInput.seek(imageInput.getStreamPosition() - 1); + } + + bitOffset = newBitOffset; + + // Shift the bit to be read to the rightmost position + return (val >> (7 - bit)) & 0x1; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RIFFChunk.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RIFFChunk.java new file mode 100644 index 00000000..624ac254 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RIFFChunk.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +import static com.twelvemonkeys.imageio.plugins.webp.WebPImageReader.fourCC; + +/** + * An abstract RIFF chunk. + *

+ * RIFF was introduced in 1991 by Microsoft and IBM, and was presented + * by Microsoft as the default format for Windows 3.1 multimedia files. It is + * based on Electronic Arts' Interchange File Format, introduced in 1985 on + * the Commodore Amiga, the only difference being that multi-byte integers are + * in little-endian format, native to the 80x86 processor series used in + * IBM PCs, rather than the big-endian format native to the 68k processor + * series used in Amiga and Apple Macintosh computers, where IFF files were + * heavily used. + *

+ * In 2010 Google introduced the WebP picture format, which uses RIFF as a + * container. + * + * @see Resource Interchange Format + */ +abstract class RIFFChunk { + + final int fourCC; + final long length; + final long offset; + + RIFFChunk(int fourCC, long length, long offset) { + this.fourCC = fourCC; + this.length = length; + this.offset = offset; + } + + @Override + public String toString() { + return fourCC(fourCC).replace(' ', '_') + + "Chunk@" + offset + "|" + length; + } + +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RasterUtils.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RasterUtils.java new file mode 100644 index 00000000..2f73dd39 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/RasterUtils.java @@ -0,0 +1,90 @@ +package com.twelvemonkeys.imageio.plugins.webp; + +import java.awt.*; +import java.awt.image.*; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * RasterUtils + */ +public final class RasterUtils { + // TODO: Generalize and move to common util package + + private RasterUtils() {} + + public static WritableRaster asByteRaster(final WritableRaster raster, final ColorModel colorModel) { + switch (raster.getTransferType()) { + case DataBuffer.TYPE_BYTE: + return raster; + case DataBuffer.TYPE_INT: + final int bands = colorModel.getNumComponents(); + final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); + + int w = raster.getWidth(); + int h = raster.getHeight(); + int size = buffer.getSize(); + + return new WritableRaster( + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, bands, w * bands, createBandOffsets(colorModel)), + new DataBuffer(DataBuffer.TYPE_BYTE, size * bands) { + // TODO: These masks should probably not be hardcoded + final int[] MASKS = { + 0xffffff00, + 0xffff00ff, + 0xff00ffff, + 0x00ffffff, + }; + + @Override + public int getElem(int bank, int i) { + int index = i / bands; + int shift = (i % bands) * 8; + + return (buffer.getElem(index) >>> shift) & 0xff; + } + + @Override + public void setElem(int bank, int i, int val) { + int index = i / bands; + int element = i % bands; + int shift = element * 8; + + int value = (buffer.getElem(index) & MASKS[element]) | ((val & 0xff) << shift); + buffer.setElem(index, value); + } + }, new Point()) {}; + default: + throw new IllegalArgumentException(String.format("Raster type %d not supported", raster.getTransferType())); + } + } + + private static int[] createBandOffsets(final ColorModel colorModel) { + notNull(colorModel, "colorModel"); + + if (colorModel instanceof DirectColorModel) { + DirectColorModel dcm = (DirectColorModel) colorModel; + int[] masks = dcm.getMasks(); + int[] offs = new int[masks.length]; + + for (int i = 0; i < masks.length; i++) { + int mask = masks[i]; + int off = 0; + + // TODO: FixMe! This only works for standard 8 bit masks (0xFF) + if (mask != 0) { + while ((mask & 0xFF) == 0) { + mask >>>= 8; + off++; + } + } + + offs[i] = off; + } + + return offs; + } + + throw new IllegalArgumentException(String.format("%s not supported", colorModel.getClass().getSimpleName())); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/VP8xChunk.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/VP8xChunk.java new file mode 100644 index 00000000..3abe30d3 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/VP8xChunk.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +/** + * A special VP8* RIFF chunk, used as the first chunk of a WebP file. + */ +final class VP8xChunk extends RIFFChunk { + int width; + int height; + + boolean isLossless; + + boolean containsICCP; + boolean containsALPH; + boolean containsEXIF; + boolean containsXMP_; + boolean containsANIM; + + VP8xChunk(int fourCC, long length, long offset) { + super(fourCC, length, offset); + } + + @Override + public String toString() { + return super.toString() + "[" + + "width=" + width + + ", height=" + height + + ", lossless=" + (isLossless ? "RGB" : "") + + (containsALPH ? "A" : (isLossless ? "" : "false")) + + ", flags=" + + (containsICCP ? "I" : "") + + (containsALPH ? "L" : "") + + (containsEXIF ? "E" : "") + + (containsXMP_ ? "X" : "") + + (containsANIM ? "A" : "") + + ']'; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebP.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebP.java new file mode 100644 index 00000000..a60b5eef --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebP.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +/** + * WebP + */ +interface WebP { + int RIFF_MAGIC = 'R' | 'I' << 8 | 'F' << 16 | 'F' << 24; + int WEBP_MAGIC = 'W' | 'E' << 8 | 'B' << 16 | 'P' << 24; + + int CHUNK_VP8_ = 'V' | 'P' << 8 | '8' << 16 | ' ' << 24; + int CHUNK_VP8L = 'V' | 'P' << 8 | '8' << 16 | 'L' << 24; + int CHUNK_VP8X = 'V' | 'P' << 8 | '8' << 16 | 'X' << 24; + + int CHUNK_ALPH = 'A' | 'L' << 8 | 'P' << 16 | 'H' << 24; + int CHUNK_ANIM = 'A' | 'N' << 8 | 'I' << 16 | 'M' << 24; + int CHUNK_ANMF = 'A' | 'N' << 8 | 'M' << 16 | 'F' << 24; + int CHUNK_ICCP = 'I' | 'C' << 8 | 'C' << 16 | 'P' << 24; + int CHUNK_EXIF = 'E' | 'X' << 8 | 'I' << 16 | 'F' << 24; + int CHUNK_XMP_ = 'X' | 'M' << 8 | 'P' << 16 | ' ' << 24; + + byte LOSSLESSS_SIG = 0x2f; +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java new file mode 100644 index 00000000..21e47bea --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.AbstractMetadata; + +import javax.imageio.metadata.IIOMetadataNode; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * WebPMetadata + */ +final class WebPImageMetadata extends AbstractMetadata { + private final VP8xChunk header; + + WebPImageMetadata(final VP8xChunk header) { + this.header = notNull(header, "header"); + } + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + + IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); + chroma.appendChild(csType); + csType.setAttribute("name", "RGB"); + + // NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data) + IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); + chroma.appendChild(numChannels); + numChannels.setAttribute("value", Integer.toString(header.containsALPH ? 4 : 3)); + + IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); + chroma.appendChild(blackIsZero); + blackIsZero.setAttribute("value", "TRUE"); + + return chroma; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode node = new IIOMetadataNode("Compression"); + + IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); + node.appendChild(compressionTypeName); + + String value = header.isLossless ? "VP8L" : "VP8"; // TODO: Naming: VP8L and VP8 or WebP and WebP Lossless? + compressionTypeName.setAttribute("value", value); + + // TODO: VP8 + lossless alpha! + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + node.appendChild(lossless); + lossless.setAttribute("value", header.isLossless ? "TRUE" : "FALSE"); + + return node; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode node = new IIOMetadataNode("Data"); + + // TODO: WebP seems to support planar as well? + IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); + node.appendChild(planarConfiguration); + planarConfiguration.setAttribute("value", "PixelInterleaved"); + + IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); + node.appendChild(sampleFormat); + sampleFormat.setAttribute("value", "UnsignedIntegral"); + + IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); + node.appendChild(bitsPerSample); + + bitsPerSample.setAttribute("value", createListValue(header.containsALPH ? 4 : 3, Integer.toString(8))); + + return node; + } + + private String createListValue(final int itemCount, final String... values) { + StringBuilder buffer = new StringBuilder(); + + for (int i = 0; i < itemCount; i++) { + if (buffer.length() > 0) { + buffer.append(' '); + } + + buffer.append(values[i % values.length]); + } + + return buffer.toString(); + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + + IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); + dimension.appendChild(imageOrientation); + imageOrientation.setAttribute("value", "Normal"); + + IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); + dimension.appendChild(pixelAspectRatio); + pixelAspectRatio.setAttribute("value", "1.0"); + + return dimension; + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document = new IIOMetadataNode("Document"); + + IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); + document.appendChild(formatVersion); + formatVersion.setAttribute("value", "1.0"); + + return document; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + IIOMetadataNode text = new IIOMetadataNode("Text"); + + // TODO: Get useful text nodes from EXIF or XMP + // NOTE: Names corresponds to equivalent fields in TIFF + + return text.hasChildNodes() ? text : null; + } + +// private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) { +// if (value != null) { +// IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry"); +// parent.appendChild(textEntry); +// textEntry.setAttribute("keyword", keyword); +// textEntry.setAttribute("value", value); +// } +// } + + // No tiling + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + if (header.containsALPH) { + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + transparency.appendChild(alpha); + alpha.setAttribute("value", "nonpremultiplied"); + return transparency; + } + + return null; + } + + // TODO: Define native WebP metadata format (probably use RIFF structure) +} 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 new file mode 100644 index 00000000..4338d580 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; +import com.twelvemonkeys.imageio.metadata.xmp.XMPReader; +import com.twelvemonkeys.imageio.plugins.webp.lossless.VP8LDecoder; +import com.twelvemonkeys.imageio.plugins.webp.vp8.VP8Frame; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; +import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import com.twelvemonkeys.imageio.util.ProgressListenerBase; + +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.color.ICC_Profile; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * WebPImageReader + */ +final class WebPImageReader extends ImageReaderBase { + + final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.webp.debug")); + + private LSBBitReader lsbBitReader; + + // Either VP8_, VP8L or VP8X chunk + private VP8xChunk header; + private ICC_Profile iccProfile; + + WebPImageReader(ImageReaderSpi provider) { + super(provider); + } + + @Override + protected void resetMembers() { + header = null; + iccProfile = null; + lsbBitReader = null; + } + + @Override + public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + + lsbBitReader = new LSBBitReader(imageInput); + } + + private void readHeader(int imageIndex) throws IOException { + checkBounds(imageIndex); + + // TODO: Consider just storing the chunks, parse until VP8, VP8L or VP8X chunk + if (header != null) { + return; + } + + // TODO: Generalize RIFF chunk parsing! + + // RIFF native order is Little Endian + imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + imageInput.seek(0); + + int riff = imageInput.readInt(); + if (riff != WebP.RIFF_MAGIC) { + throw new IIOException(String.format("Not a WebP file, invalid 'RIFF' magic: '%s'", fourCC(riff))); + } + + imageInput.readUnsignedInt(); // Skip file size NOTE: LITTLE endian! + + int webp = imageInput.readInt(); + if (webp != WebP.WEBP_MAGIC) { + throw new IIOException(String.format("Not a WebP file, invalid 'WEBP' magic: '%s'", fourCC(webp))); + } + + int chunk = imageInput.readInt(); + long chunkLen = imageInput.readUnsignedInt(); + + header = new VP8xChunk(chunk, chunkLen, imageInput.getStreamPosition()); + + switch (chunk) { + case WebP.CHUNK_VP8_: + //https://tools.ietf.org/html/rfc6386#section-9.1 + int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = interframe (not used in WebP) + + if (frameType != 0) { + throw new IIOException("Unexpected WebP frame type (expected 0): " + frameType); + } + + int versionNumber = (int) lsbBitReader.readBits(3); // 0 - 3 = different profiles (see spec) + int showFrame = lsbBitReader.readBit(); // 0 = don't show, 1 = show + + if (DEBUG) { + System.out.println("versionNumber: " + versionNumber); + System.out.println("showFrame: " + showFrame); + } + + // 19 bit field containing the size of the first data partition in bytes + lsbBitReader.readBits(19); + + // StartCode 0, 1, 2 + imageInput.readUnsignedByte(); + imageInput.readUnsignedByte(); + imageInput.readUnsignedByte(); + + // (2 bits Horizontal Scale << 14) | Width (14 bits) + int hBytes = imageInput.readUnsignedShort(); + header.width = (hBytes & 0x3fff); + + // (2 bits Vertical Scale << 14) | Height (14 bits) + int vBytes = imageInput.readUnsignedShort(); + header.height = (vBytes & 0x3fff); + + break; + + case WebP.CHUNK_VP8L: + byte signature = imageInput.readByte(); + if (signature != WebP.LOSSLESSS_SIG) { + throw new IIOException(String.format("Unexpected 'VP8L' signature, expected 0x0x%2x: 0x%2x", WebP.LOSSLESSS_SIG, signature)); + } + + header.isLossless = true; + + // 14 bit width, 14 bit height, 1 bit alpha, 3 bit version + header.width = 1 + (int) lsbBitReader.readBits(14); + header.height = 1 + (int) lsbBitReader.readBits(14); + header.containsALPH = lsbBitReader.readBit() == 1; + + int version = (int) lsbBitReader.readBits(3); + + if (version != 0) { + throw new IIOException(String.format("Unexpected 'VP8L' version, expected 0: %d", version)); + } + + break; + + case WebP.CHUNK_VP8X: + if (chunkLen != 10) { + throw new IIOException("Unexpected 'VP8X' chunk length, expected 10: " + chunkLen); + } + + // RsV|I|L|E|X|A|R + int reserved = (int) imageInput.readBits(2); + if (reserved != 0) { + // Spec says SHOULD be 0 + throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); + } + + header.containsICCP = imageInput.readBit() == 1; + header.containsALPH = imageInput.readBit() == 1; // L -> aLpha + header.containsEXIF = imageInput.readBit() == 1; + header.containsXMP_ = imageInput.readBit() == 1; + header.containsANIM = imageInput.readBit() == 1; // A -> Anim + + reserved = (int) imageInput.readBits(25); // 1 + 24 bits reserved + if (reserved != 0) { + // Spec says SHOULD be 0 + throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); + } + + // NOTE: Spec refers to this as *Canvas* size, as opposed to *Image* size for the lossless chunk + header.width = 1 + (int) lsbBitReader.readBits(24); + header.height = 1 + (int) lsbBitReader.readBits(24); + + if (header.containsICCP) { + // ICCP chunk must be first chunk, if present + while (iccProfile != null && imageInput.getStreamPosition() < imageInput.length()) { + int nextChunk = imageInput.readInt(); + long chunkLength = imageInput.readUnsignedInt(); + long chunkStart = imageInput.getStreamPosition(); + + if (nextChunk == WebP.CHUNK_ICCP) { + iccProfile = ICC_Profile.getInstance(IIOUtil.createStreamAdapter(imageInput, chunkLength)); + } + else { + processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk))); + } + + imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length + } + } + + break; + + default: + throw new IIOException(String.format("Unknown WebP chunk: '%s'", fourCC(chunk))); + } + + if (DEBUG) { + System.out.println("header: " + header); + } + } + + static String fourCC(int value) { + // NOTE: Little Endian + return new String( + new byte[]{ + (byte) ((value & 0x000000ff) ), + (byte) ((value & 0x0000ff00) >> 8), + (byte) ((value & 0x00ff0000) >> 16), + (byte) ((value & 0xff000000) >>> 24), + } + ); + } + + @Override + public int getNumImages(boolean allowSearch) throws IOException { + // TODO: Support ANIM/ANMF + return super.getNumImages(allowSearch); + } + + @Override + public int getWidth(int imageIndex) throws IOException { + readHeader(imageIndex); + return header.width; + } + + @Override + public int getHeight(int imageIndex) throws IOException { + readHeader(imageIndex); + return header.height; + } + + @Override + public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException { + readHeader(imageIndex); + + // TODO: Spec says: + // "alpha value is codified in bits 31..24, red in bits 23..16, green in bits 15..8 and blue in bits 7..0, + // but implementations of the format are free to use another representation internally." + // TODO: Doc says alpha flag is "hint only" :-P + // TODO: ICC profile?! + return ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + } + + @Override + public Iterator getImageTypes(int imageIndex) throws IOException { + ImageTypeSpecifier rawImageType = getRawImageType(imageIndex); + + List types = new ArrayList<>(); + types.add(rawImageType); + types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB)); + + return types.iterator(); + } + + @Override + public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { + int width = getWidth(imageIndex); + int height = getHeight(imageIndex); + BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height); + + processImageStarted(imageIndex); + + switch (header.fourCC) { + case WebP.CHUNK_VP8_: + imageInput.seek(header.offset); + readVP8(RasterUtils.asByteRaster(destination.getRaster(), destination.getColorModel())); + + break; + + case WebP.CHUNK_VP8L: + imageInput.seek(header.offset); + readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster(), destination.getColorModel())); + + break; + + case WebP.CHUNK_VP8X: + imageInput.seek(header.offset + header.length); + + while (imageInput.getStreamPosition() < imageInput.length()) { + int nextChunk = imageInput.readInt(); + long chunkLength = imageInput.readUnsignedInt(); + long chunkStart = imageInput.getStreamPosition(); + + if (DEBUG) { + System.out.printf("chunk: '%s'\n", fourCC(nextChunk)); + System.out.println("chunkLength: " + chunkLength); + System.out.println("chunkStart: " + chunkStart); + } + + switch (nextChunk) { + case WebP.CHUNK_ALPH: + int reserved = (int) imageInput.readBits(2); + if (reserved != 0) { + // Spec says SHOULD be 0 + throw new IIOException(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved)); + } + + int preProcessing = (int) imageInput.readBits(2); + int filtering = (int) imageInput.readBits(2); + int compression = (int) imageInput.readBits(2); + + if (DEBUG) { + System.out.println("preProcessing: " + preProcessing); + System.out.println("filtering: " + filtering); + System.out.println("compression: " + compression); + } + + switch (compression) { + case 0: + readUncompressedAlpha(destination.getAlphaRaster()); + break; + case 1: + opaqueAlpha(destination.getAlphaRaster()); // TODO: Remove when correctly implemented! + readVP8Lossless(destination.getAlphaRaster()); + break; + default: + processWarningOccurred("Unknown WebP alpha compression: " + compression); + opaqueAlpha(destination.getAlphaRaster()); + break; + } + + break; + + case WebP.CHUNK_VP8_: + readVP8(RasterUtils.asByteRaster(destination.getRaster(), destination.getColorModel()) + .createWritableChild(0, 0, width, height, 0, 0, new int[]{0, 1, 2})); + + break; + + case WebP.CHUNK_VP8L: + readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster(), destination.getColorModel())); + + break; + + case WebP.CHUNK_ANIM: + case WebP.CHUNK_ANMF: + processWarningOccurred("Ignoring unsupported chunk: " + fourCC(nextChunk)); + break; + default: + processWarningOccurred("Ignoring unexpected chunk: " + fourCC(nextChunk)); + break; + } + + imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length + } + + break; + + default: + throw new IIOException("Unknown first chunk for WebP: " + fourCC(header.fourCC)); + } + + if (abortRequested()) { + processReadAborted(); + } else { + processImageComplete(); + } + + return destination; + } + + private void opaqueAlpha(final WritableRaster alphaRaster) { + int h = alphaRaster.getHeight(); + int w = alphaRaster.getWidth(); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + alphaRaster.setSample(x, y, 0, 0xff); + } + } + } + + private void readUncompressedAlpha(final WritableRaster alphaRaster) throws IOException { + // Hardly used in practice, need to find a sample file + processWarningOccurred("Uncompressed WebP alpha not implemented"); + opaqueAlpha(alphaRaster); + } + + private void readVP8Lossless(final WritableRaster raster) throws IOException { + VP8LDecoder decoder = new VP8LDecoder(imageInput); + decoder.readVP8Lossless(raster, true); + } + + private void readVP8(final WritableRaster raster) throws IOException { + VP8Frame frame = new VP8Frame(imageInput); + + frame.setProgressListener(new ProgressListenerBase() { + @Override + public void imageProgress(ImageReader source, float percentageDone) { + processImageProgress(percentageDone); + } + }); + + // TODO: Consider merging these operations... + if (frame.decodeFrame(false)) { + frame.copyTo(raster); + } + } + + // Metadata + + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + readHeader(imageIndex); + // TODO: Read possible EXIF and 'XMP ' chunks + readMeta(); + + + return new WebPImageMetadata(header); + } + + private void readMeta() throws IOException { + if (header.containsEXIF || header.containsXMP_) { + // TODO: WebP spec says possible EXIF and XMP chunks are always AFTER image data + imageInput.seek(header.offset + header.length); + + while (imageInput.getStreamPosition() < imageInput.length()) { + int nextChunk = imageInput.readInt(); + long chunkLength = imageInput.readUnsignedInt(); + long chunkStart = imageInput.getStreamPosition(); + +// System.err.printf("chunk: '%s'\n", fourCC(nextChunk)); +// System.err.println("chunkLength: " + chunkLength); +// System.err.println("chunkStart: " + chunkStart); + + switch (nextChunk) { + case WebP.CHUNK_EXIF: + // TODO: Figure out if the following is correct + // The (only) sample image contains 'Exif\0\0', just like the JPEG APP1/Exif segment... + // However, I cannot see this documented anywhere? Probably this is bogus... + // For now, we'll support both cases for compatibility. + int skippedCount = 0; + byte[] bytes = new byte[6]; + imageInput.readFully(bytes); + if (bytes[0] == 'E' && bytes[1] == 'x' && bytes[2] == 'i' && bytes[3] == 'f' && bytes[4] == 0 && bytes[5] == 0) { + skippedCount = 6; + } + else { + imageInput.seek(chunkStart); + } + + SubImageInputStream input = new SubImageInputStream(imageInput, chunkLength - skippedCount); + Directory exif = new TIFFReader().read(input); + +// Entry jpegOffsetTag = exif.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT); +// if (jpegOffsetTag != null) { +// // Wohoo.. There's a JPEG inside the EIXF inside the WEBP... +// long jpegOffset = ((Number) jpegOffsetTag.getValue()).longValue(); +// input.seek(jpegOffset); +// BufferedImage thumb = ImageIO.read(new SubImageInputStream(input, chunkLength - jpegOffset)); +// System.err.println("thumb: " + thumb); +// showIt(thumb, "EXIF thumb"); +// } + + if (DEBUG) { + System.out.println("exif: " + exif); + } + + break; + + case WebP.CHUNK_XMP_: + Directory xmp = new XMPReader().read(new SubImageInputStream(imageInput, chunkLength)); + + if (DEBUG) { + System.out.println("xmp: " + xmp); + } + + break; + + default: + } + + imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length + } + } + } + + protected static void showIt(BufferedImage image, String title) { + ImageReaderBase.showIt(image, title); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderSpi.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderSpi.java new file mode 100644 index 00000000..ad51fa13 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderSpi.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; + +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * WebPImageReaderSpi + */ +public final class WebPImageReaderSpi extends ImageReaderSpiBase { + @SuppressWarnings("WeakerAccess") + public WebPImageReaderSpi() { + super(new WebPProviderInfo()); + } + + @Override + public boolean canDecodeInput(final Object source) throws IOException { + return source instanceof ImageInputStream && canDecode((ImageInputStream) source); + } + + private static boolean canDecode(final ImageInputStream stream) throws IOException { + ByteOrder originalOrder = stream.getByteOrder(); + stream.mark(); + + try { + // RIFF native order is Little Endian + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + + if (stream.readInt() != WebP.RIFF_MAGIC) { + return false; + } + + stream.readInt(); // Skip file size + + if (stream.readInt() != WebP.WEBP_MAGIC) { + return false; + } + + int chunk = stream.readInt(); + + switch (chunk) { + case WebP.CHUNK_VP8L: + case WebP.CHUNK_VP8X: + case WebP.CHUNK_VP8_: + return true; + default: + return false; + } + } + finally { + stream.setByteOrder(originalOrder); + stream.reset(); + } + } + + @Override + public ImageReader createReaderInstance(final Object extension) { + return new WebPImageReader(this); + } + + @Override + public String getDescription(final Locale locale) { + return "Google WebP File Format (WebP) Reader"; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfo.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfo.java new file mode 100644 index 00000000..3b80832e --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; + +/** + * WebPProviderInfo + */ +final class WebPProviderInfo extends ReaderWriterProviderInfo { + WebPProviderInfo() { + super( + WebPProviderInfo.class, + new String[] {"webp", "WEBP", "wbp", "WBP"}, + new String[] {"wbp", "webp"}, + new String[] { + "image/webp", "image/x-webp" + }, + "com.twelvemonkeys.imageio.plugins.webp.WebPImageReader", + new String[] {"com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi"}, + null, + null, + false, null, null, null, null, + true, null, null, null, null + + ); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorCache.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorCache.java new file mode 100644 index 00000000..dc36d268 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/ColorCache.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.lossless; + +import com.twelvemonkeys.lang.Validate; + +/** + * ColorCache. + * + * @author Harald Kuhr + */ +final class ColorCache { + private final int[] colors; // Color entries + private final int hashShift; // Hash shift: 32 - hashBits. + + private static final long K_HASH_MUL = 0x1e35a7bdL; + + private static int hashPix(final int argb, final int shift) { + return (int) (((argb * K_HASH_MUL) & 0xffffffffL) >> shift); + } + + ColorCache(final int hashBits) { + Validate.isTrue(hashBits > 0, "hasBits must > 0"); + + int hashSize = 1 << hashBits; + + colors = new int[hashSize]; + hashShift = 32 - hashBits; + } + + int lookup(final int key) { + return colors[key]; + } + + void set(final int key, final int argb) { + colors[key] = argb; + } + + void insert(final int argb) { + colors[index(argb)] = argb; + } + + int index(final int argb) { + return hashPix(argb, hashShift); + } + + // TODO: Use boolean? + int contains(final int argb) { + int key = index(argb); + return colors[key] == argb ? key : -1; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorMode.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorMode.java new file mode 100644 index 00000000..907c4ed2 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/PredictorMode.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.lossless; + +/** + * PredictorMode. + * + * @author Harald Kuhr + */ +public interface PredictorMode { + // Special rules: + // Top-left pixel of image is predicted BLACK + // Rest of top pixels is predicted L + // Rest of leftmost pixels are predicted T + // Rightmost pixels using TR, uses LEFTMOST pixel on SAME ROW (same distance as TR in memory!) + + int BLACK = 0; // 0xff000000 (represents solid black color in ARGB) + int L = 1; // L + int T = 2; // T + int TR = 3; // TR + int TL = 4; // TL + int AVG_L_TR_T = 5; // Average2(Average2(L, TR), T) + int AVG_L_TL = 6; // Average2(L, TL) + int AVG_L_T = 7; // Average2(L, T) + int AVG_TL_T = 8; // Average2(TL, T) + int AVG_T_TR = 9; // Average2(T, TR) + int AVG_L_TL_T_TR = 10; // Average2(Average2(L, TL), Average2(T, TR)) + int SELECT = 11; // Select(L, T, TL) + int CLAMP_ADD_SUB_FULL = 12; // ClampAddSubtractFull(L, T, TL) + int CLAMP_ADD_SUB_HALF = 13; // ClampAddSubtractHalf(Average2(L, T), TL) +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/Transform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/Transform.java new file mode 100644 index 00000000..d4aafa74 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/Transform.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.lossless; + +/** + * Transform. + * + * @author Harald Kuhr + */ +final class Transform { + final int type; + final Object data; + + Transform(final int type, final Object data) { + this.type = type; + this.data = data; + } + + byte[] getData() { + return (byte[]) data; + } + + int[] getColorMap() { + return (int[]) data; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java new file mode 100644 index 00000000..525a6fa8 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/TransformType.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.lossless; + +/** + * TransformType. + * + * @author Harald Kuhr + */ +// Hmm.. Why doesn't SUBTRACT_GREEN follow the convention? +interface TransformType { + int PREDICTOR_TRANSFORM = 0; + int COLOR_TRANSFORM = 1; + int SUBTRACT_GREEN = 2; + int COLOR_INDEXING_TRANSFORM = 3; +} 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 new file mode 100644 index 00000000..0f7e5f89 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2017, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.lossless; + +import com.twelvemonkeys.imageio.plugins.webp.LSBBitReader; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.twelvemonkeys.imageio.plugins.webp.RasterUtils.asByteRaster; +import static java.lang.Math.abs; + +/** + * VP8LDecoder. + * + * @author Harald Kuhr + */ +public final class VP8LDecoder { + + private final ImageInputStream imageInput; + private final LSBBitReader lsbBitReader; + + private final List transforms = new ArrayList<>(); + private ColorCache colorCache; + + public VP8LDecoder(final ImageInputStream imageInput) { + this.imageInput = imageInput; + lsbBitReader = new LSBBitReader(imageInput); + } + + public void readVP8Lossless(final WritableRaster raster, final boolean topLevel) throws IOException { + //https://github.com/webmproject/libwebp/blob/666bd6c65483a512fe4c2eb63fbc198b6fb4fae4/src/dec/vp8l_dec.c#L1114 + + int xSize = raster.getWidth(); + int ySize = raster.getHeight(); + + // Read transforms + while (topLevel && lsbBitReader.readBit() == 1) { + xSize = readTransform(xSize, ySize); + } + + // Read color cache size + int colorCacheBits = 0; + if (lsbBitReader.readBit() == 1) { + colorCacheBits = (int) lsbBitReader.readBits(4); + if (colorCacheBits < 1 || colorCacheBits > 11) { + throw new IIOException("Corrupt WebP stream, colorCacheBits < 1 || > 11: " + colorCacheBits); + } + } + + // Read Huffman codes + readHuffmanCodes(colorCacheBits, topLevel); + + if (colorCacheBits > 0) { + colorCache = new ColorCache(colorCacheBits); + } + + // Use the Huffman trees to decode the LZ77 encoded data. +// decodeImageData(raster, ) + } + + private int readTransform(int xSize, int ySize) throws IOException { + int transformType = (int) lsbBitReader.readBits(2); + + // TODO: Each transform type can only be present once in the stream. + + switch (transformType) { + case TransformType.PREDICTOR_TRANSFORM: { + System.err.println("transformType: PREDICTOR_TRANSFORM"); +// int sizeBits = (int) readBits(3) + 2; + int sizeBits = (int) lsbBitReader.readBits(3) + 2; + int size = 1 << sizeBits; + + int blockWidth = size; + int blockHeight = size; + +// int blockSize = divRoundUp(width, size); + int blockSize = divRoundUp(xSize, size); + + for (int y = 0; y < ySize; y++) { + for (int x = 0; x < xSize; x++) { + int blockIndex = (y >> sizeBits) * blockSize + (x >> sizeBits); + } + } + + // Special rules: + // Top-left pixel of image is predicted BLACK + // Rest of top pixels is predicted L + // Rest of leftmost pixels are predicted T + // Rightmost pixels using TR, uses LEFTMOST pixel on SAME ROW (same distance as TR in memory!) + +// WritableRaster data = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, blockWidth, blockHeight, blockWidth, 1, new int[] {0}, null); +// readVP8Lossless(data, false); +// + break; + } + case TransformType.COLOR_TRANSFORM: { + // The two first transforms contains the exact same data, can be combined + System.err.println("transformType: COLOR_TRANSFORM"); + + int sizeBits = (int) lsbBitReader.readBits(3) + 2; +// int size = 1 << sizeBits; + + // TODO: Understand difference between spec divRoundUp and impl VP8LSubSampleSize + + int blockWidth = subSampleSize(xSize, sizeBits); + int blockHeight = subSampleSize(ySize, sizeBits); + WritableRaster data = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, blockWidth, blockHeight, blockWidth, 1, new int[] {0}, null); + readVP8Lossless(data, false); + + transforms.add(new Transform(transformType, ((DataBufferByte) data.getDataBuffer()).getData())); + + break; + } + case TransformType.SUBTRACT_GREEN: { + System.err.println("transformType: SUBTRACT_GREEN"); + // No data here + +// addGreenToBlueAndRed(); + break; + } + case TransformType.COLOR_INDEXING_TRANSFORM: { + System.err.println("transformType: COLOR_INDEXING_TRANSFORM"); + + // 8 bit value for color table size + int colorTableSize = imageInput.readUnsignedByte() + 1; // 1-256 + System.err.println("colorTableSize: " + colorTableSize); + + // If the index is equal or larger than color_table_size, + // the argb color value should be set to 0x00000000 + // We handle this by allocating a possibly larger buffer + int safeColorTableSize = colorTableSize > 16 ? 256 : + colorTableSize > 4 ? 16 : + colorTableSize > 2 ? 4 : 2; + + System.err.println("safeColorTableSize: " + safeColorTableSize); + + int[] colorTable = new int[safeColorTableSize]; + + // The color table can be obtained by reading an image, + // without the RIFF header, image size, and transforms, + // assuming a height of one pixel and a width of + // color_table_size. The color table is always + // subtraction-coded to reduce image entropy. + // TODO: Read *without transforms*, using SUBTRACT_GREEN only! + readVP8Lossless(asByteRaster( + Raster.createPackedRaster( + new DataBufferInt(colorTable, colorTableSize), + colorTableSize, 1, colorTableSize, + new int[] {0}, null + ), + ColorModel.getRGBdefault() + ), false); + + // TODO: We may not really need this value... + // What we need is the number of pixels packed into each green sample (byte) + int widthBits = colorTableSize > 16 ? 0 : + colorTableSize > 4 ? 1 : + colorTableSize > 2 ? 2 : 3; + + xSize = subSampleSize(xSize, widthBits); + + /* + // TODO: read ARGB + int argb = 0; + + // Inverse transform + // TODO: Expand to mutliple pixels? + argb = colorTable[GREEN(argb)]; + */ + + // TODO: Can we use this to produce an image with IndexColorModel instead of expanding the values in-memory? + transforms.add(new Transform(transformType, colorTable)); + + break; + } + default: + throw new AssertionError("Invalid transformType: " + transformType); + } + + return xSize; + } + + private void readHuffmanCodes(int colorCacheBits, boolean allowRecursion) { + + } + + //// + + // FROM the spec + private static int divRoundUp(final int numerator, final int denominator) { + return (numerator + denominator - 1) / denominator; + } + + private static int subSampleSize(final int size, final int samplingBits) { + return (size + (1 << samplingBits) - 1) >> samplingBits; + } + + private static int ALPHA(final int ARGB) { + return ARGB >>> 24; + } + + private static int RED(final int ARGB) { + return (ARGB >> 16) & 0xff; + } + + private static int GREEN(final int ARGB) { + return (ARGB >> 8) & 0xff; + } + + private static int BLUE(final int ARGB) { + return ARGB & 0xff; + } + + private static int select(final int L, final int T, final int TL) { + // L = left pixel, T = top pixel, TL = top left pixel. + + // ARGB component estimates for prediction. + int pAlpha = ALPHA(L) + ALPHA(T) - ALPHA(TL); + int pRed = RED(L) + RED(T) - RED(TL); + int pGreen = GREEN(L) + GREEN(T) - GREEN(TL); + int pBlue = BLUE(L) + BLUE(T) - BLUE(TL); + + // Manhattan distances to estimates for left and top pixels. + int pL = abs(pAlpha - ALPHA(L)) + abs(pRed - RED(L)) + + abs(pGreen - GREEN(L)) + abs(pBlue - BLUE(L)); + int pT = abs(pAlpha - ALPHA(T)) + abs(pRed - RED(T)) + + abs(pGreen - GREEN(T)) + abs(pBlue - BLUE(T)); + + // Return either left or top, the one closer to the prediction. + if (pL < pT) { + return L; + } + else { + return T; + } + } + + private static int average2(final int a, final int b) { + return (a + b) / 2; + } + + // Clamp the input value between 0 and 255. + private static int clamp(final int a) { + return a < 0 ? 0 : a > 255 ? 255 : a; + } + + private static int clampAddSubtractFull(final int a, final int b, final int c) { + return clamp(a + b - c); + } + + private static int clampAddSubtractHalf(final int a, final int b) { + return clamp(a + (a - b) / 2); + } + + static final class ColorTransformElement { + final int green_to_red; + final int green_to_blue; + final int red_to_blue; + + ColorTransformElement(final int green_to_red, final int green_to_blue, final int red_to_blue) { + this.green_to_red = green_to_red; + this.green_to_blue = green_to_blue; + this.red_to_blue = red_to_blue; + } + } + + // NOTE: For encoding! + private static void colorTransform(final int red, final int blue, final int green, + final ColorTransformElement trans, + final int[] newRedBlue) { + // Transformed values of red and blue components + int tmp_red = red; + int tmp_blue = blue; + + // Applying transform is just adding the transform deltas + tmp_red += colorTransformDelta((byte) trans.green_to_red, (byte) green); + tmp_blue += colorTransformDelta((byte) trans.green_to_blue, (byte) green); + tmp_blue += colorTransformDelta((byte) trans.red_to_blue, (byte) red); + + // No pointer dereferences in Java... + // TODO: Consider passing an offset too, so we can modify in-place + newRedBlue[0] = tmp_red & 0xff; + newRedBlue[1] = tmp_blue & 0xff; + } + + // A conversion from the 8-bit unsigned representation (uint8) to the 8-bit + // signed one (int8) is required before calling ColorTransformDelta(). It + // should be performed using 8-bit two's complement (that is: uint8 range + // [128-255] is mapped to the [-128, -1] range of its converted int8 + // value). + private static byte colorTransformDelta(final byte t, final byte c) { + return (byte) ((t * c) >> 5); + } + + private static void inverseTransform(final byte red, final byte green, final byte blue, + final ColorTransformElement trans, + final int[] newRedBlue) { + // Applying inverse transform is just subtracting the + // color transform deltas + // Transformed values of red and blue components + int tmp_red = red; + int tmp_blue = blue; + + tmp_red -= colorTransformDelta((byte) trans.green_to_red, green); + tmp_blue -= colorTransformDelta((byte) trans.green_to_blue, green); + tmp_blue -= colorTransformDelta((byte) trans.red_to_blue, red); // Spec has red & 0xff + + newRedBlue[0] = tmp_red & 0xff; + newRedBlue[1] = tmp_blue & 0xff; + } + + private static void inverseTransform(final byte[] rgb, final ColorTransformElement trans) { + // Applying inverse transform is just subtracting the + // color transform deltas + // Transformed values of red and blue components + int tmp_red = rgb[0]; + int tmp_blue = rgb[2]; + + tmp_red -= colorTransformDelta((byte) trans.green_to_red, rgb[1]); + tmp_blue -= colorTransformDelta((byte) trans.green_to_blue, rgb[1]); + tmp_blue -= colorTransformDelta((byte) trans.red_to_blue, rgb[0]); // Spec has red & 0xff + + rgb[0] = (byte) (tmp_red & 0xff); + rgb[2] = (byte) (tmp_blue & 0xff); + } + + private static void addGreenToBlueAndRed(byte[] rgb) { + rgb[0] = (byte) ((rgb[0] + rgb[1]) & 0xff); + rgb[2] = (byte) ((rgb[2] + rgb[1]) & 0xff); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/BoolDecoder.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/BoolDecoder.java new file mode 100644 index 00000000..d7b3f82d --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/BoolDecoder.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +final class BoolDecoder { + private int bit_count; /* # of bits shifted out of value, at most 7 */ + ImageInputStream data; + private long offset; /* pointer to next compressed data byte */ + private int range; /* always identical to encoder's range */ + private int value; /* contains at least 24 significant bits */ + + BoolDecoder(ImageInputStream frame, long offset) throws IOException { + this.data = frame; + this.offset = offset; + initBoolDecoder(); + } + + private void initBoolDecoder() throws IOException { + value = 0; /* value = first 16 input bits */ + + data.seek(offset); + value = data.readUnsignedByte() << 8; + // value = (data[offset]) << 8; + offset++; + + range = 255; /* initial range is full */ + bit_count = 0; /* have not yet shifted out any bits */ + } + + public int readBit() throws IOException { + return readBool(128); + } + + public int readBool(int probability) throws IOException { + int bit = 0; + int split; + int bigsplit; + int range = this.range; + int value = this.value; + split = 1 + (((range - 1) * probability) >> 8); + bigsplit = (split << 8); + range = split; + + if (value >= bigsplit) { + range = this.range - split; + value = value - bigsplit; + bit = 1; + } + + { + int count = this.bit_count; + int shift = Globals.vp8dxBitreaderNorm[range]; + range <<= shift; + value <<= shift; + count -= shift; + + if (count <= 0) { + // data.seek(offset); + value |= data.readUnsignedByte() << (-count); + // value |= data[offset] << (-count); + offset++; + count += 8; + } + + this.bit_count = count; + } + this.value = value; + this.range = range; + return bit; + } + + /* + * Convenience function reads a "literal", that is, a "num_bits" wide + * unsigned value whose bits come high- to low-order, with each bit encoded + * at probability 128 (i.e., 1/2). + */ + public int readLiteral(int num_bits) throws IOException { + int v = 0; + while (num_bits-- > 0) { + v = (v << 1) + readBool(128); + } + return v; + } + + // int readTree(int t[], /* tree specification */ int p[] /* corresponding interior node probabilities */) throws IOException { +// int i = 0; /* begin at root */ +// +// /* Descend tree until leaf is reached */ +// while ((i = t[i + readBool(p[i >> 1])]) > 0) { +// } +// return -i; /* return value is negation of nonpositive index */ +// +// } +// +// int readTree(int t[], /* tree specification */ int p[], /* corresponding interior node probabilities */ int skip_branches) throws IOException { + int readTree(int[] t, /* tree specification */ int[] p, /* corresponding interior node probabilities */ int skip_branches) throws IOException { + int i = skip_branches * 2; /* begin at root */ + + /* Descend tree until leaf is reached */ + while ((i = t[i + readBool(p[i >> 1])]) > 0) { + } + return -i; /* return value is negation of nonpositive index */ + } + + public void seek() throws IOException { + data.seek(offset); + } + + public String toString() { + return "bc: " + value; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/DeltaQ.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/DeltaQ.java new file mode 100644 index 00000000..01c30b8a --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/DeltaQ.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class DeltaQ { + boolean update = false; + int v = 0; +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Globals.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Globals.java new file mode 100644 index 00000000..4282f3bf --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Globals.java @@ -0,0 +1,822 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class Globals { + + /* predict DC using row above and column to the left */ + static final int DC_PRED = 0; + /* predict rows using row above */ + static final int V_PRED = 1; + /* predict columns using column to the left */ + static final int H_PRED = 2; + /* propagate second differences a la "true motion" */ + static final int TM_PRED = 3; + /* each Y subblock is independently predicted */ + static final int B_PRED = 4; + + static String getModeAsString(int mode) { + switch (mode) { + case DC_PRED: + return "DC_PRED"; + case V_PRED: + return "V_PRED"; + case H_PRED: + return "H_PRED"; + case TM_PRED: + return "TM_PRED"; + case B_PRED: + return "B_PRED"; + } + return "not found"; + } + + /* intra_bmode */ + static final int B_DC_PRED = 0; /* + * predict DC using row above and + * column to the left + */ + static final int B_TM_PRED = 1; /* + * propagate second differences a la + * "true motion" + */ + static final int B_VE_PRED = 2; /* predict rows using row above */ + static final int B_HE_PRED = 3; /* + * predict columns using column to + * the left + */ + static final int B_LD_PRED = 4; /* + * southwest (left and down) 45 + * degree diagonal prediction + */ + static final int B_RD_PRED = 5; /* southeast (right and down) "" */ + static final int B_VR_PRED = 6; /* + * SSE (vertical right) diagonal + * prediction + */ + static final int B_VL_PRED = 7; /* SSW (vertical left) "" */ + static final int B_HD_PRED = 8; /* ESE (horizontal down) "" */ + static final int B_HU_PRED = 9; /* ENE (horizontal up) "" */ + + static String getSubBlockModeAsString(int mode) { + switch (mode) { + case B_DC_PRED: + return "B_DC_PRED"; + case B_TM_PRED: + return "B_TM_PRED"; + case B_VE_PRED: + return "B_VE_PRED"; + case B_HE_PRED: + return "B_HE_PRED"; + case B_LD_PRED: + return "B_LD_PRED"; + case B_RD_PRED: + return "B_RD_PRED"; + case B_VR_PRED: + return "B_VR_PRED"; + case B_VL_PRED: + return "B_VL_PRED"; + case B_HD_PRED: + return "B_HD_PRED"; + case B_HU_PRED: + return "B_HU_PRED"; + } + return "not found"; + } + + static final int MAX_MB_SEGMENTS = 4; + static final int MB_LVL_MAX = 2; + static int[] vp8MacroBlockFeatureDataBits = { 7, 6 }; + static final int MB_FEATURE_TREE_PROBS = 3; + + static int macroBlockSegmentTree[] = { 2, 4, + /* root: "0", "1" subtrees */ + -0, -1, + /* "00" = 0th value, "01" = 1st value */ + -2, -3 + /* "10" = 2nd value, "11" = 3rd value */ + }; + + static int vp8KeyFrameYModeTree[] = { -B_PRED, 2, 4, 6, -DC_PRED, + -V_PRED, -H_PRED, -TM_PRED }; + + static int vp8SubBlockModeTree[] = { -B_DC_PRED, 2, /* + * B_DC_PRED = + * "0" + */ + -B_TM_PRED, 4, /* B_TM_PRED = "10" */ + -B_VE_PRED, 6, /* B_VE_PRED = "110" */ + 8, 12, -B_HE_PRED, 10, /* B_HE_PRED = "1110" */ + -B_RD_PRED, -B_VR_PRED, /* B_RD_PRED = "111100", B_VR_PRED = "111101" */ + -B_LD_PRED, 14, /* B_LD_PRED = "111110" */ + -B_VL_PRED, 16, /* B_VL_PRED = "1111110" */ + -B_HD_PRED, -B_HU_PRED /* HD = "11111110", HU = "11111111" */ + }; + + static int[] vp8KeyFrameYModeProb = {145, 156, 163, 128 }; + + // uv + static int[] vp8UVModeTree = {-DC_PRED, 2, /* + * root: DC_PRED = "0", + * "1" subtree + */ + -V_PRED, 4, /* "1" subtree: V_PRED = "10", "11" subtree */ + -H_PRED, -TM_PRED /* "11" subtree: H_PRED = "110", TM_PRED = "111" */ + }; + static int[] vp8KeyFrameUVModeProb = {142, 114, 183 }; + + static int[][][] vp8KeyFrameSubBlockModeProb = { + { { 231, 120, 48, 89, 115, 113, 120, 152, 112 }, + { 152, 179, 64, 126, 170, 118, 46, 70, 95 }, + { 175, 69, 143, 80, 85, 82, 72, 155, 103 }, + { 56, 58, 10, 171, 218, 189, 17, 13, 152 }, + { 144, 71, 10, 38, 171, 213, 144, 34, 26 }, + { 114, 26, 17, 163, 44, 195, 21, 10, 173 }, + { 121, 24, 80, 195, 26, 62, 44, 64, 85 }, + { 170, 46, 55, 19, 136, 160, 33, 206, 71 }, + { 63, 20, 8, 114, 114, 208, 12, 9, 226 }, + { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, + { { 134, 183, 89, 137, 98, 101, 106, 165, 148 }, + { 72, 187, 100, 130, 157, 111, 32, 75, 80 }, + { 66, 102, 167, 99, 74, 62, 40, 234, 128 }, + { 41, 53, 9, 178, 241, 141, 26, 8, 107 }, + { 104, 79, 12, 27, 217, 255, 87, 17, 7 }, + { 74, 43, 26, 146, 73, 166, 49, 23, 157 }, + { 65, 38, 105, 160, 51, 52, 31, 115, 128 }, + { 87, 68, 71, 44, 114, 51, 15, 186, 23 }, + { 47, 41, 14, 110, 182, 183, 21, 17, 194 }, + { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, + { { 88, 88, 147, 150, 42, 46, 45, 196, 205 }, + { 43, 97, 183, 117, 85, 38, 35, 179, 61 }, + { 39, 53, 200, 87, 26, 21, 43, 232, 171 }, + { 56, 34, 51, 104, 114, 102, 29, 93, 77 }, + { 107, 54, 32, 26, 51, 1, 81, 43, 31 }, + { 39, 28, 85, 171, 58, 165, 90, 98, 64 }, + { 34, 22, 116, 206, 23, 34, 43, 166, 73 }, + { 68, 25, 106, 22, 64, 171, 36, 225, 114 }, + { 34, 19, 21, 102, 132, 188, 16, 76, 124 }, + { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, + { { 193, 101, 35, 159, 215, 111, 89, 46, 111 }, + { 60, 148, 31, 172, 219, 228, 21, 18, 111 }, + { 112, 113, 77, 85, 179, 255, 38, 120, 114 }, + { 40, 42, 1, 196, 245, 209, 10, 25, 109 }, + { 100, 80, 8, 43, 154, 1, 51, 26, 71 }, + { 88, 43, 29, 140, 166, 213, 37, 43, 154 }, + { 61, 63, 30, 155, 67, 45, 68, 1, 209 }, + { 142, 78, 78, 16, 255, 128, 34, 197, 171 }, + { 41, 40, 5, 102, 211, 183, 4, 1, 221 }, + { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, + { { 125, 98, 42, 88, 104, 85, 117, 175, 82 }, + { 95, 84, 53, 89, 128, 100, 113, 101, 45 }, + { 75, 79, 123, 47, 51, 128, 81, 171, 1 }, + { 57, 17, 5, 71, 102, 57, 53, 41, 49 }, + { 115, 21, 2, 10, 102, 255, 166, 23, 6 }, + { 38, 33, 13, 121, 57, 73, 26, 1, 85 }, + { 41, 10, 67, 138, 77, 110, 90, 47, 114 }, + { 101, 29, 16, 10, 85, 128, 101, 196, 26 }, + { 57, 18, 10, 102, 102, 213, 34, 20, 43 }, + { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, + { { 138, 31, 36, 171, 27, 166, 38, 44, 229 }, + { 67, 87, 58, 169, 82, 115, 26, 59, 179 }, + { 63, 59, 90, 180, 59, 166, 93, 73, 154 }, + { 40, 40, 21, 116, 143, 209, 34, 39, 175 }, + { 57, 46, 22, 24, 128, 1, 54, 17, 37 }, + { 47, 15, 16, 183, 34, 223, 49, 45, 183 }, + { 46, 17, 33, 183, 6, 98, 15, 32, 183 }, + { 65, 32, 73, 115, 28, 128, 23, 128, 205 }, + { 40, 3, 9, 115, 51, 192, 18, 6, 223 }, + { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, + { { 104, 55, 44, 218, 9, 54, 53, 130, 226 }, + { 64, 90, 70, 205, 40, 41, 23, 26, 57 }, + { 54, 57, 112, 184, 5, 41, 38, 166, 213 }, + { 30, 34, 26, 133, 152, 116, 10, 32, 134 }, + { 75, 32, 12, 51, 192, 255, 160, 43, 51 }, + { 39, 19, 53, 221, 26, 114, 32, 73, 255 }, + { 31, 9, 65, 234, 2, 15, 1, 118, 73 }, + { 88, 31, 35, 67, 102, 85, 55, 186, 85 }, + { 56, 21, 23, 111, 59, 205, 45, 37, 192 }, + { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, + { { 102, 61, 71, 37, 34, 53, 31, 243, 192 }, + { 69, 60, 71, 38, 73, 119, 28, 222, 37 }, + { 68, 45, 128, 34, 1, 47, 11, 245, 171 }, + { 62, 17, 19, 70, 146, 85, 55, 62, 70 }, + { 75, 15, 9, 9, 64, 255, 184, 119, 16 }, + { 37, 43, 37, 154, 100, 163, 85, 160, 1 }, + { 63, 9, 92, 136, 28, 64, 32, 201, 85 }, + { 86, 6, 28, 5, 64, 255, 25, 248, 1 }, + { 56, 8, 17, 132, 137, 255, 55, 116, 128 }, + { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, + { { 164, 50, 31, 137, 154, 133, 25, 35, 218 }, + { 51, 103, 44, 131, 131, 123, 31, 6, 158 }, + { 86, 40, 64, 135, 148, 224, 45, 183, 128 }, + { 22, 26, 17, 131, 240, 154, 14, 1, 209 }, + { 83, 12, 13, 54, 192, 255, 68, 47, 28 }, + { 45, 16, 21, 91, 64, 222, 7, 1, 197 }, + { 56, 21, 39, 155, 60, 138, 23, 102, 213 }, + { 85, 26, 85, 85, 128, 128, 32, 146, 171 }, + { 18, 11, 7, 63, 144, 171, 4, 4, 246 }, + { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, + { { 190, 80, 35, 99, 180, 80, 126, 54, 45 }, + { 85, 126, 47, 87, 176, 51, 41, 20, 32 }, + { 101, 75, 128, 139, 118, 146, 116, 128, 85 }, + { 56, 41, 15, 176, 236, 85, 37, 9, 62 }, + { 146, 36, 19, 30, 171, 255, 97, 27, 20 }, + { 71, 30, 17, 119, 118, 255, 17, 18, 138 }, + { 101, 38, 60, 138, 55, 70, 43, 26, 142 }, + { 138, 45, 61, 62, 219, 1, 81, 188, 64 }, + { 32, 41, 20, 117, 151, 142, 20, 21, 163 }, + { 112, 19, 12, 61, 195, 128, 48, 4, 24 } } }; + + static int[][][][] vp8CoefUpdateProbs = new int[][][][] { + { + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, + 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, + 255 }, + + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } } }, + { + { + { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, + 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, + 255 } }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } } }, + { + { + { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, + 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, + 255 } }, + { + + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } } }, + { + { + { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, + 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + + { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255 } } } }; + + static int[][][][] getDefaultCoefProbs() { + int[][][][] r = new int[vp8DefaultCoefProbs.length][vp8DefaultCoefProbs[0].length][vp8DefaultCoefProbs[0][0].length][vp8DefaultCoefProbs[0][0][0].length]; + for (int i = 0; i < vp8DefaultCoefProbs.length; i++) + for (int j = 0; j < vp8DefaultCoefProbs[0].length; j++) + for (int k = 0; k < vp8DefaultCoefProbs[0][0].length; k++) + for (int l = 0; l < vp8DefaultCoefProbs[0][0][0].length; l++) + r[i][j][k][l] = vp8DefaultCoefProbs[i][j][k][l]; + return r; + } + + private static final int[][][][] vp8DefaultCoefProbs = new int[][][][] { + { + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 } }, + { + { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, + 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, + 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, + 128 } }, + { + { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, + 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, + 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, + 128 } }, + { + { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, + 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, + 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, + 128 } }, + { + { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, + 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, + 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, + 128 } }, + { + + { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, + 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, + 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, + 128 } }, + { + { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, + 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, + 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, + 128 } }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 } } }, + { + { + { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, + 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, + 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, + 128 } }, + { + { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, + 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, + 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, + 128 } }, + { + { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, + 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, + 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, + 128 } }, + { + { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, + 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, + 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, + 128 } }, + { + { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, + 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, + 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, + 128 } }, + { + { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, + 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, + 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, + 128 } }, + { + { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, + 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, + 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, + 128 } }, + { + { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, + 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, + 128 } } }, + + { + { + { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, + 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, + 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, + 128 } }, + { + { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, + 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, + 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, + 128 } }, + { + { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, + 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, + 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, + 128 } }, + { + { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, + 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, + 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, + 128 } }, + { + { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, + 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, + 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, + 128 } }, + { + { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, + 128 } }, + { + { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, + 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, + 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, + 128 } }, + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128 } } }, + { + { + { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, + 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, + 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, + 128 } }, + { + { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, + 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, + 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, + 128 } }, + { + { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, + 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, + 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, + 128 } + + }, + { + { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, + 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, + 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, + 128 } }, + { + { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, + 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, + 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, + 128 } }, + { + { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, + 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, + 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, + 128 } }, + { + { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, + 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, + 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, + 128 } }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, + 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, + 128 } } } }; + + static final int DCT_0 = 0; /* value 0 */ + static final int DCT_1 = 1; /* 1 */ + static final int DCT_2 = 2; /* 2 */ + static final int DCT_3 = 3; /* 3 */ + static final int DCT_4 = 4; /* 4 */ + static final int dct_cat1 = 5; /* range 5 - 6 (size 2) */ + static final int dct_cat2 = 6; /* 7 - 10 (4) */ + static final int dct_cat3 = 7; /* 11 - 18 (8) */ + static final int dct_cat4 = 8; /* 19 - 34 (16) */ + static final int dct_cat5 = 9; /* 35 - 66 (32) */ + static final int dct_cat6 = 10; /* 67 - 2048 (1982) */ + static final int dct_eob = 11; /* end of block */ + + static final int[] vp8CoefTree = {-dct_eob, 2, /* eob = "0" */ + -DCT_0, 4, /* 0 = "10" */ + -DCT_1, 6, /* 1 = "110" */ + 8, 12, -DCT_2, 10, /* 2 = "11100" */ + -DCT_3, -DCT_4, /* 3 = "111010", 4 = "111011" */ + 14, 16, -dct_cat1, -dct_cat2, /* cat1 = "111100", cat2 = "111101" */ + 18, 20, -dct_cat3, -dct_cat4, /* cat3 = "1111100", cat4 = "1111101" */ + -dct_cat5, -dct_cat6 /* cat4 = "1111110", cat4 = "1111111" */ + }; + + static final int[] vp8CoefTreeNoEOB = { + // -dct_eob, 2, /* eob = "0" */ + -DCT_0, 4, /* 0 = "10" */ + -DCT_1, 6, /* 1 = "110" */ + 8, 12, -DCT_2, 10, /* 2 = "11100" */ + -DCT_3, -DCT_4, /* 3 = "111010", 4 = "111011" */ + 14, 16, -dct_cat1, -dct_cat2, /* cat1 = "111100", cat2 = "111101" */ + 18, 20, -dct_cat3, -dct_cat4, /* cat3 = "1111100", cat4 = "1111101" */ + -dct_cat5, -dct_cat6 /* cat4 = "1111110", cat4 = "1111111" */ + }; + + final static int[] Pcat1 = {159, 0 }; + final static int[] Pcat2 = {165, 145, 0 }; + final static int[] Pcat3 = {173, 148, 140, 0 }; + final static int[] Pcat4 = {176, 155, 140, 135, 0 }; + final static int[] Pcat5 = {180, 157, 141, 134, 130, 0 }; + final static int[] Pcat6 = {254, 254, 243, 230, 196, 177, 153, 140, + 133, 130, 129, 0 }; + static final int[] vp8CoefBands = {0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 7 }; + static final int[] vp8defaultZigZag1d = {0, 1, 4, 8, 5, 2, 3, 6, 9, + 12, 13, 10, 7, 11, 14, 15,}; + + static final int[] vp8dxBitreaderNorm = {0, 7, 6, 6, 5, 5, 5, 5, 4, + 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 }; + + static final int[] vp8DcQLookup = {4, 5, 6, 7, 8, 9, 10, 10, 11, + 12, 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, + 24, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, + 143, 145, 148, 151, 154, 157,}; + + static final int[] vp8AcQLookup = {4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, + 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, + 102, 104, 106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, + 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, + 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, + 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284,}; + + static String toHex(int c) { + return String.format("%1$#x ", c); + } + + // clamp between 0 and value + static int clamp(final int input, final int value) { + return input < 0 ? 0 : input > value ? value : input; + } + +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/IDCT.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/IDCT.java new file mode 100644 index 00000000..d7df9d2d --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/IDCT.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class IDCT { + /* IDCT implementation */ + private static final int cospi8sqrt2minus1 = 20091; + + private static final int sinpi8sqrt2 = 35468; + + public static int[][] idct4x4llm(int input[]) { + int i; + int a1, b1, c1, d1; + int offset = 0; + + int[] output = new int[16]; + int temp1, temp2; + + for (i = 0; i < 4; i++) { + a1 = input[offset + 0] + input[offset + 8]; + b1 = input[offset + 0] - input[offset + 8]; + + temp1 = (input[offset + 4] * sinpi8sqrt2) >> 16; + temp2 = input[offset + 12] + + ((input[offset + 12] * cospi8sqrt2minus1) >> 16); + + c1 = temp1 - temp2; + + temp1 = input[offset + 4] + + ((input[offset + 4] * cospi8sqrt2minus1) >> 16); + temp2 = (input[offset + 12] * sinpi8sqrt2) >> 16; + d1 = temp1 + temp2; + + output[offset + (0 * 4)] = a1 + d1; + output[offset + (3 * 4)] = a1 - d1; + output[offset + (1 * 4)] = b1 + c1; + output[offset + (2 * 4)] = b1 - c1; + + offset++; + } + + int diffo = 0; + int[][] diff = new int[4][4]; + offset = 0; + for (i = 0; i < 4; i++) { + a1 = output[(offset * 4) + 0] + output[(offset * 4) + 2]; + b1 = output[(offset * 4) + 0] - output[(offset * 4) + 2]; + + temp1 = (output[(offset * 4) + 1] * sinpi8sqrt2) >> 16; + temp2 = output[(offset * 4) + 3] + + ((output[(offset * 4) + 3] * cospi8sqrt2minus1) >> 16); + c1 = temp1 - temp2; + + temp1 = output[(offset * 4) + 1] + + ((output[(offset * 4) + 1] * cospi8sqrt2minus1) >> 16); + temp2 = (output[(offset * 4) + 3] * sinpi8sqrt2) >> 16; + d1 = temp1 + temp2; + + output[(offset * 4) + 0] = (a1 + d1 + 4) >> 3; + output[(offset * 4) + 3] = (a1 - d1 + 4) >> 3; + output[(offset * 4) + 1] = (b1 + c1 + 4) >> 3; + output[(offset * 4) + 2] = (b1 - c1 + 4) >> 3; + + diff[0][diffo] = (a1 + d1 + 4) >> 3; + diff[3][diffo] = (a1 - d1 + 4) >> 3; + diff[1][diffo] = (b1 + c1 + 4) >> 3; + diff[2][diffo] = (b1 - c1 + 4) >> 3; + + offset++; + diffo++; + } + + return diff; + + } + + public static int[][] iwalsh4x4(int[] input) { + int i; + int a1, b1, c1, d1; + int a2, b2, c2, d2; + + int[] output = new int[16]; + int[][] diff = new int[4][4]; + int offset = 0; + for (i = 0; i < 4; i++) { + a1 = input[offset + 0] + input[offset + 12]; + b1 = input[offset + 4] + input[offset + 8]; + c1 = input[offset + 4] - input[offset + 8]; + d1 = input[offset + 0] - input[offset + 12]; + + output[offset + 0] = a1 + b1; + output[offset + 4] = c1 + d1; + output[offset + 8] = a1 - b1; + output[offset + 12] = d1 - c1; + offset++; + } + + offset = 0; + + for (i = 0; i < 4; i++) { + a1 = output[offset + 0] + output[offset + 3]; + b1 = output[offset + 1] + output[offset + 2]; + c1 = output[offset + 1] - output[offset + 2]; + d1 = output[offset + 0] - output[offset + 3]; + + a2 = a1 + b1; + b2 = c1 + d1; + c2 = a1 - b1; + d2 = d1 - c1; + output[offset + 0] = (a2 + 3) >> 3; + output[offset + 1] = (b2 + 3) >> 3; + output[offset + 2] = (c2 + 3) >> 3; + output[offset + 3] = (d2 + 3) >> 3; + diff[0][i] = (a2 + 3) >> 3; + diff[1][i] = (b2 + 3) >> 3; + diff[2][i] = (c2 + 3) >> 3; + diff[3][i] = (d2 + 3) >> 3; + offset += 4; + } + + return diff; + + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/LoopFilter.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/LoopFilter.java new file mode 100644 index 00000000..083de9d9 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/LoopFilter.java @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class LoopFilter { + private static int abs(int v) { + return v < 0 ? -v : v; + } + + private static int c(int v) { + return v < -128 ? -128 : (v > 127 ? 127 : v); + } + + private static int common_adjust(boolean use_outer_taps, /* filter is 2 or 4 taps wide */ Segment seg) { + int p1 = u2s(seg.P1); /* retrieve and convert all 4 pixels */ + int p0 = u2s(seg.P0); + int q0 = u2s(seg.Q0); + int q1 = u2s(seg.Q1); + + /* + * Disregarding clamping, when "use_outer_taps" is false, "a" is + * 3*(q0-p0). Since we are about to divide "a" by 8, in this case we end + * up multiplying the edge difference by 5/8. When "use_outer_taps" is + * true (as for the simple filter), "a" is p1 - 3*p0 + 3*q0 - q1, which + * can be thought of as a refinement of 2*(q0 - p0) and the adjustment + * is something like (q0 - p0)/4. + */ + int a = c((use_outer_taps ? c(p1 - q1) : 0) + 3 * (q0 - p0)); + /* + * b is used to balance the rounding of a/8 in the case where the + * "fractional" part "f" of a/8 is exactly 1/2. + */ + int b = (c(a + 3)) >> 3; + /* + * Divide a by 8, rounding up when f >= 1/2. Although not strictly part + * of the "C" language, the right-shift is assumed to propagate the sign + * bit. + */ + a = c(a + 4) >> 3; + /* Subtract "a" from q0, "bringing it closer" to p0. */ + seg.Q0 = s2u(q0 - a); + /* + * Add "a" (with adjustment "b") to p0, "bringing it closer" to q0. The + * clamp of "a+b", while present in the reference decoder, is + * superfluous; we have -16 <= a <= 15 at this point. + */ + seg.P0 = s2u(p0 + b); + + return a; + } + + /* + * All functions take (among other things) a segment (of length at most 4 + + * 4 = 8) symmetrically straddling an edge. The pixel values (or pointers) + * are always given in order, from the "beforemost" to the "aftermost". So, + * for a horizontal edge (written "|"), an 8-pixel segment would be ordered + * p3 p2 p1 p0 | q0 q1 q2 q3. + */ + /* + * Filtering is disabled if the difference between any two adjacent + * "interior" pixels in the 8-pixel segment exceeds the relevant threshold + * (I). A more complex thresholding calculation is done for the group of + * four pixels that straddle the edge, in line with the calculation in + * simple_segment() above. + */ + private static boolean filter_yes(int I, /* limit on interior differences */ + int E, /* limit at the edge */ + int p3, int p2, int p1, int p0, /* pixels before edge */ + int q0, int q1, int q2, int q3 /* pixels after edge */ + ) { + return (abs(p0 - q0) * 2 + abs(p1 - q1) / 2) <= E && abs(p3 - p2) <= I + && abs(p2 - p1) <= I && abs(p1 - p0) <= I && abs(q3 - q2) <= I + && abs(q2 - q1) <= I && abs(q1 - q0) <= I; + } + + private static Segment getSegH(SubBlock rsb, SubBlock lsb, int a) { + Segment seg = new Segment(); + int[][] rdest = rsb.getDest(); + int[][] ldest = lsb.getDest(); + seg.P0 = ldest[3][a]; + seg.P1 = ldest[2][a]; + seg.P2 = ldest[1][a]; + seg.P3 = ldest[0][a]; + seg.Q0 = rdest[0][a]; + seg.Q1 = rdest[1][a]; + seg.Q2 = rdest[2][a]; + seg.Q3 = rdest[3][a]; + return seg; + } + + private static Segment getSegV(SubBlock bsb, SubBlock tsb, int a) { + Segment seg = new Segment(); + int[][] bdest = bsb.getDest(); + int[][] tdest = tsb.getDest(); + + seg.P0 = tdest[a][3]; + seg.P1 = tdest[a][2]; + seg.P2 = tdest[a][1]; + seg.P3 = tdest[a][0]; + seg.Q0 = bdest[a][0]; + seg.Q1 = bdest[a][1]; + seg.Q2 = bdest[a][2]; + seg.Q3 = bdest[a][3]; + return seg; + } + + /* + * Filtering is altered if (at least) one of the differences on either side + * of the edge exceeds a threshold (we have "high edge variance"). + */ + private static boolean hev(int threshold, int p1, int p0, /* + * pixels before + * edge + */ + int q0, int q1 /* pixels after edge */ + ) { + return abs(p1 - p0) > threshold || abs(q1 - q0) > threshold; + } + + public static void loopFilter(VP8Frame frame) { +// frame.fireLFProgressUpdate(0); + if (frame.getFilterType() == 2) { + loopFilterUV(frame); +// frame.fireLFProgressUpdate(50); + loopFilterY(frame); + } else if (frame.getFilterType() == 1) { + loopFilterSimple(frame); + } +// frame.fireLFProgressUpdate(100); + } + + private static void loopFilterSimple(VP8Frame frame) { + for (int y = 0; y < frame.getMacroBlockRows(); y++) { +// frame.fireLFProgressUpdate((100.0f * ((float) (y + 1) / (float) (frame +// .getMacroBlockRows())))); + for (int x = 0; x < frame.getMacroBlockCols(); x++) { + // System.out.println("x: "+x+" y: "+y); + MacroBlock rmb = frame.getMacroBlock(x, y); + MacroBlock bmb = frame.getMacroBlock(x, y); + + int loop_filter_level = rmb.getFilterLevel(); + if (loop_filter_level != 0) { + int interior_limit = rmb.getFilterLevel(); + + int sharpnessLevel = frame.getSharpnessLevel(); + if (sharpnessLevel > 0) { + interior_limit >>= sharpnessLevel > 4 ? 2 : 1; + if (interior_limit > 9 - sharpnessLevel) { + interior_limit = 9 - sharpnessLevel; + } + } + if (interior_limit == 0) { + interior_limit = 1; + } + + /* Luma and Chroma use the same inter-subblock edge limit */ + int sub_bedge_limit = (loop_filter_level * 2) + interior_limit; + if (sub_bedge_limit < 1) { + sub_bedge_limit = 1; + } + + /* Luma and Chroma use the same inter-macroblock edge limit */ + int mbedge_limit = sub_bedge_limit + 4; + + // left + if (x > 0) { + MacroBlock lmb = frame.getMacroBlock(x - 1, y); + for (int b = 0; b < 4; b++) { + SubBlock rsb = rmb.getSubBlock(SubBlock.PLANE.Y1, 0, b); + SubBlock lsb = lmb.getSubBlock(SubBlock.PLANE.Y1, 3, b); + for (int a = 0; a < 4; a++) { + Segment seg = getSegH(rsb, lsb, a); + // MBfilter(hev_threshold, interior_limit, + // mbedge_limit, seg); + // System.out.println(mbedge_limit); + simple_segment(mbedge_limit, seg); + setSegH(rsb, lsb, seg, a); + } + } + } + + // sb left + if (!rmb.isSkip_inner_lf()) { + + for (int a = 1; a < 4; a++) { + for (int b = 0; b < 4; b++) { + SubBlock lsb = rmb.getSubBlock(SubBlock.PLANE.Y1, + a - 1, b); + SubBlock rsb = rmb.getSubBlock(SubBlock.PLANE.Y1, + a, b); + for (int c = 0; c < 4; c++) { + // System.out.println("sbleft a:"+a+" b:"+b+" c:"+c); + Segment seg = getSegH(rsb, lsb, c); + simple_segment(sub_bedge_limit, seg); + // System.out.println(sub_bedge_limit); + // subblock_filter(hev_threshold,interior_limit,sub_bedge_limit, + // seg); + setSegH(rsb, lsb, seg, c); + } + } + } + } + + // top + if (y > 0) { + MacroBlock tmb = frame.getMacroBlock(x, y - 1); + for (int b = 0; b < 4; b++) { + SubBlock tsb = tmb.getSubBlock(SubBlock.PLANE.Y1, b, 3); + SubBlock bsb = bmb.getSubBlock(SubBlock.PLANE.Y1, b, 0); + for (int a = 0; a < 4; a++) { + Segment seg = getSegV(bsb, tsb, a); + simple_segment(mbedge_limit, seg); + // System.out.println(mbedge_limit); + // MBfilter(hev_threshold, interior_limit, + // mbedge_limit, seg); + setSegV(bsb, tsb, seg, a); + } + } + } + + // sb top + if (!rmb.isSkip_inner_lf()) { + for (int a = 1; a < 4; a++) { + for (int b = 0; b < 4; b++) { + SubBlock tsb = bmb.getSubBlock(SubBlock.PLANE.Y1, + b, a - 1); + SubBlock bsb = bmb.getSubBlock(SubBlock.PLANE.Y1, + b, a); + for (int c = 0; c < 4; c++) { + // System.out.println("sbtop"); + Segment seg = getSegV(bsb, tsb, c); + simple_segment(sub_bedge_limit, seg); + // System.out.println(sub_bedge_limit); + // subblock_filter(hev_threshold,interior_limit,sub_bedge_limit, + // seg); + setSegV(bsb, tsb, seg, c); + } + } + } + } + } + } + } + } + + private static void loopFilterUV(VP8Frame frame) { + for (int y = 0; y < frame.getMacroBlockRows(); y++) { +// frame.fireLFProgressUpdate((100.0f * ((float) (y + 1) / (float) (frame +// .getMacroBlockRows()))) / 2); + for (int x = 0; x < frame.getMacroBlockCols(); x++) { + MacroBlock rmb = frame.getMacroBlock(x, y); + MacroBlock bmb = frame.getMacroBlock(x, y); + int sharpnessLevel = frame.getSharpnessLevel(); + int loop_filter_level = rmb.getFilterLevel(); + if (loop_filter_level != 0) { + int interior_limit = rmb.getFilterLevel(); + if (sharpnessLevel > 0) { + interior_limit >>= sharpnessLevel > 4 ? 2 : 1; + if (interior_limit > 9 - sharpnessLevel) { + interior_limit = 9 - sharpnessLevel; + } + } + if (interior_limit == 0) { + interior_limit = 1; + } + + int hev_threshold = 0; + if (frame.getFrameType() == 0) /* current frame is a key frame */ { + if (loop_filter_level >= 40) { + hev_threshold = 2; + } else if (loop_filter_level >= 15) { + hev_threshold = 1; + } + } else /* current frame is an interframe */ { + if (loop_filter_level >= 40) { + hev_threshold = 3; + } else if (loop_filter_level >= 20) { + hev_threshold = 2; + } else if (loop_filter_level >= 15) { + hev_threshold = 1; + } + } + + /* Luma and Chroma use the same inter-macroblock edge limit */ + int mbedge_limit = ((loop_filter_level + 2) * 2) + + interior_limit; + /* Luma and Chroma use the same inter-subblock edge limit */ + int sub_bedge_limit = (loop_filter_level * 2) + interior_limit; + + if (x > 0) { + MacroBlock lmb = frame.getMacroBlock(x - 1, y); + for (int b = 0; b < 2; b++) { + SubBlock rsbU = rmb.getSubBlock(SubBlock.PLANE.U, 0, b); + SubBlock lsbU = lmb.getSubBlock(SubBlock.PLANE.U, 1, b); + SubBlock rsbV = rmb.getSubBlock(SubBlock.PLANE.V, 0, b); + SubBlock lsbV = lmb.getSubBlock(SubBlock.PLANE.V, 1, b); + for (int a = 0; a < 4; a++) { + Segment seg = getSegH(rsbU, lsbU, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegH(rsbU, lsbU, seg, a); + seg = getSegH(rsbV, lsbV, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegH(rsbV, lsbV, seg, a); + + } + } + } + // sb left + + if (!rmb.isSkip_inner_lf()) { + for (int a = 1; a < 2; a++) { + for (int b = 0; b < 2; b++) { + SubBlock lsbU = rmb.getSubBlock(SubBlock.PLANE.U, + a - 1, b); + SubBlock rsbU = rmb.getSubBlock(SubBlock.PLANE.U, + a, b); + SubBlock lsbV = rmb.getSubBlock(SubBlock.PLANE.V, + a - 1, b); + SubBlock rsbV = rmb.getSubBlock(SubBlock.PLANE.V, + a, b); + for (int c = 0; c < 4; c++) { + Segment seg = getSegH(rsbU, lsbU, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegH(rsbU, lsbU, seg, c); + seg = getSegH(rsbV, lsbV, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegH(rsbV, lsbV, seg, c); + } + } + } + } + // top + if (y > 0) { + MacroBlock tmb = frame.getMacroBlock(x, y - 1); + for (int b = 0; b < 2; b++) { + SubBlock tsbU = tmb.getSubBlock(SubBlock.PLANE.U, b, 1); + SubBlock bsbU = bmb.getSubBlock(SubBlock.PLANE.U, b, 0); + SubBlock tsbV = tmb.getSubBlock(SubBlock.PLANE.V, b, 1); + SubBlock bsbV = bmb.getSubBlock(SubBlock.PLANE.V, b, 0); + for (int a = 0; a < 4; a++) { + // System.out.println("l"); + Segment seg = getSegV(bsbU, tsbU, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegV(bsbU, tsbU, seg, a); + seg = getSegV(bsbV, tsbV, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegV(bsbV, tsbV, seg, a); + } + } + } + // sb top + + if (!rmb.isSkip_inner_lf()) { + for (int a = 1; a < 2; a++) { + for (int b = 0; b < 2; b++) { + SubBlock tsbU = bmb.getSubBlock(SubBlock.PLANE.U, + b, a - 1); + SubBlock bsbU = bmb.getSubBlock(SubBlock.PLANE.U, + b, a); + SubBlock tsbV = bmb.getSubBlock(SubBlock.PLANE.V, + b, a - 1); + SubBlock bsbV = bmb.getSubBlock(SubBlock.PLANE.V, + b, a); + for (int c = 0; c < 4; c++) { + Segment seg = getSegV(bsbU, tsbU, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegV(bsbU, tsbU, seg, c); + seg = getSegV(bsbV, tsbV, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegV(bsbV, tsbV, seg, c); + } + } + } + } + } + } + } + } + + private static void loopFilterY(VP8Frame frame) { + for (int y = 0; y < frame.getMacroBlockRows(); y++) { +// frame.fireLFProgressUpdate(50 + (100.0f * ((float) (y + 1) / (float) (frame +// .getMacroBlockRows()))) / 2); + for (int x = 0; x < frame.getMacroBlockCols(); x++) { + MacroBlock rmb = frame.getMacroBlock(x, y); + MacroBlock bmb = frame.getMacroBlock(x, y); + int sharpnessLevel = frame.getSharpnessLevel(); + int loop_filter_level = rmb.getFilterLevel(); + + if (loop_filter_level != 0) { + int interior_limit = rmb.getFilterLevel(); + + if (sharpnessLevel > 0) { + interior_limit >>= sharpnessLevel > 4 ? 2 : 1; + if (interior_limit > 9 - sharpnessLevel) { + interior_limit = 9 - sharpnessLevel; + } + } + if (interior_limit == 0) { + interior_limit = 1; + } + + int hev_threshold = 0; + if (frame.getFrameType() == 0) /* current frame is a key frame */ { + if (loop_filter_level >= 40) { + hev_threshold = 2; + } else if (loop_filter_level >= 15) { + hev_threshold = 1; + } + } else /* current frame is an interframe */ { + if (loop_filter_level >= 40) { + hev_threshold = 3; + } else if (loop_filter_level >= 20) { + hev_threshold = 2; + } else if (loop_filter_level >= 15) { + hev_threshold = 1; + } + } + + /* Luma and Chroma use the same inter-macroblock edge limit */ + int mbedge_limit = ((loop_filter_level + 2) * 2) + + interior_limit; + /* Luma and Chroma use the same inter-subblock edge limit */ + int sub_bedge_limit = (loop_filter_level * 2) + interior_limit; + + // left + if (x > 0) { + MacroBlock lmb = frame.getMacroBlock(x - 1, y); + for (int b = 0; b < 4; b++) { + SubBlock rsb = rmb.getSubBlock(SubBlock.PLANE.Y1, 0, b); + SubBlock lsb = lmb.getSubBlock(SubBlock.PLANE.Y1, 3, b); + for (int a = 0; a < 4; a++) { + Segment seg = getSegH(rsb, lsb, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegH(rsb, lsb, seg, a); + } + } + } + // sb left + if (!rmb.isSkip_inner_lf()) { + for (int a = 1; a < 4; a++) { + for (int b = 0; b < 4; b++) { + SubBlock lsb = rmb.getSubBlock(SubBlock.PLANE.Y1, + a - 1, b); + SubBlock rsb = rmb.getSubBlock(SubBlock.PLANE.Y1, + a, b); + for (int c = 0; c < 4; c++) { + // System.out.println("sbleft a:"+a+" b:"+b+" c:"+c); + Segment seg = getSegH(rsb, lsb, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegH(rsb, lsb, seg, c); + } + } + } + } + // top + if (y > 0) { + MacroBlock tmb = frame.getMacroBlock(x, y - 1); + for (int b = 0; b < 4; b++) { + SubBlock tsb = tmb.getSubBlock(SubBlock.PLANE.Y1, b, 3); + SubBlock bsb = bmb.getSubBlock(SubBlock.PLANE.Y1, b, 0); + for (int a = 0; a < 4; a++) { + Segment seg = getSegV(bsb, tsb, a); + MBfilter(hev_threshold, interior_limit, + mbedge_limit, seg); + setSegV(bsb, tsb, seg, a); + } + } + } + // sb top + if (!rmb.isSkip_inner_lf()) { + for (int a = 1; a < 4; a++) { + for (int b = 0; b < 4; b++) { + SubBlock tsb = bmb.getSubBlock(SubBlock.PLANE.Y1, + b, a - 1); + SubBlock bsb = bmb.getSubBlock(SubBlock.PLANE.Y1, + b, a); + for (int c = 0; c < 4; c++) { + Segment seg = getSegV(bsb, tsb, c); + subblock_filter(hev_threshold, interior_limit, + sub_bedge_limit, seg); + setSegV(bsb, tsb, seg, c); + } + } + } + } + } + } + } + } + + private static void MBfilter(int hev_threshold, /* detect high edge variance */ + int interior_limit, /* possibly disable filter */ + int edge_limit, Segment seg) { + int p3 = u2s(seg.P3), p2 = u2s(seg.P2), p1 = u2s(seg.P1), p0 = u2s(seg.P0); + int q0 = u2s(seg.Q0), q1 = u2s(seg.Q1), q2 = u2s(seg.Q2), q3 = u2s(seg.Q3); + if (filter_yes(interior_limit, edge_limit, q3, q2, q1, q0, p0, p1, p2, + p3)) { + if (!hev(hev_threshold, p1, p0, q0, q1)) { + // Same as the initial calculation in "common_adjust", + // w is something like twice the edge difference + int w = c(c(p1 - q1) + 3 * (q0 - p0)); + + // 9/64 is approximately 9/63 = 1/7 and 1<<7 = 128 = 2*64. + // So this a, used to adjust the pixels adjacent to the edge, + // is something like 3/7 the edge difference. + int a = (27 * w + 63) >> 7; + + seg.Q0 = s2u(q0 - a); + seg.P0 = s2u(p0 + a); + // Next two are adjusted by 2/7 the edge difference + a = (18 * w + 63) >> 7; + // System.out.println("a: "+a); + seg.Q1 = s2u(q1 - a); + seg.P1 = s2u(p1 + a); + // Last two are adjusted by 1/7 the edge difference + a = (9 * w + 63) >> 7; + seg.Q2 = s2u(q2 - a); + seg.P2 = s2u(p2 + a); + } else + // if hev, do simple filter + { + common_adjust(true, seg); // using outer taps + } + } + } + + /* Clamp, then convert signed number back to pixel value. */ + private static int s2u(int v) { + return c(v) + 128; + } + + private static void setSegH(SubBlock rsb, SubBlock lsb, Segment seg, int a) { + int[][] rdest = rsb.getDest(); + int[][] ldest = lsb.getDest(); + ldest[3][a] = seg.P0; + ldest[2][a] = seg.P1; + ldest[1][a] = seg.P2; + ldest[0][a] = seg.P3; + rdest[0][a] = seg.Q0; + rdest[1][a] = seg.Q1; + rdest[2][a] = seg.Q2; + rdest[3][a] = seg.Q3; + + } + + private static void setSegV(SubBlock bsb, SubBlock tsb, Segment seg, int a) { + int[][] bdest = bsb.getDest(); + int[][] tdest = tsb.getDest(); + tdest[a][3] = seg.P0; + tdest[a][2] = seg.P1; + tdest[a][1] = seg.P2; + tdest[a][0] = seg.P3; + bdest[a][0] = seg.Q0; + bdest[a][1] = seg.Q1; + bdest[a][2] = seg.Q2; + bdest[a][3] = seg.Q3; + + } + + private static void simple_segment(int edge_limit, /* + * do nothing if edge + * difference exceeds + * limit + */ + Segment seg) { + if ((abs(seg.P0 - seg.Q0) * 2 + abs(seg.P1 - seg.Q1) / 2) <= edge_limit) { + common_adjust(true, seg); /* use outer taps */ + } + else { + // TODO? + } + } + + private static void subblock_filter(int hev_threshold, /* + * detect high edge + * variance + */ + int interior_limit, /* possibly disable filter */ + int edge_limit, Segment seg) { + int p3 = u2s(seg.P3), p2 = u2s(seg.P2), p1 = u2s(seg.P1), p0 = u2s(seg.P0); + int q0 = u2s(seg.Q0), q1 = u2s(seg.Q1), q2 = u2s(seg.Q2), q3 = u2s(seg.Q3); + if (filter_yes(interior_limit, edge_limit, q3, q2, q1, q0, p0, p1, p2, p3)) { + boolean hv = hev(hev_threshold, p1, p0, q0, q1); + int a = (common_adjust(hv, seg) + 1) >> 1; + if (!hv) { + seg.Q1 = s2u(q1 - a); + seg.P1 = s2u(p1 + a); + } + } + else { + // TODO? + } + + } + + /* Convert pixel value (0 <= v <= 255) to an 8-bit signed number. */ + private static int u2s(int v) { + return v - 128; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/MacroBlock.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/MacroBlock.java new file mode 100644 index 00000000..5a15befb --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/MacroBlock.java @@ -0,0 +1,840 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +import java.io.IOException; + +final class MacroBlock { + + private int filterLevel; + private boolean keepDebugInfo = false; + private int segmentId; + private int skipCoeff; + private boolean skipInnerLoopFilter; + SubBlock[][] uSubBlocks; + private int uVFilterLevel; + + private int uvMode; + SubBlock[][] vSubBlocks; + private int x, y; + SubBlock y2SubBlock; + private int yMode; + SubBlock[][] ySubBlocks; + + MacroBlock(int x, int y, boolean keepDebugInfo) { + this.x = x - 1; + this.y = y - 1; + this.keepDebugInfo = keepDebugInfo; + + ySubBlocks = new SubBlock[4][4]; + uSubBlocks = new SubBlock[2][2]; + vSubBlocks = new SubBlock[2][2]; + SubBlock above = null; + SubBlock left = null; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + left = null; + above = null; + if (j > 0) { + left = ySubBlocks[j - 1][i]; + } + if (i > 0) { + above = ySubBlocks[j][i - 1]; + } + ySubBlocks[j][i] = new SubBlock(this, above, left, + SubBlock.PLANE.Y1); + } + } + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + left = null; + above = null; + if (j > 0) { + left = uSubBlocks[j - 1][i]; + } + if (i > 0) { + above = uSubBlocks[j][i - 1]; + } + uSubBlocks[j][i] = new SubBlock(this, above, left, + SubBlock.PLANE.U); + } + } + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + left = null; + above = null; + if (j > 0) { + left = vSubBlocks[j - 1][i]; + } + if (i > 0) { + above = vSubBlocks[j][i - 1]; + } + vSubBlocks[j][i] = new SubBlock(this, above, left, + SubBlock.PLANE.V); + } + } + y2SubBlock = new SubBlock(this, null, null, SubBlock.PLANE.Y2); + + } + + public void decodeMacroBlock(VP8Frame frame) throws IOException { + MacroBlock mb = this; + if (mb.getSkipCoeff() > 0) { + if (mb.getYMode() != Globals.B_PRED) { + mb.skipInnerLoopFilter = true; + } + } else if (mb.getYMode() != Globals.B_PRED) { + decodeMacroBlockTokens(frame, true); + } else { + decodeMacroBlockTokens(frame, false); + } + } + + private void decodeMacroBlockTokens(VP8Frame frame, boolean withY2) + throws IOException { + skipInnerLoopFilter = false; + if (withY2) { + skipInnerLoopFilter = skipInnerLoopFilter + | decodePlaneTokens(frame, 1, SubBlock.PLANE.Y2, false); + } + skipInnerLoopFilter = skipInnerLoopFilter + | decodePlaneTokens(frame, 4, SubBlock.PLANE.Y1, withY2); + skipInnerLoopFilter = skipInnerLoopFilter + | decodePlaneTokens(frame, 2, SubBlock.PLANE.U, false); + skipInnerLoopFilter = skipInnerLoopFilter + | decodePlaneTokens(frame, 2, SubBlock.PLANE.V, false); + skipInnerLoopFilter = !skipInnerLoopFilter; + } + + private boolean decodePlaneTokens(VP8Frame frame, int dimentions, + SubBlock.PLANE plane, boolean withY2) throws IOException { + MacroBlock mb = this; + boolean r = false; + for (int y = 0; y < dimentions; y++) { + for (int x = 0; x < dimentions; x++) { + int L = 0; + int A = 0; + int lc = 0; + SubBlock sb = mb.getSubBlock(plane, x, y); + SubBlock left = frame.getLeftSubBlock(sb, plane); + SubBlock above = frame.getAboveSubBlock(sb, plane); + if (left.hasNoZeroToken()) { + + L = 1; + } + + lc += L; + + if (above.hasNoZeroToken()) { + + A = 1; + } + + lc += A; + sb.decodeSubBlock(frame.getTokenBoolDecoder(), + frame.getCoefProbs(), lc, + SubBlock.planeToType(plane, withY2), withY2); + r = r | sb.hasNoZeroToken(); + } + } + return r; + } + + public void dequantMacroBlock(VP8Frame frame) { + MacroBlock mb = this; + if (mb.getYMode() != Globals.B_PRED) { + SubBlock sb = mb.getY2SubBlock(); + int acQValue = frame.getSegmentQuants().getSegQuants()[this.getSegmentId()] + .getY2ac_delta_q(); + int dcQValue = frame.getSegmentQuants().getSegQuants()[this.getSegmentId()].getY2dc(); + + int input[] = new int[16]; + input[0] = sb.getTokens()[0] * dcQValue; + + for (int x = 1; x < 16; x++) { + input[x] = sb.getTokens()[x] * acQValue; + } + + sb.setDiff(IDCT.iwalsh4x4(input)); + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + SubBlock ysb = mb.getYSubBlock(i, j); + ysb.dequantSubBlock(frame, sb.getDiff()[i][j]); + } + } + + mb.predictY(frame); + mb.predictUV(frame); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + SubBlock uvsb = mb.getUSubBlock(j, i); + uvsb.dequantSubBlock(frame, null); + uvsb = mb.getVSubBlock(i, j); + uvsb.dequantSubBlock(frame, null); + } + } + mb.recon_mb(); + + } else { + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + SubBlock sb = mb.getYSubBlock(i, j); + sb.dequantSubBlock(frame, null); + sb.predict(frame); + sb.reconstruct(); + } + } + mb.predictUV(frame); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + SubBlock sb = mb.getUSubBlock(j, i); + sb.dequantSubBlock(frame, null); + sb.reconstruct(); + } + } + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + SubBlock sb = mb.getVSubBlock(j, i); + sb.dequantSubBlock(frame, null); + sb.reconstruct(); + } + } + } + } + + public void drawDebug() { + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + SubBlock sb = ySubBlocks[i][0]; + sb.drawDebugH(); + sb = ySubBlocks[0][j]; + sb.drawDebugV(); + } + } + } + + public String getDebugString() { + String r = " YMode: " + Globals.getModeAsString(yMode); + r = r + "\n UVMode: " + Globals.getModeAsString(uvMode); + r = r + "\n SegmentID: " + segmentId; + r = r + "\n Filter Level: " + filterLevel; + r = r + "\n UV Filter Level: " + uVFilterLevel; + r = r + "\n Skip Coeff: " + skipCoeff; + + return r; + } + + public int getFilterLevel() { + return this.filterLevel; + } + + public SubBlock getBottomSubBlock(int x, SubBlock.PLANE plane) { + switch (plane) { + case Y1: + return ySubBlocks[x][3]; + case U: + return uSubBlocks[x][1]; + case V: + return vSubBlocks[x][1]; + case Y2: + return y2SubBlock; + } + + throw new IllegalArgumentException("Bad plane: " + plane); + } + + public SubBlock getLeftSubBlock(int y, SubBlock.PLANE plane) { + switch (plane) { + case Y1: + return ySubBlocks[0][y]; + case U: + return uSubBlocks[0][y]; + case V: + return vSubBlocks[0][y]; + case Y2: + return y2SubBlock; + } + + throw new IllegalArgumentException("Bad plane: " + plane); + } + + public SubBlock getRightSubBlock(int y, SubBlock.PLANE plane) { + switch (plane) { + case Y1: + return ySubBlocks[3][y]; + case U: + return uSubBlocks[1][y]; + case V: + return vSubBlocks[1][y]; + case Y2: + return y2SubBlock; + } + + throw new IllegalArgumentException("Bad plane: " + plane); + } + + public int getSkipCoeff() { + return skipCoeff; + } + + public SubBlock getSubBlock(SubBlock.PLANE plane, int i, int j) { + switch (plane) { + case Y1: + return getYSubBlock(i, j); + case U: + return getUSubBlock(i, j); + case V: + return getVSubBlock(i, j); + case Y2: + return getY2SubBlock(); + } + + throw new IllegalArgumentException("Bad plane: " + plane); + } + + public int getSubblockX(SubBlock sb) { + if (sb.getPlane() == SubBlock.PLANE.Y1) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (ySubBlocks[x][y] == sb) { + return x; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.U) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + if (uSubBlocks[x][y] == sb) { + return x; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.V) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + if (vSubBlocks[x][y] == sb) { + return x; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.Y2) { + return 0; + } + + return -100; + + } + + public int getSubblockY(SubBlock sb) { + if (sb.getPlane() == SubBlock.PLANE.Y1) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (ySubBlocks[x][y] == sb) { + return y; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.U) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + if (uSubBlocks[x][y] == sb) { + return y; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.V) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + if (vSubBlocks[x][y] == sb) { + return y; + } + } + } + } else if (sb.getPlane() == SubBlock.PLANE.Y2) { + return 0; + } + + return -100; + } + + public SubBlock getUSubBlock(int i, int j) { + return uSubBlocks[i][j]; + } + + public int getUVFilterLevel() { + return this.uVFilterLevel; + } + + public int getUvMode() { + return uvMode; + } + + public SubBlock getVSubBlock(int i, int j) { + return vSubBlocks[i][j]; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public SubBlock getY2SubBlock() { + return y2SubBlock; + } + + public int getYMode() { + return yMode; + } + + public SubBlock getYSubBlock(int i, int j) { + return ySubBlocks[i][j]; + } + + public boolean isKeepDebugInfo() { + return keepDebugInfo; + } + + public boolean isSkip_inner_lf() { + return skipInnerLoopFilter; + } + + public void predictUV(VP8Frame frame) { + MacroBlock aboveMb = frame.getMacroBlock(x, y - 1); + MacroBlock leftMb = frame.getMacroBlock(x - 1, y); + + switch (this.uvMode) { + case Globals.DC_PRED: + // System.out.println("UV DC_PRED"); + + boolean up_available = false; + boolean left_available = false; + int Uaverage = 0; + int Vaverage = 0; + int expected_udc = 0; + int expected_vdc = 0; + if (x > 0) { + left_available = true; + } + if (y > 0) { + up_available = true; + } + if (up_available || left_available) { + if (up_available) { + for (int j = 0; j < 2; j++) { + SubBlock usb = aboveMb.getUSubBlock(j, 1); + SubBlock vsb = aboveMb.getVSubBlock(j, 1); + for (int i = 0; i < 4; i++) { + Uaverage += usb.getDest()[i][3]; + Vaverage += vsb.getDest()[i][3]; + } + } + } + + if (left_available) { + for (int j = 0; j < 2; j++) { + SubBlock usb = leftMb.getUSubBlock(1, j); + SubBlock vsb = leftMb.getVSubBlock(1, j); + for (int i = 0; i < 4; i++) { + Uaverage += usb.getDest()[3][i]; + Vaverage += vsb.getDest()[3][i]; + } + } + } + + int shift = 2; + if (up_available) { + shift++; + } + if (left_available) { + shift++; + } + + expected_udc = (Uaverage + (1 << (shift - 1))) >> shift; + expected_vdc = (Vaverage + (1 << (shift - 1))) >> shift; + } else { + expected_udc = 128; + expected_vdc = 128; + } + + int ufill[][] = new int[4][4]; + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + ufill[x][y] = expected_udc; + } + } + + int vfill[][] = new int[4][4]; + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + vfill[x][y] = expected_vdc; + } + } + + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + SubBlock usb = uSubBlocks[x][y]; + SubBlock vsb = vSubBlocks[x][y]; + usb.setPredict(ufill); + vsb.setPredict(vfill); + } + } + + break; + case Globals.V_PRED: + // System.out.println("UV V_PRED"); + + SubBlock[] aboveUSb = new SubBlock[2]; + SubBlock[] aboveVSb = new SubBlock[2]; + for (int x = 0; x < 2; x++) { + aboveUSb[x] = aboveMb.getUSubBlock(x, 1); + aboveVSb[x] = aboveMb.getVSubBlock(x, 1); + } + + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + SubBlock usb = uSubBlocks[y][x]; + SubBlock vsb = vSubBlocks[y][x]; + int ublock[][] = new int[4][4]; + int vblock[][] = new int[4][4]; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + ublock[j][i] = aboveUSb[y] + .getMacroBlockPredict(Globals.V_PRED)[j][3]; + vblock[j][i] = aboveVSb[y] + .getMacroBlockPredict(Globals.V_PRED)[j][3]; + } + } + usb.setPredict(ublock); + vsb.setPredict(vblock); + } + } + + break; + + case Globals.H_PRED: + // System.out.println("UV H_PRED"); + + SubBlock[] leftUSb = new SubBlock[2]; + SubBlock[] leftVSb = new SubBlock[2]; + for (int x = 0; x < 2; x++) { + leftUSb[x] = leftMb.getUSubBlock(1, x); + leftVSb[x] = leftMb.getVSubBlock(1, x); + } + + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + SubBlock usb = uSubBlocks[x][y]; + SubBlock vsb = vSubBlocks[x][y]; + int ublock[][] = new int[4][4]; + int vblock[][] = new int[4][4]; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + ublock[i][j] = leftUSb[y] + .getMacroBlockPredict(Globals.H_PRED)[3][j]; + vblock[i][j] = leftVSb[y] + .getMacroBlockPredict(Globals.H_PRED)[3][j]; + } + } + usb.setPredict(ublock); + vsb.setPredict(vblock); + } + } + + break; + case Globals.TM_PRED: + // TODO: + // System.out.println("UV TM_PRED MB"); + MacroBlock ALMb = frame.getMacroBlock(x - 1, y - 1); + SubBlock ALUSb = ALMb.getUSubBlock(1, 1); + int alu = ALUSb.getDest()[3][3]; + SubBlock ALVSb = ALMb.getVSubBlock(1, 1); + int alv = ALVSb.getDest()[3][3]; + + aboveUSb = new SubBlock[2]; + leftUSb = new SubBlock[2]; + aboveVSb = new SubBlock[2]; + leftVSb = new SubBlock[2]; + for (int x = 0; x < 2; x++) { + aboveUSb[x] = aboveMb.getUSubBlock(x, 1); + leftUSb[x] = leftMb.getUSubBlock(1, x); + aboveVSb[x] = aboveMb.getVSubBlock(x, 1); + leftVSb[x] = leftMb.getVSubBlock(1, x); + } + + for (int b = 0; b < 2; b++) { + for (int a = 0; a < 4; a++) { + for (int d = 0; d < 2; d++) { + for (int c = 0; c < 4; c++) { + + int upred = leftUSb[b].getDest()[3][a] + + aboveUSb[d].getDest()[c][3] - alu; + upred = Globals.clamp(upred, 255); + uSubBlocks[d][b].setPixel(c, a, upred); + + int vpred = leftVSb[b].getDest()[3][a] + + aboveVSb[d].getDest()[c][3] - alv; + vpred = Globals.clamp(vpred, 255); + vSubBlocks[d][b].setPixel(c, a, vpred); + + } + } + + } + } + + break; + default: + // TODO: FixME! + throw new AssertionError("TODO predict_mb_uv: " + this.yMode); + } + } + + public void predictY(VP8Frame frame) { + MacroBlock aboveMb = frame.getMacroBlock(x, y - 1); + MacroBlock leftMb = frame.getMacroBlock(x - 1, y); + + switch (this.yMode) { + case Globals.DC_PRED: + // System.out.println("DC_PRED"); + boolean up_available = false; + boolean left_available = false; + + int average = 0; + int expected_dc = 0; + if (x > 0) { + left_available = true; + } + if (y > 0) { + up_available = true; + } + + if (up_available || left_available) { + if (up_available) { + for (int j = 0; j < 4; j++) { + SubBlock sb = aboveMb.getYSubBlock(j, 3); + for (int i = 0; i < 4; i++) { + average += sb.getDest()[i][3]; + } + } + } + + if (left_available) { + for (int j = 0; j < 4; j++) { + SubBlock sb = leftMb.getYSubBlock(3, j); + for (int i = 0; i < 4; i++) { + average += sb.getDest()[3][i]; + } + } + } + + int shift = 3; + if (up_available) { + shift++; + } + if (left_available) { + shift++; + } + + expected_dc = (average + (1 << (shift - 1))) >> shift; + } else { + expected_dc = 128; + } + + int fill[][] = new int[4][4]; + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + fill[x][y] = expected_dc; + } + } + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + SubBlock sb = ySubBlocks[x][y]; + sb.setPredict(fill); + } + } + + break; + case Globals.V_PRED: + // System.out.println("V_PRED"); + + SubBlock[] aboveYSb = new SubBlock[4]; + for (int x = 0; x < 4; x++) { + aboveYSb[x] = aboveMb.getYSubBlock(x, 3); + } + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + SubBlock sb = ySubBlocks[x][y]; + int block[][] = new int[4][4]; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + block[i][j] = aboveYSb[x].getPredict( + Globals.B_VE_PRED, false)[i][3]; + } + } + sb.setPredict(block); + + } + } + + break; + + case Globals.H_PRED: + // System.out.println("H_PRED"); + + SubBlock[] leftYSb = new SubBlock[4]; + for (int x = 0; x < 4; x++) { + leftYSb[x] = leftMb.getYSubBlock(3, x); + } + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + SubBlock sb = ySubBlocks[x][y]; + int block[][] = new int[4][4]; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + block[i][j] = leftYSb[y].getPredict( + Globals.B_DC_PRED, true)[3][j]; + } + } + sb.setPredict(block); + } + } + + SubBlock[] leftUSb = new SubBlock[2]; + for (int x = 0; x < 2; x++) { + leftUSb[x] = leftMb.getYSubBlock(1, x); + } + + break; + case Globals.TM_PRED: + // System.out.println("TM_PRED MB"); + MacroBlock ALMb = frame.getMacroBlock(x - 1, y - 1); + SubBlock ALSb = ALMb.getYSubBlock(3, 3); + int al = ALSb.getDest()[3][3]; + + aboveYSb = new SubBlock[4]; + leftYSb = new SubBlock[4]; + for (int x = 0; x < 4; x++) { + aboveYSb[x] = aboveMb.getYSubBlock(x, 3); + } + for (int x = 0; x < 4; x++) { + leftYSb[x] = leftMb.getYSubBlock(3, x); + } + fill = new int[4][4]; + + for (int b = 0; b < 4; b++) { + for (int a = 0; a < 4; a++) { + + for (int d = 0; d < 4; d++) { + for (int c = 0; c < 4; c++) { + + int pred = leftYSb[b].getDest()[3][a] + + aboveYSb[d].getDest()[c][3] - al; + + ySubBlocks[d][b].setPixel(c, a, + Globals.clamp(pred, 255)); + + } + } + + } + } + + break; + default: + System.out.println("TODO predict_mb_y: " + this.yMode); + System.exit(0); + } + } + + public void recon_mb() { + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + SubBlock sb = ySubBlocks[i][j]; + sb.reconstruct(); + } + } + + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 2; i++) { + SubBlock sb = uSubBlocks[i][j]; + sb.reconstruct(); + } + } + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 2; i++) { + SubBlock sb = vSubBlocks[i][j]; + sb.reconstruct(); + } + } + + } + + public void setFilterLevel(int value) { + this.filterLevel = value; + } + + public void setSegmentId(int value) { + this.segmentId = value; + } + + public void setSkipCoeff(int mbSkipCoeff) { + skipCoeff = mbSkipCoeff; + } + + public void setUVFilterLevel(int value) { + this.uVFilterLevel = value; + } + + public void setUvMode(int mode) { + this.uvMode = mode; + } + + public void setYMode(int yMode) { + this.yMode = yMode; + } + + public String toString() { + return "x: " + x + "y: " + y; + } + + public int getSegmentId() { + return segmentId; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Segment.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Segment.java new file mode 100644 index 00000000..cfae8ca8 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/Segment.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class Segment { + int P0, P1, P2, P3; + int Q0, Q1, Q2, Q3; + + public String toString() { + return Globals.toHex(P3) + " " + Globals.toHex(P2) + " " + + Globals.toHex(P1) + " " + Globals.toHex(P0) + " " + + Globals.toHex(Q0) + " " + Globals.toHex(Q1) + " " + + Globals.toHex(Q2) + " " + Globals.toHex(Q3); + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuant.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuant.java new file mode 100644 index 00000000..5304fb37 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuant.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +final class SegmentQuant { + private int filterStrength; + private int Qindex; + private int uvac; + private int uvdc; + private int y1ac; + private int y1dc; + private int y2ac; + private int y2dc; + + private int clip(int val, int max) { + int r = val; + if (val > max) { + r = max; + } + if (r < 0) { + r = 0; + } + return r; + } + + public int getQindex() { + return Qindex; + } + + public int getUvac_delta_q() { + return uvac; + } + + public int getUvdc_delta_q() { + return uvdc; + } + + public int getY1ac() { + return y1ac; + } + + public int getY1dc() { + return y1dc; + } + + public int getY2ac_delta_q() { + return y2ac; + } + + public int getY2dc() { + return y2dc; + } + + public void setFilterStrength(int value) { + this.filterStrength = value; + } + + public void setQindex(int qindex) { + Qindex = qindex; + } + + public void setUvac_delta_q(int uvac_delta_q) { + this.uvac = Globals.vp8AcQLookup[clip(Qindex + uvac_delta_q, 127)]; + } + + public void setUvdc_delta_q(int uvdc_delta_q) { + this.uvdc = Globals.vp8DcQLookup[clip(Qindex + uvdc_delta_q, 127)]; + } + + public void setY1ac() { + this.y1ac = Globals.vp8AcQLookup[clip(Qindex, 127)]; + } + + public void setY1dc(int y1dc) { + this.y1dc = Globals.vp8DcQLookup[clip(Qindex + y1dc, 127)]; + this.setY1ac(); + } + + public void setY2ac_delta_q(int y2ac_delta_q) { + this.y2ac = Globals.vp8AcQLookup[clip(Qindex + y2ac_delta_q, 127)] * 155 / 100; + if (this.y2ac < 8) { + this.y2ac = 8; + } + } + + public void setY2dc(int y2dc_delta_q) { + this.y2dc = Globals.vp8DcQLookup[clip(Qindex + y2dc_delta_q, 127)] * 2; + } + + public int getFilterStrength() { + return filterStrength; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuants.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuants.java new file mode 100644 index 00000000..1099fe75 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SegmentQuants.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +import java.io.IOException; + +final class SegmentQuants { + + private static DeltaQ get_delta_q(BoolDecoder bc, int prev) + throws IOException { + DeltaQ ret = new DeltaQ(); + ret.v = 0; + ret.update = false; + + if (bc.readBit() > 0) { + ret.v = bc.readLiteral(4); + + if (bc.readBit() > 0) { + ret.v = -ret.v; + } + } + + /* Trigger a quantizer update if the delta-q value has changed */ + if (ret.v != prev) { + ret.update = true; + } + + return ret; + } + + private int qIndex; + + private SegmentQuant[] segQuants = new SegmentQuant[Globals.MAX_MB_SEGMENTS]; + + public SegmentQuants() { + for (int x = 0; x < Globals.MAX_MB_SEGMENTS; x++) { + segQuants[x] = new SegmentQuant(); + } + } + + public int getqIndex() { + return qIndex; + } + + public SegmentQuant[] getSegQuants() { + return segQuants; + } + + public void parse(BoolDecoder bc, boolean segmentation_enabled, + boolean mb_segement_abs_delta) throws IOException { + qIndex = bc.readLiteral(7); + boolean q_update = false; + DeltaQ v = get_delta_q(bc, 0); + int y1dc_delta_q = v.v; + q_update = q_update || v.update; + v = get_delta_q(bc, 0); + int y2dc_delta_q = v.v; + q_update = q_update || v.update; + v = get_delta_q(bc, 0); + int y2ac_delta_q = v.v; + q_update = q_update || v.update; + v = get_delta_q(bc, 0); + int uvdc_delta_q = v.v; + q_update = q_update || v.update; + v = get_delta_q(bc, 0); + int uvac_delta_q = v.v; + q_update = q_update || v.update; + + for (SegmentQuant s : segQuants) { + if (!segmentation_enabled) { + s.setQindex(qIndex); + } else if (!mb_segement_abs_delta) { + s.setQindex(s.getQindex() + qIndex); + } + + s.setY1dc(y1dc_delta_q); + s.setY2dc(y2dc_delta_q); + s.setY2ac_delta_q(y2ac_delta_q); + s.setUvdc_delta_q(uvdc_delta_q); + s.setUvac_delta_q(uvac_delta_q); + + } + } + + public void setSegQuants(SegmentQuant[] segQuants) { + this.segQuants = segQuants; + } +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SubBlock.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SubBlock.java new file mode 100644 index 00000000..0577d8b5 --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/SubBlock.java @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +import java.io.IOException; + +final class SubBlock { + public enum PLANE { + U, V, Y1, Y2 + } + + public static int planeToType(PLANE plane, Boolean withY2) { + switch (plane) { + case Y2: + return 1; + case Y1: + if (withY2) + return 0; + else + return 3; + case U: + return 2; + case V: + return 2; + } + return -1; + + } + + private SubBlock above; + + private int[][] dest; + private int[][] diff; + private boolean hasNoZeroToken; + private SubBlock left; + private MacroBlock macroBlock; + private int mode; + private PLANE plane; + private int predict[][]; + private int tokens[]; + + SubBlock(MacroBlock macroBlock, SubBlock above, SubBlock left, + SubBlock.PLANE plane) { + this.macroBlock = macroBlock; + this.plane = plane; + this.above = above; + this.left = left; + mode = 0; + tokens = new int[16]; + for (int z = 0; z < 16; z++) + tokens[z] = 0; + } + + private int DCTextra(BoolDecoder bc2, int p[]) throws IOException { + int v = 0; + int offset = 0; + do { + v += v + bc2.readBool(p[offset]); + offset++; + } while (p[offset] > 0); + return v; + } + + public void decodeSubBlock(BoolDecoder bc2, int[][][][] coef_probs, + int ilc, int type, boolean withY2) throws IOException { + SubBlock sb = this; + int startAt = 0; + if (withY2) + startAt = 1; + int lc = ilc; + int c = 0; + int v = 1; + + boolean skip = false; + + while (!(v == Globals.dct_eob) && c + startAt < 16) { + +// if (!skip) +// v = bc2.readTree(Globals.vp8CoefTree, +// coef_probs[type][Globals.vp8CoefBands[c + startAt]][lc]); +// else +// v = bc2.readTree( +// Globals.vp8CoefTree, +// coef_probs[type][Globals.vp8CoefBands[c + startAt]][lc], +// 1); + v = bc2.readTree(Globals.vp8CoefTree, coef_probs[type][Globals.vp8CoefBands[c + startAt]][lc], + skip ? 1 : 0); + + int dv = decodeToken(bc2, v); + lc = 0; + skip = false; + if (dv == 1 || dv == -1) + lc = 1; + else if (dv > 1 || dv < -1) + lc = 2; + else if (dv == Globals.DCT_0) + skip = true; + + int tokens[] = sb.getTokens(); + + if (v != Globals.dct_eob) + tokens[Globals.vp8defaultZigZag1d[c + startAt]] = dv; + c++; + } + hasNoZeroToken = false; + for (int x = 0; x < 16; x++) + if (tokens[x] != 0) + hasNoZeroToken = true; + } + + private int decodeToken(BoolDecoder bc2, int v) throws IOException { + int r = v; + + if (v == Globals.dct_cat1) { + r = 5 + DCTextra(bc2, Globals.Pcat1); + } + if (v == Globals.dct_cat2) { + r = 7 + DCTextra(bc2, Globals.Pcat2); + } + if (v == Globals.dct_cat3) { + r = 11 + DCTextra(bc2, Globals.Pcat3); + } + if (v == Globals.dct_cat4) { + r = 19 + DCTextra(bc2, Globals.Pcat4); + } + if (v == Globals.dct_cat5) { + r = 35 + DCTextra(bc2, Globals.Pcat5); + } + if (v == Globals.dct_cat6) { + r = 67 + DCTextra(bc2, Globals.Pcat6); + } + if (v != Globals.DCT_0 && v != Globals.dct_eob) { + if (bc2.readBit() > 0) + r = -r; + } + + return r; + } + + public void dequantSubBlock(VP8Frame frame, Integer Dc) { + SubBlock sb = this; + + int[] adjustedValues = new int[16]; + for (int i = 0; i < 16; i++) { + int QValue; + if (plane == PLANE.U || plane == PLANE.V) { + QValue = frame.getSegmentQuants().getSegQuants()[this.getMacroBlock().getSegmentId()] + .getUvac_delta_q(); + if (i == 0) + QValue = frame.getSegmentQuants().getSegQuants()[this.getMacroBlock().getSegmentId()] + .getUvdc_delta_q(); + } else { + QValue = frame.getSegmentQuants().getSegQuants()[this.getMacroBlock().getSegmentId()].getY1ac(); + if (i == 0) + QValue = frame.getSegmentQuants().getSegQuants()[this.getMacroBlock().getSegmentId()] + .getY1dc(); + } + + int inputValue = sb.getTokens()[i]; + adjustedValues[i] = inputValue * QValue; + + } + + if (Dc != null) + adjustedValues[0] = Dc; + + int[][] diff = IDCT.idct4x4llm(adjustedValues); + sb.setDiff(diff); + + } + + public void drawDebug() { + if (dest != null) { + dest[0][0] = 128; + dest[1][0] = 128; + dest[2][0] = 128; + dest[3][0] = 128; + dest[0][0] = 128; + dest[0][1] = 128; + dest[0][2] = 128; + dest[0][3] = 128; + } + + } + + public void drawDebugH() { + if (dest != null) { + dest[0][0] = 0; + dest[1][0] = 0; + dest[2][0] = 0; + dest[3][0] = 0; + } + + } + + public void drawDebugV() { + if (dest != null) { + dest[0][0] = 0; + dest[0][1] = 0; + dest[0][2] = 0; + dest[0][3] = 0; + } + } + + public SubBlock getAbove() { + + return above; + } + + public String getDebugString() { + String r = new String(); + r = r + " " + plane; + if (getMacroBlock().getYMode() == Globals.B_PRED + && plane == SubBlock.PLANE.Y1) + r = r + "\n " + Globals.getSubBlockModeAsString(mode); + return r; + } + + public int[][] getDest() { + if (dest != null) + return dest; + else + return new int[4][4]; + } + + public int[][] getDiff() { + + return diff; + } + + public SubBlock getLeft() { + + return left; + } + + public MacroBlock getMacroBlock() { + return macroBlock; + } + + public int[][] getMacroBlockPredict(int intra_mode) { + if (dest != null) + return dest; + + else { + int rv = 127; + if (intra_mode == Globals.H_PRED) + rv = 129; + int r[][] = new int[4][4]; + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + r[i][j] = rv; + return r; + } + } + + public int getMode() { + return mode; + } + + public PLANE getPlane() { + return plane; + } + + public int[][] getPredict() { + if (predict != null) + return predict; + return getPredict(Globals.B_DC_PRED, false); + } + + public int[][] getPredict(int intra_bmode, boolean left) { + if (dest != null) + return dest; + if (predict != null) + return predict; + else { + int rv = 127; + + if ((intra_bmode == Globals.B_TM_PRED + || intra_bmode == Globals.B_DC_PRED + || intra_bmode == Globals.B_VE_PRED + || intra_bmode == Globals.B_HE_PRED + || intra_bmode == Globals.B_VR_PRED + || intra_bmode == Globals.B_RD_PRED || intra_bmode == Globals.B_HD_PRED) + && left) + + rv = 129; + int r[][] = new int[4][4]; + for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) + r[i][j] = rv; + return r; + } + } + + int[] getTokens() { + return tokens; + } + + public boolean hasNoZeroToken() { + return hasNoZeroToken; + } + + public boolean isDest() { + if (dest == null) + return false; + return true; + } + + public void predict(VP8Frame frame) { + SubBlock sb = this; + SubBlock aboveSb = frame.getAboveSubBlock(sb, sb.getPlane()); + SubBlock leftSb = frame.getLeftSubBlock(sb, sb.getPlane()); + + int[] above = new int[4]; + int[] left = new int[4]; + + above[0] = aboveSb.getPredict(sb.getMode(), false)[0][3]; + above[1] = aboveSb.getPredict(sb.getMode(), false)[1][3]; + above[2] = aboveSb.getPredict(sb.getMode(), false)[2][3]; + above[3] = aboveSb.getPredict(sb.getMode(), false)[3][3]; + left[0] = leftSb.getPredict(sb.getMode(), true)[3][0]; + left[1] = leftSb.getPredict(sb.getMode(), true)[3][1]; + left[2] = leftSb.getPredict(sb.getMode(), true)[3][2]; + left[3] = leftSb.getPredict(sb.getMode(), true)[3][3]; + SubBlock AL = frame.getLeftSubBlock(aboveSb, sb.getPlane()); + + // for above left if left and above is null use left (129?) else use + // above (127?) + int al; + if (!leftSb.isDest() && !aboveSb.isDest()) { + + al = AL.getPredict(sb.getMode(), false)[3][3]; + } else if (!aboveSb.isDest()) { + + al = AL.getPredict(sb.getMode(), false)[3][3]; + } else + al = AL.getPredict(sb.getMode(), true)[3][3]; + SubBlock AR = frame.getAboveRightSubBlock(sb, sb.plane); + int ar[] = new int[4]; + ar[0] = AR.getPredict(sb.getMode(), false)[0][3]; + ar[1] = AR.getPredict(sb.getMode(), false)[1][3]; + ar[2] = AR.getPredict(sb.getMode(), false)[2][3]; + ar[3] = AR.getPredict(sb.getMode(), false)[3][3]; + int[][] p = new int[4][4]; + int pp[]; + switch (sb.getMode()) { + case Globals.B_DC_PRED: + // System.out.println("B_DC_PRED"); + int expected_dc = 0; + + for (int i = 0; i < 4; i++) { + expected_dc += above[i]; + expected_dc += left[i]; + } + expected_dc = (expected_dc + 4) >> 3; + + for (int y = 0; y < 4; y++) + for (int x = 0; x < 4; x++) + p[x][y] = expected_dc; + + break; + case Globals.B_TM_PRED: + + // System.out.println("B_TM_PRED"); + + // prediction similar to true_motion prediction + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + + int pred = above[c] - al + left[r]; + if (pred < 0) + pred = 0; + + if (pred > 255) + pred = 255; + + p[c][r] = pred; + } + } + break; + case Globals.B_VE_PRED: + // System.out.println("B_VE_PRED"); + + int ap[] = new int[4]; + ap[0] = (al + 2 * above[0] + above[1] + 2) >> 2; + ap[1] = (above[0] + 2 * above[1] + above[2] + 2) >> 2; + ap[2] = (above[1] + 2 * above[2] + above[3] + 2) >> 2; + ap[3] = (above[2] + 2 * above[3] + ar[0] + 2) >> 2; + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + + p[c][r] = ap[c]; + + } + } + break; + case Globals.B_HE_PRED: + // System.out.println("B_HE_PRED"); + + int lp[] = new int[4]; + lp[0] = (al + 2 * left[0] + left[1] + 2) >> 2; + lp[1] = (left[0] + 2 * left[1] + left[2] + 2) >> 2; + lp[2] = (left[1] + 2 * left[2] + left[3] + 2) >> 2; + lp[3] = (left[2] + 2 * left[3] + left[3] + 2) >> 2; + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + p[c][r] = lp[r]; + } + } + break; + case Globals.B_LD_PRED: + // System.out.println("B_LD_PRED"); + p[0][0] = (above[0] + above[1] * 2 + above[2] + 2) >> 2; + p[1][0] = p[0][1] = (above[1] + above[2] * 2 + above[3] + 2) >> 2; + p[2][0] = p[1][1] = p[0][2] = (above[2] + above[3] * 2 + ar[0] + 2) >> 2; + p[3][0] = p[2][1] = p[1][2] = p[0][3] = (above[3] + ar[0] * 2 + + ar[1] + 2) >> 2; + p[3][1] = p[2][2] = p[1][3] = (ar[0] + ar[1] * 2 + ar[2] + 2) >> 2; + p[3][2] = p[2][3] = (ar[1] + ar[2] * 2 + ar[3] + 2) >> 2; + p[3][3] = (ar[2] + ar[3] * 2 + ar[3] + 2) >> 2; + + break; + case Globals.B_RD_PRED: + // System.out.println("B_RD_PRED"); + pp = new int[9]; + + pp[0] = left[3]; + pp[1] = left[2]; + pp[2] = left[1]; + pp[3] = left[0]; + pp[4] = al; + pp[5] = above[0]; + pp[6] = above[1]; + pp[7] = above[2]; + pp[8] = above[3]; + + p[0][3] = (pp[0] + pp[1] * 2 + pp[2] + 2) >> 2; + p[1][3] = p[0][2] = (pp[1] + pp[2] * 2 + pp[3] + 2) >> 2; + p[2][3] = p[1][2] = p[0][1] = (pp[2] + pp[3] * 2 + pp[4] + 2) >> 2; + p[3][3] = p[2][2] = p[1][1] = p[0][0] = (pp[3] + pp[4] * 2 + pp[5] + 2) >> 2; + p[3][2] = p[2][1] = p[1][0] = (pp[4] + pp[5] * 2 + pp[6] + 2) >> 2; + p[3][1] = p[2][0] = (pp[5] + pp[6] * 2 + pp[7] + 2) >> 2; + p[3][0] = (pp[6] + pp[7] * 2 + pp[8] + 2) >> 2; + break; + + case Globals.B_VR_PRED: + // System.out.println("B_VR_PRED"); + pp = new int[9]; + + pp[0] = left[3]; + pp[1] = left[2]; + pp[2] = left[1]; + pp[3] = left[0]; + pp[4] = al; + pp[5] = above[0]; + pp[6] = above[1]; + pp[7] = above[2]; + pp[8] = above[3]; + + p[0][3] = (pp[1] + pp[2] * 2 + pp[3] + 2) >> 2; + p[0][2] = (pp[2] + pp[3] * 2 + pp[4] + 2) >> 2; + p[1][3] = p[0][1] = (pp[3] + pp[4] * 2 + pp[5] + 2) >> 2; + p[1][2] = p[0][0] = (pp[4] + pp[5] + 1) >> 1; + p[2][3] = p[1][1] = (pp[4] + pp[5] * 2 + pp[6] + 2) >> 2; + p[2][2] = p[1][0] = (pp[5] + pp[6] + 1) >> 1; + p[3][3] = p[2][1] = (pp[5] + pp[6] * 2 + pp[7] + 2) >> 2; + p[3][2] = p[2][0] = (pp[6] + pp[7] + 1) >> 1; + p[3][1] = (pp[6] + pp[7] * 2 + pp[8] + 2) >> 2; + p[3][0] = (pp[7] + pp[8] + 1) >> 1; + + break; + case Globals.B_VL_PRED: + // System.out.println("B_VL_PRED"); + + p[0][0] = (above[0] + above[1] + 1) >> 1; + p[0][1] = (above[0] + above[1] * 2 + above[2] + 2) >> 2; + p[0][2] = p[1][0] = (above[1] + above[2] + 1) >> 1; + p[1][1] = p[0][3] = (above[1] + above[2] * 2 + above[3] + 2) >> 2; + p[1][2] = p[2][0] = (above[2] + above[3] + 1) >> 1; + p[1][3] = p[2][1] = (above[2] + above[3] * 2 + ar[0] + 2) >> 2; + p[3][0] = p[2][2] = (above[3] + ar[0] + 1) >> 1; + p[3][1] = p[2][3] = (above[3] + ar[0] * 2 + ar[1] + 2) >> 2; + p[3][2] = (ar[0] + ar[1] * 2 + ar[2] + 2) >> 2; + p[3][3] = (ar[1] + ar[2] * 2 + ar[3] + 2) >> 2; + + break; + case Globals.B_HD_PRED: + // System.out.println("B_HD_PRED"); + pp = new int[9]; + pp[0] = left[3]; + pp[1] = left[2]; + pp[2] = left[1]; + pp[3] = left[0]; + pp[4] = al; + pp[5] = above[0]; + pp[6] = above[1]; + pp[7] = above[2]; + pp[8] = above[3]; + + p[0][3] = (pp[0] + pp[1] + 1) >> 1; + p[1][3] = (pp[0] + pp[1] * 2 + pp[2] + 2) >> 2; + p[0][2] = p[2][3] = (pp[1] + pp[2] + 1) >> 1; + p[1][2] = p[3][3] = (pp[1] + pp[2] * 2 + pp[3] + 2) >> 2; + p[2][2] = p[0][1] = (pp[2] + pp[3] + 1) >> 1; + p[3][2] = p[1][1] = (pp[2] + pp[3] * 2 + pp[4] + 2) >> 2; + p[2][1] = p[0][0] = (pp[3] + pp[4] + 1) >> 1; + p[3][1] = p[1][0] = (pp[3] + pp[4] * 2 + pp[5] + 2) >> 2; + p[2][0] = (pp[4] + pp[5] * 2 + pp[6] + 2) >> 2; + p[3][0] = (pp[5] + pp[6] * 2 + pp[7] + 2) >> 2; + break; + case Globals.B_HU_PRED: + // System.out.println("B_HU_PRED"); + + p[0][0] = (left[0] + left[1] + 1) >> 1; + p[1][0] = (left[0] + left[1] * 2 + left[2] + 2) >> 2; + p[2][0] = p[0][1] = (left[1] + left[2] + 1) >> 1; + p[3][0] = p[1][1] = (left[1] + left[2] * 2 + left[3] + 2) >> 2; + p[2][1] = p[0][2] = (left[2] + left[3] + 1) >> 1; + p[3][1] = p[1][2] = (left[2] + left[3] * 2 + left[3] + 2) >> 2; + p[2][2] = p[3][2] = p[0][3] = p[1][3] = p[2][3] = p[3][3] = left[3]; + break; + + default: + // TODO: FixME! + throw new AssertionError("TODO mode: " + sb.getMode()); + } + + sb.setPredict(p); + } + + public void reconstruct() { + SubBlock sb = this; + + int r, c; + int p[][] = sb.getPredict(1, false); + + int dest[][] = new int[4][4]; + int diff[][] = sb.getDiff(); + + for (r = 0; r < 4; r++) { + for (c = 0; c < 4; c++) { + int a = diff[r][c] + p[r][c]; + + if (a < 0) + a = 0; + + if (a > 255) + a = 255; + + dest[r][c] = a; + + } + + } + + sb.setDest(dest); + if (!this.getMacroBlock().isKeepDebugInfo()) { + sb.diff = null; + sb.predict = null; + sb.tokens = null; + } + } + + public void setDest(int[][] dest) { + this.dest = dest; + } + + public void setDiff(int[][] diff) { + this.diff = diff; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public void setPixel(int x, int y, int p) { + if (dest == null) { + dest = new int[4][4]; + } + dest[x][y] = p; + } + + public void setPredict(int[][] predict) { + this.predict = predict; + + } + + public String toString() { + String r = "["; + for (int x = 0; x < 16; x++) + r = r + tokens[x] + " "; + r = r + "]"; + + return r; + } + +} diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/VP8Frame.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/VP8Frame.java new file mode 100644 index 00000000..a2b2748e --- /dev/null +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/vp8/VP8Frame.java @@ -0,0 +1,1131 @@ +/* + * Copyright (c) 2017, Brooss, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.webp.vp8; + +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.twelvemonkeys.imageio.color.YCbCrConverter.convertYCbCr2RGB; + + +public final class VP8Frame { + private static int BLOCK_TYPES = 4; + private static int COEF_BANDS = 8; + private static int MAX_ENTROPY_TOKENS = 12; + private static int MAX_MODE_LF_DELTAS = 4; + private static int MAX_REF_LF_DELTAS = 4; + private static int PREV_COEF_CONTEXTS = 3; + + + private IIOReadProgressListener listener = null; + +// private int bufferCount; +// private int buffersToCreate = 1; + private int[][][][] coefProbs; + private boolean debug = false; + private int filterLevel; + + private int filterType; + + private ImageInputStream frame; + private int frameType; + private int height; +// private Logger logger; + private int macroBlockCols; + private int macroBlockNoCoeffSkip; + private int macroBlockRows; + + private MacroBlock[][] macroBlocks; + private int macroBlockSegementAbsoluteDelta; + private int[] macroBlockSegmentTreeProbs; + private int[] modeLoopFilterDeltas = new int[MAX_MODE_LF_DELTAS]; + private int modeRefLoopFilterDeltaEnabled; + private int modeRefLoopFilterDeltaUpdate; + private int multiTokenPartition = 0; + + private long offset; + private int[] refLoopFilterDeltas = new int[MAX_REF_LF_DELTAS]; + private int refreshEntropyProbs; + private int refreshLastFrame; + private int segmentationIsEnabled; + private SegmentQuants segmentQuants; + private int sharpnessLevel; + private int simpleFilter; + private BoolDecoder tokenBoolDecoder; + private List tokenBoolDecoders; + private int updateMacroBlockSegmentationMap; + private int updateMacroBlockSegmentatonData; + private int width; + + public VP8Frame(ImageInputStream stream) throws IOException { + this.frame = stream; + offset = frame.getStreamPosition(); + this.coefProbs = Globals.getDefaultCoefProbs(); + tokenBoolDecoders = new ArrayList<>(); +// logger = new Logger(); + } + +// public VP8Frame(ImageInputStream stream, int[][][][] coefProbs) throws IOException { +// this.frame = stream; +// offset = frame.getStreamPosition(); +// this.coefProbs = coefProbs; +// tokenBoolDecoders = new ArrayList<>(); +// logger = new Logger(); +// } + + public void setProgressListener(IIOReadProgressListener listener) { + this.listener = listener; + } + + private void createMacroBlocks() { + macroBlocks = new MacroBlock[macroBlockCols + 2][macroBlockRows + 2]; + for (int x = 0; x < macroBlockCols + 2; x++) { + for (int y = 0; y < macroBlockRows + 2; y++) { + macroBlocks[x][y] = new MacroBlock(x, y, debug); + + } + } + } + + public boolean decodeFrame(boolean debug) throws IOException { + this.debug = debug; + segmentQuants = new SegmentQuants(); + int c = frame.readUnsignedByte(); + frameType = getBitAsInt(c, 0); +// logger.log("Frame type: " + frameType); + + if (frameType != 0) { + return false; + } + + int versionNumber = getBitAsInt(c, 1) << 1; + versionNumber += getBitAsInt(c, 2) << 1; + versionNumber += getBitAsInt(c, 3); + +// logger.log("Version Number: " + versionNumber); +// logger.log("show_frame: " + getBit(c, 4)); + + int firstPartitionLengthInBytes; + firstPartitionLengthInBytes = getBitAsInt(c, 5)/*<< 0*/; + firstPartitionLengthInBytes += getBitAsInt(c, 6) << 1; + firstPartitionLengthInBytes += getBitAsInt(c, 7) << 2; + + c = frame.readUnsignedByte(); + firstPartitionLengthInBytes += c << 3; + + c = frame.readUnsignedByte(); + firstPartitionLengthInBytes += c << 11; +// logger.log("first_partition_length_in_bytes: "+ firstPartitionLengthInBytes); + + c = frame.readUnsignedByte(); +// logger.log("StartCode: " + c); + + c = frame.readUnsignedByte(); +// logger.log(" " + c); + + c = frame.readUnsignedByte(); +// logger.log(" " + c); + + c = frame.readUnsignedByte(); + int hBytes = c; + c = frame.readUnsignedByte(); + hBytes += c << 8; + width = (hBytes & 0x3fff); +// logger.log("width: " + width); +// logger.log("hScale: " + (hBytes >> 14)); + + c = frame.readUnsignedByte(); + int vBytes = c; + c = frame.readUnsignedByte(); + vBytes += c << 8; + height = (vBytes & 0x3fff); +// logger.log("height: " + height); +// logger.log("vScale: " + (vBytes >> 14)); + int tWidth = width; + int tHeight = height; + if ((tWidth & 0xf) != 0) { + tWidth += 16 - (tWidth & 0xf); + } + + if ((tHeight & 0xf) != 0) { + tHeight += 16 - (tHeight & 0xf); + } + macroBlockRows = tHeight >> 4; + macroBlockCols = tWidth >> 4; +// logger.log("macroBlockCols: " + macroBlockCols); +// logger.log("macroBlockRows: " + macroBlockRows); + + createMacroBlocks(); + + offset = frame.getStreamPosition(); + + BoolDecoder bc = new BoolDecoder(frame, offset); + + if (frameType == 0) { + int clr_type = bc.readBit(); +// logger.log("clr_type: " + clr_type); +// logger.log("" + bc); + + int clamp_type = bc.readBit(); +// logger.log("clamp_type: " + clamp_type); + + } + segmentationIsEnabled = bc.readBit(); +// logger.log("segmentation_enabled: " + segmentationIsEnabled); + + if (segmentationIsEnabled > 0) { + // TODO: The original code logged a TODO warning here, but what is left to do? + updateMacroBlockSegmentationMap = bc.readBit(); + updateMacroBlockSegmentatonData = bc.readBit(); +// logger.log("update_mb_segmentaton_map: " + updateMacroBlockSegmentationMap); +// logger.log("update_mb_segmentaton_data: " + updateMacroBlockSegmentatonData); + + if (updateMacroBlockSegmentatonData > 0) { + macroBlockSegementAbsoluteDelta = bc.readBit(); + /* For each segmentation feature (Quant and loop filter level) */ + for (int i = 0; i < Globals.MAX_MB_SEGMENTS; i++) { + int value = 0; + if (bc.readBit() > 0) { + value = bc.readLiteral(Globals.vp8MacroBlockFeatureDataBits[0]); + if (bc.readBit() > 0) { + value = -value; + } + } + this.segmentQuants.getSegQuants()[i].setQindex(value); + } + for (int i = 0; i < Globals.MAX_MB_SEGMENTS; i++) { + int value = 0; + if (bc.readBit() > 0) { + value = bc.readLiteral(Globals.vp8MacroBlockFeatureDataBits[1]); + if (bc.readBit() > 0) { + value = -value; + } + } + this.segmentQuants.getSegQuants()[i].setFilterStrength(value); + } + + if (updateMacroBlockSegmentationMap > 0) { + macroBlockSegmentTreeProbs = new int[Globals.MB_FEATURE_TREE_PROBS]; + for (int i = 0; i < Globals.MB_FEATURE_TREE_PROBS; i++) { + int value = 255; + if (bc.readBit() > 0) { + value = bc.readLiteral(8); + } else { + value = 255; + } + macroBlockSegmentTreeProbs[i] = value; + } + } + } + } + + simpleFilter = bc.readBit(); +// logger.log("simpleFilter: " + simpleFilter); + filterLevel = bc.readLiteral(6); + +// logger.log("filter_level: " + filterLevel); + sharpnessLevel = bc.readLiteral(3); +// logger.log("sharpness_level: " + sharpnessLevel); + modeRefLoopFilterDeltaEnabled = bc.readBit(); +// logger.log("mode_ref_lf_delta_enabled: " + modeRefLoopFilterDeltaEnabled); + + if (modeRefLoopFilterDeltaEnabled > 0) { + // Do the deltas need to be updated + modeRefLoopFilterDeltaUpdate = bc.readBit(); +// logger.log("mode_ref_lf_delta_update: " + modeRefLoopFilterDeltaUpdate); + if (modeRefLoopFilterDeltaUpdate > 0) { + for (int i = 0; i < MAX_REF_LF_DELTAS; i++) { + if (bc.readBit() > 0) { + refLoopFilterDeltas[i] = bc.readLiteral(6); + if (bc.readBit() > 0) // Apply sign + { + refLoopFilterDeltas[i] = refLoopFilterDeltas[i] * -1; + } +// logger.log("ref_lf_deltas[i]: " + refLoopFilterDeltas[i]); + } + } + for (int i = 0; i < MAX_MODE_LF_DELTAS; i++) { + + if (bc.readBit() > 0) { + modeLoopFilterDeltas[i] = bc.readLiteral(6); + if (bc.readBit() > 0) // Apply sign + { + modeLoopFilterDeltas[i] = modeLoopFilterDeltas[i] * -1; + } +// logger.log("mode_lf_deltas[i]: " + modeLoopFilterDeltas[i]); + } + } + } + } + + filterType = (filterLevel == 0) ? 0 : (simpleFilter > 0) ? 1 : 2; +// logger.log("filter_type: " + filterType); + + setupTokenDecoder(bc, firstPartitionLengthInBytes, offset); + bc.seek(); + + segmentQuants.parse(bc, segmentationIsEnabled == 1, macroBlockSegementAbsoluteDelta == 1); + + // Determine if the golden frame or ARF buffer should be updated and how. + // For all non key frames the GF and ARF refresh flags and sign bias + // flags must be set explicitly. + if (frameType != 0) { + throw new IllegalArgumentException("Bad input: Not an Intra frame"); + } + + refreshEntropyProbs = bc.readBit(); +// logger.log("refresh_entropy_probs: " + refreshEntropyProbs); + + if (refreshEntropyProbs > 0) { + // TODO? Original code has nothing here... + } + + refreshLastFrame = 0; + if (frameType == 0) { + refreshLastFrame = 1; + } else { + refreshLastFrame = bc.readBit(); + } + +// logger.log("refresh_last_frame: " + refreshLastFrame); + + for (int i = 0; i < BLOCK_TYPES; i++) { + for (int j = 0; j < COEF_BANDS; j++) { + for (int k = 0; k < PREV_COEF_CONTEXTS; k++) { + for (int l = 0; l < MAX_ENTROPY_TOKENS - 1; l++) { + + if (bc.readBool(Globals.vp8CoefUpdateProbs[i][j][k][l]) > 0) { + int newp = bc.readLiteral(8); + this.coefProbs[i][j][k][l] = newp; + } + } + } + } + } + + // Read the mb_no_coeff_skip flag + macroBlockNoCoeffSkip = bc.readBit(); +// logger.log("mb_no_coeff_skip: " + macroBlockNoCoeffSkip); + + if (frameType == 0) { + readModes(bc); + } else { + // TODO + throw new IllegalArgumentException("Bad input: Not an Intra frame"); + } + + int ibc = 0; + int num_part = 1 << multiTokenPartition; + + for (int mb_row = 0; mb_row < macroBlockRows; mb_row++) { + if (num_part > 1) { + tokenBoolDecoder = tokenBoolDecoders.get(ibc); + tokenBoolDecoder.seek(); + + decodeMacroBlockRow(mb_row); + + ibc++; + if (ibc == num_part) { + ibc = 0; + } + } else { + decodeMacroBlockRow(mb_row); + } + + fireProgressUpdate(mb_row); + } + + if (getFilterType() > 0 && getFilterLevel() != 0) { + LoopFilter.loopFilter(this); + } + + return true; + } + + private void decodeMacroBlockRow(int mbRow) throws IOException { + for (int mbCol = 0; mbCol < macroBlockCols; mbCol++) { + MacroBlock mb = getMacroBlock(mbCol, mbRow); + + mb.decodeMacroBlock(this); + mb.dequantMacroBlock(this); + } + } + + private void fireProgressUpdate(int mb_row) { + if (listener != null) { + float percentageDone = (100.0f * ((float) (mb_row + 1) / (float) getMacroBlockRows())); + listener.imageProgress(null, percentageDone); + } + } + + public SubBlock getAboveRightSubBlock(SubBlock sb, SubBlock.PLANE plane) { + // this might break at right edge + SubBlock r; + MacroBlock mb = sb.getMacroBlock(); + int x = mb.getSubblockX(sb); + int y = mb.getSubblockY(sb); + + if (plane == SubBlock.PLANE.Y1) { + + // top row + if (y == 0 && x < 3) { + + MacroBlock mb2 = this.getMacroBlock(mb.getX(), mb.getY() - 1); + r = mb2.getSubBlock(plane, x + 1, 3); + return r; + } + //top right + else if (y == 0 && x == 3) { + + MacroBlock mb2 = this.getMacroBlock(mb.getX() + 1, mb.getY() - 1); + r = mb2.getSubBlock(plane, 0, 3); + + if (mb2.getX() == this.getMacroBlockCols()) { + + int dest[][] = new int[4][4]; + for (int b = 0; b < 4; b++) { + for (int a = 0; a < 4; a++) { + if (mb2.getY() < 0) { + dest[a][b] = 127; + } else { + dest[a][b] = this.getMacroBlock(mb.getX(), mb.getY() - 1).getSubBlock(SubBlock.PLANE.Y1, 3, 3).getDest()[3][3]; + } + } + } + r = new SubBlock(mb2, null, null, SubBlock.PLANE.Y1); + r.setDest(dest); + + + } + + return r; + } + //not right edge or top row + else if (y > 0 && x < 3) { + + r = mb.getSubBlock(plane, x + 1, y - 1); + return r; + } + //else use top right + else { + SubBlock sb2 = mb.getSubBlock(sb.getPlane(), 3, 0); + return this.getAboveRightSubBlock(sb2, plane); + } + } else { + // TODO + throw new IllegalArgumentException("bad input: getAboveRightSubBlock()"); + } + } + + public SubBlock getAboveSubBlock(SubBlock sb, SubBlock.PLANE plane) { + SubBlock r = sb.getAbove(); + if (r == null) { + MacroBlock mb = sb.getMacroBlock(); + int x = mb.getSubblockX(sb); + + MacroBlock mb2 = getMacroBlock(mb.getX(), mb.getY() - 1); + //TODO: SPLIT + while (plane == SubBlock.PLANE.Y2 && mb2.getYMode() == Globals.B_PRED) { + mb2 = getMacroBlock(mb2.getX(), mb2.getY() - 1); + } + r = mb2.getBottomSubBlock(x, sb.getPlane()); + + } + + return r; + } + + private boolean getBit(int data, int bit) { + int r = data & (1 << bit); + return r != 0; + } + + private int getBitAsInt(int data, int bit) { + int r = data & (1 << bit); + if (r != 0) { + return 1; + } + return 0; + } + + int[][][][] getCoefProbs() { + return coefProbs; + } + + public BufferedImage getDebugImageDiff() { + + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int yy, u, v; + yy = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getDiff()[x % 4][y % 4]; + u = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4]; + v = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4]; + c[0] = (int) (1.164 * (yy - 16) + 1.596 * (v - 128)); + c[1] = (int) (1.164 * (yy - 16) - 0.813 * (v - 128) - 0.391 * (u - 128)); + c[2] = (int) (1.164 * (yy - 16) + 2.018 * (u - 128)); + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImagePredict() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int yy, u, v; + yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getPredict()[x % 4][y % 4]; + u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4]; + v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4]; + c[0] = (int) (1.164 * (yy - 16) + 1.596 * (v - 128)); + c[1] = (int) (1.164 * (yy - 16) - 0.813 * (v - 128) - 0.391 * (u - 128)); + c[2] = (int) (1.164 * (yy - 16) + 2.018 * (u - 128)); + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageUBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int u; + u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4]; + c[0] = u; + c[1] = u; + c[2] = u; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageUDiffBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int u; + u = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4]; + c[0] = u; + c[1] = u; + c[2] = u; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageUPredBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int u; + u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4]; + c[0] = u; + c[1] = u; + c[2] = u; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageVBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int v; + v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4]; + c[0] = v; + c[1] = v; + c[2] = v; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageVDiffBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int v; + v = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4]; + c[0] = v; + c[1] = v; + c[2] = v; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageVPredBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int v; + v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4]; + c[0] = v; + c[1] = v; + c[2] = v; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageYBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int yy; + yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getDest()[x % 4][y % 4]; + c[0] = yy; + c[1] = yy; + c[2] = yy; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageYDiffBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int yy; + yy = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getDiff()[x % 4][y % 4]; + c[0] = yy; + c[1] = yy; + c[2] = yy; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public BufferedImage getDebugImageYPredBuffer() { + BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + WritableRaster imRas = bi.getWritableTile(0, 0); + for (int x = 0; x < getWidth(); x++) { + for (int y = 0; y < getHeight(); y++) { + int c[] = new int[3]; + int yy; + yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getPredict()[x % 4][y % 4]; + c[0] = yy; + c[1] = yy; + c[2] = yy; + + for (int z = 0; z < 3; z++) { + if (c[z] < 0) { + c[z] = 0; + } + if (c[z] > 255) { + c[z] = 255; + } + } + imRas.setPixel(x, y, c); + } + } +// bufferCount++; + return bi; + } + + public int getFilterLevel() { + return filterLevel; + } + + public int getFilterType() { + return filterType; + } + + public int getFrameType() { + return frameType; + } + + public int getHeight() { + return height; + } + + public SubBlock getLeftSubBlock(SubBlock sb, SubBlock.PLANE plane) { + SubBlock r = sb.getLeft(); + if (r == null) { + MacroBlock mb = sb.getMacroBlock(); + int y = mb.getSubblockY(sb); + MacroBlock mb2 = getMacroBlock(mb.getX() - 1, mb.getY()); + //TODO: SPLIT + + while (plane == SubBlock.PLANE.Y2 && mb2.getYMode() == Globals.B_PRED) { + mb2 = getMacroBlock(mb2.getX() - 1, mb2.getY()); + } + + r = mb2.getRightSubBlock(y, sb.getPlane()); + + } + + return r; + } + + public MacroBlock getMacroBlock(int mbCol, int mbRow) { + return macroBlocks[mbCol + 1][mbRow + 1]; + } + + public int getMacroBlockCols() { + return macroBlockCols; + } + + public String getMacroBlockDebugString(int mbx, int mby, int sbx, int sby) { + String r = new String(); + if (mbx < this.macroBlockCols && mby < this.getMacroBlockRows()) { + MacroBlock mb = getMacroBlock(mbx, mby); + r = r + mb.getDebugString(); + if (sbx < 4 && sby < 4) { + SubBlock sb = mb.getSubBlock(SubBlock.PLANE.Y1, sbx, sby); + r = r + "\n SubBlock " + sbx + ", " + sby + "\n " + sb.getDebugString(); + sb = mb.getSubBlock(SubBlock.PLANE.Y2, sbx, sby); + r = r + "\n SubBlock " + sbx + ", " + sby + "\n " + sb.getDebugString(); + sb = mb.getSubBlock(SubBlock.PLANE.U, sbx / 2, sby / 2); + r = r + "\n SubBlock " + sbx / 2 + ", " + sby / 2 + "\n " + sb.getDebugString(); + sb = mb.getSubBlock(SubBlock.PLANE.V, sbx / 2, sby / 2); + r = r + "\n SubBlock " + sbx / 2 + ", " + sby / 2 + "\n " + sb.getDebugString(); + } + } + return r; + } + + public int getMacroBlockRows() { + return macroBlockRows; + } + + public int getQIndex() { + return segmentQuants.getqIndex(); + } + + public SegmentQuants getSegmentQuants() { + return segmentQuants; + } + + public int getSharpnessLevel() { + return sharpnessLevel; + } + + public BoolDecoder getTokenBoolDecoder() throws IOException { + tokenBoolDecoder.seek(); + return tokenBoolDecoder; + } + + public int[][] getUBuffer() { + int r[][] = new int[macroBlockCols * 8][macroBlockRows * 8]; + for (int y = 0; y < macroBlockRows; y++) { + for (int x = 0; x < macroBlockCols; x++) { + MacroBlock mb = macroBlocks[x + 1][y + 1]; + for (int b = 0; b < 2; b++) { + for (int a = 0; a < 2; a++) { + SubBlock sb = mb.getUSubBlock(a, b); + for (int d = 0; d < 4; d++) { + for (int c = 0; c < 4; c++) { + r[(x * 8) + (a * 4) + c][(y * 8) + (b * 4) + d] = sb.getDest()[c][d]; + + } + } + } + } + } + } + return r; + } + + public int[][] getVBuffer() { + int r[][] = new int[macroBlockCols * 8][macroBlockRows * 8]; + for (int y = 0; y < macroBlockRows; y++) { + for (int x = 0; x < macroBlockCols; x++) { + MacroBlock mb = macroBlocks[x + 1][y + 1]; + for (int b = 0; b < 2; b++) { + for (int a = 0; a < 2; a++) { + SubBlock sb = mb.getVSubBlock(a, b); + for (int d = 0; d < 4; d++) { + for (int c = 0; c < 4; c++) { + r[(x * 8) + (a * 4) + c][(y * 8) + (b * 4) + d] = sb.getDest()[c][d]; + + } + } + } + } + } + } + return r; + } + + public int getWidth() { + return width; + } + + public int[][] getYBuffer() { + int r[][] = new int[macroBlockCols * 16][macroBlockRows * 16]; + for (int y = 0; y < macroBlockRows; y++) { + for (int x = 0; x < macroBlockCols; x++) { + MacroBlock mb = macroBlocks[x + 1][y + 1]; + for (int b = 0; b < 4; b++) { + for (int a = 0; a < 4; a++) { + SubBlock sb = mb.getYSubBlock(a, b); + for (int d = 0; d < 4; d++) { + for (int c = 0; c < 4; c++) { + r[(x * 16) + (a * 4) + c][(y * 16) + (b * 4) + d] = sb.getDest()[c][d]; + + } + } + } + } + } + } + return r; + } + + private void readModes(BoolDecoder bc) throws IOException { + int mb_row = -1; + int prob_skip_false = 0; + + if (macroBlockNoCoeffSkip > 0) { + prob_skip_false = bc.readLiteral(8); + } + + while (++mb_row < macroBlockRows) { + int mb_col = -1; + while (++mb_col < macroBlockCols) { + + //if (this.segmentation_enabled > 0) { + // logger.log(Level.SEVERE, "TODO:"); + // throw new IllegalArgumentException("bad input: segmentation_enabled()"); + //} + // Read the macroblock coeff skip flag if this feature is in + // use, else default to 0 + MacroBlock mb = getMacroBlock(mb_col, mb_row); + + if ((segmentationIsEnabled > 0) && (updateMacroBlockSegmentationMap > 0)) { + int value = bc.readTree(Globals.macroBlockSegmentTree, this.macroBlockSegmentTreeProbs, 0); + mb.setSegmentId(value); + } + + if (modeRefLoopFilterDeltaEnabled > 0) { + int level = filterLevel; + level = level + refLoopFilterDeltas[0]; + level = (level < 0) ? 0 : (level > 63) ? 63 : level; + mb.setFilterLevel(level); + } else { + mb.setFilterLevel(segmentQuants.getSegQuants()[mb.getSegmentId()].getFilterStrength()); + } + + int mb_skip_coeff = macroBlockNoCoeffSkip > 0 ? bc.readBool(prob_skip_false) : 0; + + mb.setSkipCoeff(mb_skip_coeff); + + int y_mode = readYMode(bc); + + mb.setYMode(y_mode); + + if (y_mode == Globals.B_PRED) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + + SubBlock sb = mb.getYSubBlock(j, i); + + SubBlock A = getAboveSubBlock(sb, SubBlock.PLANE.Y1); + + SubBlock L = getLeftSubBlock(sb, SubBlock.PLANE.Y1); + + int mode = readSubBlockMode(bc, A.getMode(), L.getMode()); + + sb.setMode(mode); + + } + } + + if (modeRefLoopFilterDeltaEnabled > 0) { + int level = mb.getFilterLevel(); + level = level + this.modeLoopFilterDeltas[0]; + level = (level < 0) ? 0 : (level > 63) ? 63 : level; + mb.setFilterLevel(level); + } + } + else { + int BMode; + + + switch (y_mode) { + case Globals.DC_PRED: + BMode = Globals.B_DC_PRED; + break; + case Globals.V_PRED: + BMode = Globals.B_VE_PRED; + break; + case Globals.H_PRED: + BMode = Globals.B_HE_PRED; + break; + case Globals.TM_PRED: + BMode = Globals.B_TM_PRED; + break; + default: + BMode = Globals.B_DC_PRED; + break; + } + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + SubBlock sb = mb.getYSubBlock(x, y); + sb.setMode(BMode); + } + } + } + int mode = readUvMode(bc); + mb.setUvMode(mode); + } + } + } + + private int readPartitionSize(long l) throws IOException { + frame.seek(l); + return frame.readUnsignedByte() + (frame.readUnsignedByte() << 8) + (frame.readUnsignedByte() << 16); + + } + + private int readSubBlockMode(BoolDecoder bc, int A, int L) throws IOException { + return bc.readTree(Globals.vp8SubBlockModeTree, Globals.vp8KeyFrameSubBlockModeProb[A][L], 0); + } + + private int readUvMode(BoolDecoder bc) throws IOException { + return bc.readTree(Globals.vp8UVModeTree, Globals.vp8KeyFrameUVModeProb, 0); + } + + private int readYMode(BoolDecoder bc) throws IOException { + return bc.readTree(Globals.vp8KeyFrameYModeTree, Globals.vp8KeyFrameYModeProb, 0); + } + +// public void setBuffersToCreate(int count) { +// this.buffersToCreate = 3 + count; +//// this.bufferCount = 0; +// } + + private void setupTokenDecoder(BoolDecoder bc, int first_partition_length_in_bytes, long offset) throws IOException { + long partitionSize; + long partitionsStart = offset + first_partition_length_in_bytes; + long partition = partitionsStart; + multiTokenPartition = bc.readLiteral(2); +// logger.log("multi_token_partition: " + multiTokenPartition); + int num_part = 1 << multiTokenPartition; +// logger.log("num_part: " + num_part); + if (num_part > 1) { + partition += 3 * (num_part - 1); + } + for (int i = 0; i < num_part; i++) { + /* + * Calculate the length of this partition. The last partition size + * is implicit. + */ + if (i < num_part - 1) { + + partitionSize = readPartitionSize(partitionsStart + (i * 3)); + bc.seek(); + } else { + partitionSize = frame.length() - partition; + } + + tokenBoolDecoders.add(new BoolDecoder(frame, partition)); + partition += partitionSize; + } + + tokenBoolDecoder = tokenBoolDecoders.get(0); + } + + public void copyTo(final WritableRaster byteRGBRaster) { + // TODO: Consider doing YCbCr -> RGB in reader instead, or pass a flag to allow readRaster reading direct YUV/YCbCr values + + // We might be copying into a smaller raster + int w = Math.min(width, byteRGBRaster.getWidth()); + int h = Math.min(height, byteRGBRaster.getHeight()); + + byte[] yuv = new byte[3]; + byte[] rgb = new byte[4]; // Allow decoding into RGBA, leaving the alpha out. + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + MacroBlock macroBlock = getMacroBlock(x / 16, y / 16); + + yuv[0] = (byte) macroBlock.getSubBlock(SubBlock.PLANE.Y1, (x % 16) / 4, (y % 16) / 4).getDest()[x % 4][y % 4]; + yuv[1] = (byte) macroBlock.getSubBlock(SubBlock.PLANE.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4]; + yuv[2] = (byte) macroBlock.getSubBlock(SubBlock.PLANE.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4]; + + convertYCbCr2RGB(yuv, rgb, 0); + byteRGBRaster.setDataElements(x, y, rgb); + } + } + } + +// public void setFrame(ImageInputStream frame) { +// try { +// this.frame.flush(); +// this.frame.close(); +// this.frame = frame; +// offset = frame.getStreamPosition(); +// this.coefProbs=Globals.getDefaultCoefProbs(); +// tokenBoolDecoders = new ArrayList<>(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +} diff --git a/imageio/imageio-webp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-webp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi new file mode 100644 index 00000000..9fcb5a2e --- /dev/null +++ b/imageio/imageio-webp/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -0,0 +1 @@ +com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi diff --git a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java new file mode 100644 index 00000000..41b83899 --- /dev/null +++ b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java @@ -0,0 +1,317 @@ +package com.twelvemonkeys.imageio.plugins.webp; + +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.w3c.dom.Node; + +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; + +import static org.junit.Assert.*; + +/** + * WebPImageMetadataTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: WebPImageMetadataTest.java,v 1.0 21/11/2020 haraldk Exp$ + */ +public class WebPImageMetadataTest { + @Test + public void testStandardFeatures() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); + final WebPImageMetadata metadata = new WebPImageMetadata(header); + + // Standard metadata format + assertTrue(metadata.isStandardMetadataFormatSupported()); + Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + assertNotNull(root); + assertTrue(root instanceof IIOMetadataNode); + + // Other formats + assertNull(metadata.getNativeMetadataFormatName()); + assertNull(metadata.getExtraMetadataFormatNames()); + assertThrows(IllegalArgumentException.class, new ThrowingRunnable() { + @Override + public void run() { + metadata.getAsTree("com_foo_bar_1.0"); + } + }); + + // Read-only + assertTrue(metadata.isReadOnly()); + assertThrows(IllegalStateException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + metadata.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName)); + } + }); + } + + @Test + public void testStandardChromaRGB() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("3", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + } + + @Test + public void testStandardChromaRGBA() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + header.containsALPH = true; + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("4", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); + + } + + @Test + public void testStandardCompressionVP8() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("VP8", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("FALSE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardCompressionVP8L() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8L, 27, 33); + header.isLossless = true; + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("VP8L", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("TRUE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardCompressionVP8X() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("VP8", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("FALSE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardCompressionVP8XLossless() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + header.isLossless = true; + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("VP8L", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("TRUE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardDataRGB() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataRGBA() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + header.containsALPH = true; + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionNormal() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(2, dimension.getLength()); + + IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDocument() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode document = metadata.getStandardDocumentNode(); + assertNotNull(document); + assertEquals("Document", document.getNodeName()); + assertEquals(1, document.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild(); + assertEquals("FormatVersion", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardText() { + } + + @Test + public void testStandardTransparencyVP8() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNull(transparency); // No transparency, just defaults + } + + @Test + public void testStandardTransparencyVP8L() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNull(transparency); // No transparency, just defaults + } + + @Test + public void testStandardTransparencyVP8X() { + VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); + header.containsALPH = true; + WebPImageMetadata metadata = new WebPImageMetadata(header); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", pixelAspectRatio.getNodeName()); + assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } +} \ No newline at end of file diff --git a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderTest.java b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderTest.java new file mode 100644 index 00000000..538f37c4 --- /dev/null +++ b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReaderTest.java @@ -0,0 +1,61 @@ +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + +import javax.imageio.spi.ImageReaderSpi; +import java.awt.*; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * WebPImageReaderTest + */ +public class WebPImageReaderTest extends ImageReaderAbstractTest { + + @Override + protected List getTestData() { + return asList( + // Original Google WebP sample files + new TestData(getClassLoaderResource("/webp/1.webp"), new Dimension(550, 368)), + new TestData(getClassLoaderResource("/webp/5.webp"), new Dimension(1024, 752)), + // Various samples from javavp8codec project + new TestData(getClassLoaderResource("/webp/bug3.webp"), new Dimension(95, 95)), + new TestData(getClassLoaderResource("/webp/segment01.webp"), new Dimension(160, 160)), + new TestData(getClassLoaderResource("/webp/segment02.webp"), new Dimension(160, 160)), + new TestData(getClassLoaderResource("/webp/segment03.webp"), new Dimension(160, 160)), + new TestData(getClassLoaderResource("/webp/small_1x1.webp"), new Dimension(1, 1)), + new TestData(getClassLoaderResource("/webp/small_1x13.webp"), new Dimension(1, 13)), + new TestData(getClassLoaderResource("/webp/small_13x1.webp"), new Dimension(13, 1)), + new TestData(getClassLoaderResource("/webp/small_31x13.webp"), new Dimension(31, 13)), + new TestData(getClassLoaderResource("/webp/test.webp"), new Dimension(128, 128)), + new TestData(getClassLoaderResource("/webp/very_short.webp"), new Dimension(63, 66)), + // Lossless + new TestData(getClassLoaderResource("/webp/1_webp_ll.webp"), new Dimension(400, 301)), + // Extended format: Alpha + VP8 + new TestData(getClassLoaderResource("/webp/1_webp_a.webp"), new Dimension(400, 301)), + // Extendad format: Anim + new TestData(getClassLoaderResource("/webp/animated-webp-supported.webp"), new Dimension(400, 400)) + ); + } + + @Override + protected ImageReaderSpi createProvider() { + return new WebPImageReaderSpi(); + } + + @Override + protected List getFormatNames() { + return asList("webp", "WEBP"); + } + + @Override + protected List getSuffixes() { + return asList("wbp", "webp"); + } + + @Override + protected List getMIMETypes() { + return asList("image/webp", "image/x-webp"); + } +} diff --git a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfoTest.java b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfoTest.java new file mode 100644 index 00000000..fb9cc0a2 --- /dev/null +++ b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPProviderInfoTest.java @@ -0,0 +1,18 @@ +package com.twelvemonkeys.imageio.plugins.webp; + +import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; +import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest; + +/** + * WebPProviderInfoTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: WebPProviderInfoTest.java,v 1.0 21/11/2020 haraldk Exp$ + */ +public class WebPProviderInfoTest extends ReaderWriterProviderInfoTest { + @Override + protected ReaderWriterProviderInfo createProviderInfo() { + return new WebPProviderInfo(); + } +} \ No newline at end of file diff --git a/imageio/imageio-webp/src/test/resources/webp/1.webp b/imageio/imageio-webp/src/test/resources/webp/1.webp new file mode 100644 index 0000000000000000000000000000000000000000..122741b605f3121d393829ffb5b7a0924db13c86 GIT binary patch literal 30320 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 TU=pkm8Veub3_|1vJMaJi)t)C5 literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/1_webp_a.webp b/imageio/imageio-webp/src/test/resources/webp/1_webp_a.webp new file mode 100644 index 0000000000000000000000000000000000000000..1e4b57ce8e0553b3e76731170179e3b8431f2f1f GIT binary patch literal 18840 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 literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/1_webp_ll.webp b/imageio/imageio-webp/src/test/resources/webp/1_webp_ll.webp new file mode 100644 index 0000000000000000000000000000000000000000..2217831e0f342a0776025ef44c60e6637eb94f94 GIT binary patch literal 81978 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 E0QOAUApigX literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/5.webp b/imageio/imageio-webp/src/test/resources/webp/5.webp new file mode 100644 index 0000000000000000000000000000000000000000..89e6075fde8578d5442556ce2466025650e1d28b GIT binary patch literal 82698 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 BD;59% literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/animated-webp-supported.webp b/imageio/imageio-webp/src/test/resources/webp/animated-webp-supported.webp new file mode 100644 index 0000000000000000000000000000000000000000..7a1d3fe146ae49b89f0cf8ff33864c3053562960 GIT binary patch literal 37342 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 literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/bug3.webp b/imageio/imageio-webp/src/test/resources/webp/bug3.webp new file mode 100644 index 0000000000000000000000000000000000000000..0b59987e4dc16f10924f653ef221794a8c56822b GIT binary patch literal 954 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 Y2$pR2Z^vyP7D5$-U)?xZr=cT&0QdJNwg3PC literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/segment02.webp b/imageio/imageio-webp/src/test/resources/webp/segment02.webp new file mode 100644 index 0000000000000000000000000000000000000000..65d1de4e8c9b585edeaefff460d94483f9d7e59d GIT binary patch literal 7112 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 yal26s9##j&rGV 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 U!?77q{Abk0K25OX*}!C&09e}L1^@s6 literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/small_13x1.webp b/imageio/imageio-webp/src/test/resources/webp/small_13x1.webp new file mode 100644 index 0000000000000000000000000000000000000000..632a349aae8238bc85d0e3e534a2e6c21c9e990a GIT binary patch literal 106 zcmWIYbaP8$U|8CDDop3c4@TtKr*Qj1Fr JjVlZp7y$b9B_03( literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/small_1x1.webp b/imageio/imageio-webp/src/test/resources/webp/small_1x1.webp new file mode 100644 index 0000000000000000000000000000000000000000..47c928274b72b16c23bc3631e880fd626c3fe500 GIT binary patch literal 94 zcmWIYbaM-1U|;IqG!{F)c8^QrJp(M4q#IVAU0RTRqAXNYW literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/small_1x13.webp b/imageio/imageio-webp/src/test/resources/webp/small_1x13.webp new file mode 100644 index 0000000000000000000000000000000000000000..8049cac20292e050193ee99f1f1c206013859ff0 GIT binary patch literal 106 zcmWIYbaP8$U|le@SA zj0&iiWIw}k2_-eI^ya)&iBWXMc2KK(*^8Sgo~EHm5?=lsU(E0KHxN{%lo(t0kGnxB M_=+9OfB*mh0MqSrkpKVy literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/test.webp b/imageio/imageio-webp/src/test/resources/webp/test.webp new file mode 100644 index 0000000000000000000000000000000000000000..c4a7b16c3752490987e0932405fe1b7fa0469701 GIT binary patch literal 4928 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 literal 0 HcmV?d00001 diff --git a/imageio/imageio-webp/src/test/resources/webp/very_short.webp b/imageio/imageio-webp/src/test/resources/webp/very_short.webp new file mode 100644 index 0000000000000000000000000000000000000000..128f6e746489bd769cb6a021607e84bc4c3f4f1c GIT binary patch literal 86 zcmWIYbaV4#U|imageio-tga imageio-thumbsdb imageio-tiff + imageio-webp imageio-xwd