com.twelvemonkeys.imageio.plugins.webp.vp8.VP8Frame Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imageio-webp Show documentation
Show all versions of imageio-webp Show documentation
ImageIO plugin for Google WebP File Format (WebP).
The newest version!
/*
* 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.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.imageio.color.YCbCrConverter.convertRec601YCbCr2RGB;
public final class VP8Frame {
private static final int BLOCK_TYPES = 4;
private static final int COEF_BANDS = 8;
private static final int MAX_ENTROPY_TOKENS = 12;
private static final int MAX_MODE_LF_DELTAS = 4;
private static final int MAX_REF_LF_DELTAS = 4;
private static final int PREV_COEF_CONTEXTS = 3;
private IIOReadProgressListener listener = null;
private final int[][][][] coefProbs;
private int filterLevel;
private final ImageInputStream frame;
private final boolean debug;
private int frameType;
private int height;
private int macroBlockCols;
private int macroBlockNoCoeffSkip;
private int macroBlockRows;
private MacroBlock[][] macroBlocks;
private int macroBlockSegementAbsoluteDelta;
private int[] macroBlockSegmentTreeProbs;
private final int[] modeLoopFilterDeltas = new int[MAX_MODE_LF_DELTAS];
private int modeRefLoopFilterDeltaEnabled;
private int modeRefLoopFilterDeltaUpdate;
private int multiTokenPartition = 0;
private long offset;
private final int[] refLoopFilterDeltas = new int[MAX_REF_LF_DELTAS];
private int refreshEntropyProbs;
private int refreshLastFrame;
private int segmentationIsEnabled;
private SegmentQuants segmentQuants;
private int sharpnessLevel;
private boolean simpleFilter;
private BoolDecoder tokenBoolDecoder;
private final List tokenBoolDecoders;
private int updateMacroBlockSegmentationMap;
private int updateMacroBlockSegmentatonData;
private int width;
public VP8Frame(final ImageInputStream stream, boolean debug) throws IOException {
this.frame = stream;
this.debug = debug;
offset = frame.getStreamPosition();
coefProbs = Globals.getDefaultCoefProbs();
tokenBoolDecoders = new ArrayList<>();
}
public void setProgressListener(IIOReadProgressListener listener) {
this.listener = listener;
}
private void createMacroBlocks() {
macroBlocks = new MacroBlock[macroBlockRows + 2][macroBlockCols + 2];
for (int y = 0; y < macroBlockRows + 2; y++) {
for (int x = 0; x < macroBlockCols + 2; x++) {
macroBlocks[y][x] = new MacroBlock(x, y, debug);
}
}
}
public boolean decode(final WritableRaster raster, final ImageReadParam param) throws IOException {
segmentQuants = new SegmentQuants();
int c = frame.readUnsignedByte();
frameType = getBitAsInt(c, 0);
if (frameType != 0) {
return false;
}
int versionNumber = getBitAsInt(c, 1) << 1;
versionNumber += getBitAsInt(c, 2) << 1;
versionNumber += getBitAsInt(c, 3);
// logger.log("Version Number: " + versionNumber);
// logger.log("show_frame: " + getBit(c, 4));
int firstPartitionLengthInBytes;
firstPartitionLengthInBytes = getBitAsInt(c, 5)/*<< 0*/;
firstPartitionLengthInBytes += getBitAsInt(c, 6) << 1;
firstPartitionLengthInBytes += getBitAsInt(c, 7) << 2;
c = frame.readUnsignedByte();
firstPartitionLengthInBytes += c << 3;
c = frame.readUnsignedByte();
firstPartitionLengthInBytes += c << 11;
// logger.log("first_partition_length_in_bytes: "+ firstPartitionLengthInBytes);
c = frame.readUnsignedByte();
// logger.log("StartCode: " + c);
c = frame.readUnsignedByte();
// logger.log(" " + c);
c = frame.readUnsignedByte();
// logger.log(" " + c);
c = frame.readUnsignedByte();
int hBytes = c;
c = frame.readUnsignedByte();
hBytes += c << 8;
width = (hBytes & 0x3fff);
// logger.log("width: " + width);
// logger.log("hScale: " + (hBytes >> 14));
c = frame.readUnsignedByte();
int vBytes = c;
c = frame.readUnsignedByte();
vBytes += c << 8;
height = (vBytes & 0x3fff);
// logger.log("height: " + height);
// logger.log("vScale: " + (vBytes >> 14));
int tWidth = width;
int tHeight = height;
if ((tWidth & 0xf) != 0) {
tWidth += 16 - (tWidth & 0xf);
}
if ((tHeight & 0xf) != 0) {
tHeight += 16 - (tHeight & 0xf);
}
macroBlockRows = tHeight >> 4;
macroBlockCols = tWidth >> 4;
// logger.log("macroBlockCols: " + macroBlockCols);
// logger.log("macroBlockRows: " + macroBlockRows);
createMacroBlocks();
offset = frame.getStreamPosition();
BoolDecoder bc = new BoolDecoder(frame, offset);
if (frameType == 0) {
int clr_type = bc.readBit();
// logger.log("clr_type: " + clr_type);
// logger.log("" + bc);
int clamp_type = bc.readBit();
// logger.log("clamp_type: " + clamp_type);
}
segmentationIsEnabled = bc.readBit();
// logger.log("segmentation_enabled: " + segmentationIsEnabled);
if (segmentationIsEnabled > 0) {
// TODO: The original code logged a TODO warning here, but what is left to do?
updateMacroBlockSegmentationMap = bc.readBit();
updateMacroBlockSegmentatonData = bc.readBit();
// logger.log("update_mb_segmentaton_map: " + updateMacroBlockSegmentationMap);
// logger.log("update_mb_segmentaton_data: " + updateMacroBlockSegmentatonData);
if (updateMacroBlockSegmentatonData > 0) {
macroBlockSegementAbsoluteDelta = bc.readBit();
/* For each segmentation feature (Quant and loop filter level) */
for (int i = 0; i < Globals.MAX_MB_SEGMENTS; i++) {
int value = 0;
if (bc.readBit() > 0) {
value = bc.readLiteral(Globals.vp8MacroBlockFeatureDataBits[0]);
if (bc.readBit() > 0) {
value = -value;
}
}
this.segmentQuants.getSegQuants()[i].setQindex(value);
}
for (int i = 0; i < Globals.MAX_MB_SEGMENTS; i++) {
int value = 0;
if (bc.readBit() > 0) {
value = bc.readLiteral(Globals.vp8MacroBlockFeatureDataBits[1]);
if (bc.readBit() > 0) {
value = -value;
}
}
this.segmentQuants.getSegQuants()[i].setFilterStrength(value);
}
if (updateMacroBlockSegmentationMap > 0) {
macroBlockSegmentTreeProbs = new int[Globals.MB_FEATURE_TREE_PROBS];
for (int i = 0; i < Globals.MB_FEATURE_TREE_PROBS; i++) {
int value = bc.readBit() > 0 ? bc.readLiteral(8) : 255;
macroBlockSegmentTreeProbs[i] = value;
}
}
}
}
simpleFilter = bc.readBit() != 0;
// logger.log("simpleFilter: " + simpleFilter);
filterLevel = bc.readLiteral(6);
// logger.log("filter_level: " + filterLevel);
sharpnessLevel = bc.readLiteral(3);
// logger.log("sharpness_level: " + sharpnessLevel);
modeRefLoopFilterDeltaEnabled = bc.readBit();
// logger.log("mode_ref_lf_delta_enabled: " + modeRefLoopFilterDeltaEnabled);
if (modeRefLoopFilterDeltaEnabled > 0) {
// Do the deltas need to be updated
modeRefLoopFilterDeltaUpdate = bc.readBit();
// logger.log("mode_ref_lf_delta_update: " + modeRefLoopFilterDeltaUpdate);
if (modeRefLoopFilterDeltaUpdate > 0) {
for (int i = 0; i < MAX_REF_LF_DELTAS; i++) {
if (bc.readBit() > 0) {
refLoopFilterDeltas[i] = bc.readLiteral(6);
if (bc.readBit() > 0) { // Apply sign
refLoopFilterDeltas[i] = refLoopFilterDeltas[i] * -1;
}
// logger.log("ref_lf_deltas[i]: " + refLoopFilterDeltas[i]);
}
}
for (int i = 0; i < MAX_MODE_LF_DELTAS; i++) {
if (bc.readBit() > 0) {
modeLoopFilterDeltas[i] = bc.readLiteral(6);
if (bc.readBit() > 0) { // Apply sign
modeLoopFilterDeltas[i] = modeLoopFilterDeltas[i] * -1;
}
// logger.log("mode_lf_deltas[i]: " + modeLoopFilterDeltas[i]);
}
}
}
}
setupTokenDecoder(bc, firstPartitionLengthInBytes, offset);
bc.seek();
segmentQuants.parse(bc, segmentationIsEnabled == 1, macroBlockSegementAbsoluteDelta == 1);
// Determine if the golden frame or ARF buffer should be updated and how.
// For all non key frames the GF and ARF refresh flags and sign bias
// flags must be set explicitly.
if (frameType != 0) {
throw new IllegalArgumentException("Bad input: Not an Intra frame");
}
refreshEntropyProbs = bc.readBit();
// logger.log("refresh_entropy_probs: " + refreshEntropyProbs);
if (refreshEntropyProbs > 0) {
// TODO? Original code has nothing here...
}
refreshLastFrame = 0;
if (frameType == 0) {
refreshLastFrame = 1;
}
else {
refreshLastFrame = bc.readBit();
}
// logger.log("refresh_last_frame: " + refreshLastFrame);
for (int i = 0; i < BLOCK_TYPES; i++) {
for (int j = 0; j < COEF_BANDS; j++) {
for (int k = 0; k < PREV_COEF_CONTEXTS; k++) {
for (int l = 0; l < MAX_ENTROPY_TOKENS - 1; l++) {
if (bc.readBool(Globals.vp8CoefUpdateProbs[i][j][k][l]) > 0) {
int newp = bc.readLiteral(8);
this.coefProbs[i][j][k][l] = newp;
}
}
}
}
}
// Read the mb_no_coeff_skip flag
macroBlockNoCoeffSkip = bc.readBit();
// logger.log("mb_no_coeff_skip: " + macroBlockNoCoeffSkip);
if (frameType == 0) {
readModes(bc);
}
else {
throw new IIOException("Bad input: Not an Intra frame");
}
int ibc = 0;
int parts = 1 << multiTokenPartition;
Rectangle region = param != null && param.getSourceRegion() != null ? param.getSourceRegion() : raster.getBounds();
int sourceXSubsampling = param != null ? param.getSourceXSubsampling() : 1;
int sourceYSubsampling = param != null ? param.getSourceYSubsampling() : 1;
for (int row = 0; row < macroBlockRows; row++) {
if (parts > 1) {
tokenBoolDecoder = tokenBoolDecoders.get(ibc);
tokenBoolDecoder.seek();
ibc++;
if (ibc == parts) {
ibc = 0;
}
}
decodeMacroBlockRow(row, raster, region, sourceXSubsampling, sourceYSubsampling);
fireProgressUpdate(row);
}
return true;
}
private void decodeMacroBlockRow(final int mbRow, final WritableRaster raster, final Rectangle region,
final int xSubsampling, final int ySubsampling) throws IOException {
final boolean filter = filterLevel != 0;
MacroBlock left = null;
MacroBlock[] prevRow = macroBlocks[mbRow];
MacroBlock[] currRow = macroBlocks[mbRow + 1];
for (int mbCol = 0; mbCol < macroBlockCols; mbCol++) {
MacroBlock mb = currRow[mbCol + 1];
mb.decodeMacroBlock(this);
mb.dequantMacroBlock(this);
if (filter) {
MacroBlock top = mbRow > 0 ? prevRow[mbCol + 1] : null;
LoopFilter.loopFilterBlock(mb, left, top, frameType, simpleFilter, sharpnessLevel);
}
copyBlock(mb, raster, region, xSubsampling, ySubsampling);
left = mb;
}
}
private void fireProgressUpdate(int mbRow) {
if (listener != null) {
float percentageDone = (100.0f * ((float) (mbRow + 1) / (float) getMacroBlockRows()));
listener.imageProgress(null, percentageDone);
}
}
public SubBlock getAboveRightSubBlock(SubBlock sb, SubBlock.Plane plane) {
// this might break at right edge
SubBlock r;
MacroBlock mb = sb.getMacroBlock();
int x = mb.getSubblockX(sb);
int y = mb.getSubblockY(sb);
if (plane == SubBlock.Plane.Y1) {
// top row
if (y == 0 && x < 3) {
MacroBlock mb2 = this.getMacroBlock(mb.getX(), mb.getY() - 1);
r = mb2.getSubBlock(plane, x + 1, 3);
return r;
}
//top right
else if (y == 0 && x == 3) {
MacroBlock mb2 = this.getMacroBlock(mb.getX() + 1, mb.getY() - 1);
r = mb2.getSubBlock(plane, 0, 3);
if (mb2.getX() == this.getMacroBlockCols()) {
int[][] dest = new int[4][4];
for (int b = 0; b < 4; b++) {
for (int a = 0; a < 4; a++) {
if (mb2.getY() < 0) {
dest[a][b] = 127;
}
else {
dest[a][b] = this.getMacroBlock(mb.getX(), mb.getY() - 1).getSubBlock(SubBlock.Plane.Y1, 3, 3).getDest()[3][3];
}
}
}
r = new SubBlock(mb2, null, null, SubBlock.Plane.Y1);
r.setDest(dest);
}
return r;
}
//not right edge or top row
else if (y > 0 && x < 3) {
r = mb.getSubBlock(plane, x + 1, y - 1);
return r;
}
//else use top right
else {
SubBlock sb2 = mb.getSubBlock(sb.getPlane(), 3, 0);
return this.getAboveRightSubBlock(sb2, plane);
}
}
else {
// TODO
throw new IllegalArgumentException("bad input: getAboveRightSubBlock()");
}
}
public SubBlock getAboveSubBlock(SubBlock sb, SubBlock.Plane plane) {
SubBlock above = sb.getAbove();
if (above == null) {
MacroBlock mb = sb.getMacroBlock();
int x = mb.getSubblockX(sb);
MacroBlock mb2 = getMacroBlock(mb.getX(), mb.getY() - 1);
//TODO: SPLIT
while (plane == SubBlock.Plane.Y2 && mb2.getYMode() == Globals.B_PRED) {
mb2 = getMacroBlock(mb2.getX(), mb2.getY() - 1);
}
above = mb2.getBottomSubBlock(x, sb.getPlane());
}
return above;
}
// private boolean getBit(int data, int bit) {
// int r = data & (1 << bit);
// return r != 0;
// }
private int getBitAsInt(int data, int bit) {
int r = data & (1 << bit);
if (r != 0) {
return 1;
}
return 0;
}
int[][][][] getCoefProbs() {
return coefProbs;
}
public BufferedImage getDebugImageDiff() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int yy, u, v;
yy = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.Y1, (x % 16) / 4, (y % 16) / 4).getDiff()[x % 4][y % 4];
u = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4];
v = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4];
c[0] = (int) (1.164 * (yy - 16) + 1.596 * (v - 128));
c[1] = (int) (1.164 * (yy - 16) - 0.813 * (v - 128) - 0.391 * (u - 128));
c[2] = (int) (1.164 * (yy - 16) + 2.018 * (u - 128));
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImagePredict() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int yy, u, v;
yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.Y1, (x % 16) / 4, (y % 16) / 4).getPredict()[x % 4][y % 4];
u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4];
v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4];
c[0] = (int) (1.164 * (yy - 16) + 1.596 * (v - 128));
c[1] = (int) (1.164 * (yy - 16) - 0.813 * (v - 128) - 0.391 * (u - 128));
c[2] = (int) (1.164 * (yy - 16) + 2.018 * (u - 128));
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageUBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int u;
u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4];
c[0] = u;
c[1] = u;
c[2] = u;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageUDiffBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int u;
u = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4];
c[0] = u;
c[1] = u;
c[2] = u;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageUPredBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int u;
u = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.U, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4];
c[0] = u;
c[1] = u;
c[2] = u;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageVBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int v;
v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDest()[(x / 2) % 4][(y / 2) % 4];
c[0] = v;
c[1] = v;
c[2] = v;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageVDiffBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int v;
v = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getDiff()[(x / 2) % 4][(y / 2) % 4];
c[0] = v;
c[1] = v;
c[2] = v;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageVPredBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int v;
v = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.V, ((x / 2) % 8) / 4, ((y / 2) % 8) / 4).getPredict()[(x / 2) % 4][(y / 2) % 4];
c[0] = v;
c[1] = v;
c[2] = v;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageYBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int yy;
yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.Y1, (x % 16) / 4, (y % 16) / 4).getDest()[x % 4][y % 4];
c[0] = yy;
c[1] = yy;
c[2] = yy;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageYDiffBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int yy;
yy = 127 + this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.Y1, (x % 16) / 4, (y % 16) / 4).getDiff()[x % 4][y % 4];
c[0] = yy;
c[1] = yy;
c[2] = yy;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public BufferedImage getDebugImageYPredBuffer() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
for (int y = 0; y < getHeight(); y++) {
int[] c = new int[3];
int yy;
yy = this.getMacroBlock(x / 16, y / 16).getSubBlock(SubBlock.Plane.Y1, (x % 16) / 4, (y % 16) / 4).getPredict()[x % 4][y % 4];
c[0] = yy;
c[1] = yy;
c[2] = yy;
for (int z = 0; z < 3; z++) {
if (c[z] < 0) {
c[z] = 0;
}
if (c[z] > 255) {
c[z] = 255;
}
}
imRas.setPixel(x, y, c);
}
}
// bufferCount++;
return bi;
}
public int getFrameType() {
return frameType;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public SubBlock getLeftSubBlock(SubBlock sb, SubBlock.Plane plane) {
SubBlock r = sb.getLeft();
if (r == null) {
MacroBlock mb = sb.getMacroBlock();
int y = mb.getSubblockY(sb);
MacroBlock mb2 = getMacroBlock(mb.getX() - 1, mb.getY());
//TODO: SPLIT
while (plane == SubBlock.Plane.Y2 && mb2.getYMode() == Globals.B_PRED) {
mb2 = getMacroBlock(mb2.getX() - 1, mb2.getY());
}
r = mb2.getRightSubBlock(y, sb.getPlane());
}
return r;
}
public MacroBlock getMacroBlock(int mbCol, int mbRow) {
// return macroBlocks[mbCol + 1][mbRow + 1];
return macroBlocks[mbRow + 1][mbCol + 1];
}
public int getMacroBlockCols() {
return macroBlockCols;
}
public String getMacroBlockDebugString(int mbx, int mby, int sbx, int sby) {
String r = "";
if (mbx < this.macroBlockCols && mby < this.getMacroBlockRows()) {
MacroBlock mb = getMacroBlock(mbx, mby);
r = r + mb.getDebugString();
if (sbx < 4 && sby < 4) {
SubBlock sb = mb.getSubBlock(SubBlock.Plane.Y1, sbx, sby);
r = r + "\n SubBlock " + sbx + ", " + sby + "\n " + sb.getDebugString();
sb = mb.getSubBlock(SubBlock.Plane.Y2, sbx, sby);
r = r + "\n SubBlock " + sbx + ", " + sby + "\n " + sb.getDebugString();
sb = mb.getSubBlock(SubBlock.Plane.U, sbx / 2, sby / 2);
r = r + "\n SubBlock " + sbx / 2 + ", " + sby / 2 + "\n " + sb.getDebugString();
sb = mb.getSubBlock(SubBlock.Plane.V, sbx / 2, sby / 2);
r = r + "\n SubBlock " + sbx / 2 + ", " + sby / 2 + "\n " + sb.getDebugString();
}
}
return r;
}
public int getMacroBlockRows() {
return macroBlockRows;
}
public int getQIndex() {
return segmentQuants.getqIndex();
}
public SegmentQuants getSegmentQuants() {
return segmentQuants;
}
public int getSharpnessLevel() {
return sharpnessLevel;
}
public BoolDecoder getTokenBoolDecoder() throws IOException {
tokenBoolDecoder.seek();
return tokenBoolDecoder;
}
// public int[][] getUBuffer() {
// int[][] r = new int[macroBlockCols * 8][macroBlockRows * 8];
// for (int y = 0; y < macroBlockRows; y++) {
// for (int x = 0; x < macroBlockCols; x++) {
// MacroBlock mb = macroBlocks[x + 1][y + 1];
// for (int b = 0; b < 2; b++) {
// for (int a = 0; a < 2; a++) {
// SubBlock sb = mb.getUSubBlock(a, b);
// for (int d = 0; d < 4; d++) {
// for (int c = 0; c < 4; c++) {
// r[(x * 8) + (a * 4) + c][(y * 8) + (b * 4) + d] = sb.getDest()[c][d];
// }
// }
// }
// }
// }
// }
// return r;
// }
//
// public int[][] getVBuffer() {
// int[][] r = new int[macroBlockCols * 8][macroBlockRows * 8];
// for (int y = 0; y < macroBlockRows; y++) {
// for (int x = 0; x < macroBlockCols; x++) {
// MacroBlock mb = macroBlocks[x + 1][y + 1];
// for (int b = 0; b < 2; b++) {
// for (int a = 0; a < 2; a++) {
// SubBlock sb = mb.getVSubBlock(a, b);
// for (int d = 0; d < 4; d++) {
// for (int c = 0; c < 4; c++) {
// r[(x * 8) + (a * 4) + c][(y * 8) + (b * 4) + d] = sb.getDest()[c][d];
// }
// }
// }
// }
// }
// }
// return r;
// }
// public int[][] getYBuffer() {
// int[][] r = new int[macroBlockCols * 16][macroBlockRows * 16];
// for (int y = 0; y < macroBlockRows; y++) {
// for (int x = 0; x < macroBlockCols; x++) {
// MacroBlock mb = macroBlocks[x + 1][y + 1];
// for (int b = 0; b < 4; b++) {
// for (int a = 0; a < 4; a++) {
// SubBlock sb = mb.getYSubBlock(a, b);
// for (int d = 0; d < 4; d++) {
// for (int c = 0; c < 4; c++) {
// r[(x * 16) + (a * 4) + c][(y * 16) + (b * 4) + d] = sb.getDest()[c][d];
// }
// }
// }
// }
// }
// }
// return r;
// }
private void readModes(BoolDecoder bc) throws IOException {
int mb_row = -1;
int prob_skip_false = 0;
if (macroBlockNoCoeffSkip > 0) {
prob_skip_false = bc.readLiteral(8);
}
while (++mb_row < macroBlockRows) {
int mb_col = -1;
while (++mb_col < macroBlockCols) {
//if (this.segmentation_enabled > 0) {
// logger.log(Level.SEVERE, "TODO:");
// throw new IllegalArgumentException("bad input: segmentation_enabled()");
//}
// Read the macroblock coeff skip flag if this feature is in
// use, else default to 0
MacroBlock mb = getMacroBlock(mb_col, mb_row);
if ((segmentationIsEnabled > 0) && (updateMacroBlockSegmentationMap > 0)) {
int value = bc.readTree(Globals.macroBlockSegmentTree, this.macroBlockSegmentTreeProbs, 0);
mb.setSegmentId(value);
}
if (modeRefLoopFilterDeltaEnabled > 0) {
int level = filterLevel;
level = level + refLoopFilterDeltas[0];
level = (level < 0) ? 0 : Math.min(level, 63);
mb.setFilterLevel(level);
}
else {
mb.setFilterLevel(segmentQuants.getSegQuants()[mb.getSegmentId()].getFilterStrength());
}
int mb_skip_coeff = macroBlockNoCoeffSkip > 0 ? bc.readBool(prob_skip_false) : 0;
mb.setSkipCoeff(mb_skip_coeff);
int y_mode = readYMode(bc);
mb.setYMode(y_mode);
if (y_mode == Globals.B_PRED) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
SubBlock sb = mb.getYSubBlock(j, i);
SubBlock A = getAboveSubBlock(sb, SubBlock.Plane.Y1);
SubBlock L = getLeftSubBlock(sb, SubBlock.Plane.Y1);
int mode = readSubBlockMode(bc, A.getMode(), L.getMode());
sb.setMode(mode);
}
}
if (modeRefLoopFilterDeltaEnabled > 0) {
int level = mb.getFilterLevel();
level = level + this.modeLoopFilterDeltas[0];
level = (level < 0) ? 0 : Math.min(level, 63);
mb.setFilterLevel(level);
}
}
else {
int BMode;
switch (y_mode) {
case Globals.V_PRED:
BMode = Globals.B_VE_PRED;
break;
case Globals.H_PRED:
BMode = Globals.B_HE_PRED;
break;
case Globals.TM_PRED:
BMode = Globals.B_TM_PRED;
break;
case Globals.DC_PRED:
default:
BMode = Globals.B_DC_PRED;
break;
}
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
SubBlock sb = mb.getYSubBlock(x, y);
sb.setMode(BMode);
}
}
}
int mode = readUvMode(bc);
mb.setUvMode(mode);
}
}
}
private int readPartitionSize(long l) throws IOException {
frame.seek(l);
return frame.readUnsignedByte() + (frame.readUnsignedByte() << 8) + (frame.readUnsignedByte() << 16);
}
private int readSubBlockMode(BoolDecoder bc, int A, int L) throws IOException {
return bc.readTree(Globals.vp8SubBlockModeTree, Globals.vp8KeyFrameSubBlockModeProb[A][L], 0);
}
private int readUvMode(BoolDecoder bc) throws IOException {
return bc.readTree(Globals.vp8UVModeTree, Globals.vp8KeyFrameUVModeProb, 0);
}
private int readYMode(BoolDecoder bc) throws IOException {
return bc.readTree(Globals.vp8KeyFrameYModeTree, Globals.vp8KeyFrameYModeProb, 0);
}
private void setupTokenDecoder(BoolDecoder bc, int first_partition_length_in_bytes, long offset) throws IOException {
long partitionSize;
long partitionsStart = offset + first_partition_length_in_bytes;
long partition = partitionsStart;
multiTokenPartition = bc.readLiteral(2);
int num_part = 1 << multiTokenPartition;
if (num_part > 1) {
partition += 3L * (num_part - 1);
}
for (int i = 0; i < num_part; i++) {
// Calculate the length of this partition. The last partition size is implicit.
if (i < num_part - 1) {
partitionSize = readPartitionSize(partitionsStart + (i * 3L));
bc.seek();
}
else {
partitionSize = frame.length() - partition;
}
tokenBoolDecoders.add(new BoolDecoder(frame, partition));
partition += partitionSize;
}
tokenBoolDecoder = tokenBoolDecoders.get(0);
}
private final byte[] yuv = new byte[3];
private final byte[] rgb = new byte[4]; // Allow decoding into RGBA, leaving the alpha out.
private void copyBlock(final MacroBlock macroBlock, final WritableRaster byteRGBRaster,
final Rectangle region, final int xSubsampling, final int ySubsampling) {
// We might be copying into a smaller raster
int yStart = macroBlock.getY() * 16 - region.y;
int yEnd = Math.min(16, byteRGBRaster.getHeight() * ySubsampling - yStart);
int xStart = macroBlock.getX() * 16 - region.x;
int xEnd = Math.min(16, byteRGBRaster.getWidth() * xSubsampling - xStart);
for (int y = 0; y < yEnd; y += ySubsampling) {
int dstY = (yStart + y) / ySubsampling;
if (dstY < 0) {
continue;
}
for (int x = 0; x < xEnd; x += xSubsampling) {
int dstX = (xStart + x) / xSubsampling;
if (dstX < 0) {
continue;
}
yuv[0] = (byte) macroBlock.getSubBlock(SubBlock.Plane.Y1, x / 4, y / 4).getDest()[x % 4][y % 4];
yuv[1] = (byte) macroBlock.getSubBlock(SubBlock.Plane.U, (x / 2) / 4, (y / 2) / 4).getDest()[(x / 2) % 4][(y / 2) % 4];
yuv[2] = (byte) macroBlock.getSubBlock(SubBlock.Plane.V, (x / 2) / 4, (y / 2) / 4).getDest()[(x / 2) % 4][(y / 2) % 4];
// TODO: Consider doing YCbCr -> RGB in reader instead, or pass a flag to allow readRaster reading direct YUV/YCbCr values
convertRec601YCbCr2RGB(yuv, rgb, 0);
byteRGBRaster.setDataElements(dstX, dstY, rgb);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy