WebP initial commit

This commit is contained in:
Harald Kuhr 2020-11-22 13:07:58 +01:00
parent 1fe0bdd41f
commit 2376d16ffd
47 changed files with 6949 additions and 0 deletions

View File

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.7-SNAPSHOT</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
<description>
ImageIO plugin for Google WebP File Format (WebP).
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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.
* <p/>
* 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.
* <p/>
* In 2010 Google introduced the WebP picture format, which uses RIFF as a
* container.
*
* @see <a href="https://en.wikipedia.org/wiki/Resource_Interchange_File_Format">Resource Interchange Format</a>
*/
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;
}
}

View File

@ -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()));
}
}

View File

@ -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" : "") +
']';
}
}

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
ImageTypeSpecifier rawImageType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> 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);
}
}

View File

@ -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";
}
}

View File

@ -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
);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
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;
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
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)
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
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;
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
// 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;
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public final class VP8LDecoder {
private final ImageInputStream imageInput;
private final LSBBitReader lsbBitReader;
private final List<Transform> 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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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
}
}

View File

@ -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<WebPImageReader> {
@Override
protected List<TestData> 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<String> getFormatNames() {
return asList("webp", "WEBP");
}
@Override
protected List<String> getSuffixes() {
return asList("wbp", "webp");
}
@Override
protected List<String> getMIMETypes() {
return asList("image/webp", "image/x-webp");
}
}

View File

@ -0,0 +1,18 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* WebPProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

View File

@ -45,6 +45,7 @@
<module>imageio-tga</module> <module>imageio-tga</module>
<module>imageio-thumbsdb</module> <module>imageio-thumbsdb</module>
<module>imageio-tiff</module> <module>imageio-tiff</module>
<module>imageio-webp</module>
<module>imageio-xwd</module> <module>imageio-xwd</module>
<!-- Wrappers for 3rd party libs --> <!-- Wrappers for 3rd party libs -->