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