com.twelvemonkeys.imageio.plugins.webp.vp8.MacroBlock 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).
/*
* 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 final boolean keepDebugInfo;
private int segmentId;
private int skipCoeff;
private boolean skipInnerLoopFilter;
final SubBlock[][] uSubBlocks;
private int uVFilterLevel;
private int uvMode;
final SubBlock[][] vSubBlocks;
private final int x, y;
final SubBlock y2SubBlock;
private int yMode;
final 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;
SubBlock left;
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 {
decodeMacroBlockTokens(frame, mb.getYMode() != Globals.B_PRED);
}
}
private void decodeMacroBlockTokens(VP8Frame frame, boolean withY2) throws IOException {
if (withY2) {
skipInnerLoopFilter = decodePlaneTokens(frame, 1, SubBlock.Plane.Y2, false);
}
skipInnerLoopFilter |= decodePlaneTokens(frame, 4, SubBlock.Plane.Y1, withY2);
skipInnerLoopFilter |= decodePlaneTokens(frame, 2, SubBlock.Plane.U, false);
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;
int expected_vdc;
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;
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;
}
}