Implement applying of the inverse transforms

This commit is contained in:
Simon Kammermeier 2022-08-30 15:36:57 +02:00
parent fafa58b718
commit 326b98d5e5
5 changed files with 281 additions and 69 deletions

View File

@ -120,6 +120,10 @@ public final class VP8LDecoder {
// Use the Huffman trees to decode the LZ77 encoded data.
decodeImage(writableChild, huffmanInfo, colorCache);
for (Transform transform : transforms) {
transform.applyInverse(raster);
}
}
private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException {

View File

@ -14,5 +14,30 @@ public class ColorIndexingTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) {
//Reversed so no used elements are overridden (in case of packing)
for (int x = width - 1; x >= 0; x--) {
int componentSize = 8 >> bits;
int packed = 1 << bits;
int xC = x / packed;
int componentOffset = componentSize * (x % packed);
int sample = raster.getSample(xC, y, 1);
int index = sample >> componentOffset & ((1 << componentSize) - 1);
//Arraycopy for 4 elements might not be beneficial
System.arraycopy(colorTable, index * 4, rgba, 0, 4);
raster.setDataElements(x, y, rgba);
}
}
}
}

View File

@ -13,6 +13,24 @@ public class ColorTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data.getDataElements(x >> bits, y >> bits, rgba);
ColorTransformElement trans = new ColorTransformElement(rgba);
raster.getDataElements(x, y, rgba);
trans.inverseTransform(rgba);
raster.setDataElements(x, y, rgba);
}
}
}
// NOTE: For encoding!
@ -43,47 +61,33 @@ public class ColorTransform implements Transform {
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;
private static final class ColorTransformElement {
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);
}
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;
ColorTransformElement(final byte[] rgba) {
this.green_to_red = rgba[2];
this.green_to_blue = rgba[1];
this.red_to_blue = rgba[0];
}
private void inverseTransform(final byte[] rgb) {
// Applying inverse transform is just adding (!, different from specification) the
// color transform deltas 3
// Transformed values of red and blue components
int tmp_red = rgb[0];
int tmp_blue = rgb[2];
tmp_red += colorTransformDelta((byte) this.green_to_red, rgb[1]);
tmp_blue += colorTransformDelta((byte) this.green_to_blue, rgb[1]);
tmp_blue += colorTransformDelta((byte) this.red_to_blue, (byte) tmp_red); // Spec has red & 0xff
rgb[0] = (byte) (tmp_red & 0xff);
rgb[2] = (byte) (tmp_blue & 0xff);
}
}
}

View File

