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 00000000..122741b6
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/1.webp differ
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 00000000..1e4b57ce
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/1_webp_a.webp differ
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 00000000..2217831e
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/1_webp_ll.webp differ
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 00000000..89e6075f
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/5.webp differ
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 00000000..7a1d3fe1
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/animated-webp-supported.webp differ
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 00000000..0b59987e
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/bug3.webp differ
diff --git a/imageio/imageio-webp/src/test/resources/webp/segment01.webp b/imageio/imageio-webp/src/test/resources/webp/segment01.webp
new file mode 100644
index 00000000..29ec6b36
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/segment01.webp differ
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 00000000..65d1de4e
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/segment02.webp differ
diff --git a/imageio/imageio-webp/src/test/resources/webp/segment03.webp b/imageio/imageio-webp/src/test/resources/webp/segment03.webp
new file mode 100644
index 00000000..1c262192
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/segment03.webp differ
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 00000000..632a349a
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/small_13x1.webp differ
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 00000000..47c92827
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/small_1x1.webp differ
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 00000000..8049cac2
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/small_1x13.webp differ
diff --git a/imageio/imageio-webp/src/test/resources/webp/small_31x13.webp b/imageio/imageio-webp/src/test/resources/webp/small_31x13.webp
new file mode 100644
index 00000000..ab2f7e6d
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/small_31x13.webp differ
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 00000000..c4a7b16c
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/test.webp differ
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 00000000..128f6e74
Binary files /dev/null and b/imageio/imageio-webp/src/test/resources/webp/very_short.webp differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index 08c6de08..e032082d 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -45,6 +45,7 @@
imageio-tga
imageio-thumbsdb
imageio-tiff
+ imageio-webp
imageio-xwd