@ -15,45 +15,190 @@ public class PredictorTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
//Handle top and left border separately
//(0,0) Black (0x000000ff) predict
raster.getDataElements(0, 0, rgba);
rgba[3] += 0xff;
raster.setDataElements(0, 0, rgba);
byte[] predictor = new byte[4];
byte[] predictor2 = new byte[4];
byte[] predictor3 = new byte[4];
//(x,0) L predict
for (int x = 1; x < width; x++) {
raster.getDataElements(x, 0, rgba);
raster.getDataElements(x - 1, 0, predictor);
addPixels(rgba, predictor);
raster.setDataElements(x, 0, rgba);
}
//(0,y) T predict
for (int y = 1; y < height; y++) {
raster.getDataElements(0, y, rgba);
raster.getDataElements(0, y - 1, predictor);
addPixels(rgba, predictor);
raster.setDataElements(0, y, rgba);
}
for (int y = 1; y < height; y++) {
for (int x = 1; x < width; x++) {
int transformType = data.getSample(x >> bits, y >> bits, 1);
raster.getDataElements(x, y, rgba);
int lX = x - 1; //x for left
int tY = y - 1; //y for top
//top right is not (x+1, tY) if last pixel in line instead (0, y)
int trX = x == width - 1 ? 0 : x + 1;
int trY = x == width - 1 ? y : tY;
switch (transformType) {
case PredictorMode.BLACK:
rgba[3] += 0xff;
break;
case PredictorMode.L:
raster.getDataElements(lX, y, predictor);
addPixels(rgba, predictor);
break;
case PredictorMode.T:
raster.getDataElements(x, tY, predictor);
addPixels(rgba, predictor);
break;
case PredictorMode.TR:
raster.getDataElements(trX, trY, predictor);
addPixels(rgba, predictor);
break;
case PredictorMode.TL:
raster.getDataElements(lX, tY, predictor);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_L_TR_T:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(trX, trY, predictor2);
average2(predictor, predictor2);
raster.getDataElements(x, tY, predictor2);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_L_TL:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(lX, tY, predictor2);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_L_T:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(x, tY, predictor2);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_TL_T:
raster.getDataElements(lX, tY, predictor);
raster.getDataElements(x, tY, predictor2);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_T_TR:
raster.getDataElements(x, tY, predictor);
raster.getDataElements(trX, trY, predictor2);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.AVG_L_TL_T_TR:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(lX, tY, predictor2);
average2(predictor, predictor2);
raster.getDataElements(x, tY, predictor2);
raster.getDataElements(trX, trY, predictor3);
average2(predictor2, predictor3);
average2(predictor, predictor2);
addPixels(rgba, predictor);
break;
case PredictorMode.SELECT:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(x, tY, predictor2);
raster.getDataElements(lX, tY, predictor3);
addPixels(rgba, select(predictor, predictor2, predictor3));
break;
case PredictorMode.CLAMP_ADD_SUB_FULL:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(x, tY, predictor2);
raster.getDataElements(lX, tY, predictor3);
clampAddSubtractFull(predictor, predictor2, predictor3);
addPixels(rgba, predictor);
break;
case PredictorMode.CLAMP_ADD_SUB_HALF:
raster.getDataElements(lX, y, predictor);
raster.getDataElements(x, tY, predictor2);
average2(predictor, predictor2);
raster.getDataElements(lX, tY, predictor2);
clampAddSubtractHalf(predictor, predictor2);
addPixels(rgba, predictor);
break;
}
raster.setDataElements(x, y, rgba);
}
}
}
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.
private static byte[] select(final byte[] l, final byte[] t, final byte[] 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);
int pAlpha = addSubtractFull(l[3], t[3], tl[3]);
int pRed = addSubtractFull(l[0], t[0], tl[0]);
int pGreen = addSubtractFull(l[1], t[1], tl[1]);
int pBlue = addSubtractFull(l[2], t[2], tl[2]);
// 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));
int pL = manhattanDistance(l, pAlpha, pRed, pGreen, pBlue);
int pT = manhattanDistance(t, pAlpha, pRed, pGreen, pBlue);
// Return either left or top, the one closer to the prediction.
return pL < pT ? L : T;
return pL < pT ? l : t;
}
private static int average2(final int a, final int b) {
return (a + b) / 2;
private static int manhattanDistance(byte[] rgba, int pAlpha, int pRed, int pGreen, int pBlue) {
return abs(pAlpha - (rgba[3] & 0xff)) + abs(pRed - (rgba[0] & 0xff)) +
abs(pGreen - (rgba[1] & 0xff)) + abs(pBlue - (rgba[2] & 0xff));
}
private static void average2(final byte[] rgba1, final byte[] rgba2) {
rgba1[0] = (byte) (((rgba1[0] & 0xff) + (rgba2[0] & 0xff)) / 2);
rgba1[1] = (byte) (((rgba1[1] & 0xff) + (rgba2[1] & 0xff)) / 2);
rgba1[2] = (byte) (((rgba1[2] & 0xff) + (rgba2[2] & 0xff)) / 2);
rgba1[3] = (byte) (((rgba1[3] & 0xff) + (rgba2[3] & 0xff)) / 2);
}
// Clamp the input value between 0 and 255.
@ -61,12 +206,33 @@ public class PredictorTransform implements Transform {
return max(0, min(a, 255));
}
private static int clampAddSubtractFull(final int a, final int b, final int c) {
return clamp(a + b - c);
private static void clampAddSubtractFull(final byte[] a, final byte[] b, final byte[] c) {
a[0] = (byte) clamp(addSubtractFull(a[0], b[0], c[0]));
a[1] = (byte) clamp(addSubtractFull(a[1], b[1], c[1]));
a[2] = (byte) clamp(addSubtractFull(a[2], b[2], c[2]));
a[3] = (byte) clamp(addSubtractFull(a[3], b[3], c[3]));
}
private static int clampAddSubtractHalf(final int a, final int b) {
return clamp(a + (a - b) / 2);
private static void clampAddSubtractHalf(final byte[] a, final byte[] b) {
a[0] = (byte) clamp(addSubtractHalf(a[0], b[0]));
a[1] = (byte) clamp(addSubtractHalf(a[1], b[1]));
a[2] = (byte) clamp(addSubtractHalf(a[2], b[2]));
a[3] = (byte) clamp(addSubtractHalf(a[3], b[3]));
}
private static int addSubtractFull(byte a, byte b, byte c) {
return (a & 0xff) + (b & 0xff) - (c & 0xff);
}
private static int addSubtractHalf(byte a, byte b) {
return (a & 0xff) + ((a & 0xff) - (b & 0xff)) / 2;
}
private static void addPixels(byte[] rgba, byte[] predictor) {
rgba[0] += predictor[0];
rgba[1] += predictor[1];
rgba[2] += predictor[2];
rgba[3] += predictor[3];
}
}

View File

@ -12,5 +12,18 @@ public class SubtractGreenTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
raster.getDataElements(x, y, rgba);
addGreenToBlueAndRed(rgba);
raster.setDataElements(x, y, rgba);
}
}
}
}