From 326b98d5e500caf5874bdfa370c192d2c7756559 Mon Sep 17 00:00:00 2001 From: Simon Kammermeier Date: Tue, 30 Aug 2022 15:36:57 +0200 Subject: [PATCH] Implement applying of the inverse transforms --- .../plugins/webp/lossless/VP8LDecoder.java | 4 + .../transform/ColorIndexingTransform.java | 25 ++ .../lossless/transform/ColorTransform.java | 76 +++--- .../transform/PredictorTransform.java | 232 +++++++++++++++--- .../transform/SubtractGreenTransform.java | 13 + 5 files changed, 281 insertions(+), 69 deletions(-) diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java index 3b4621e8..19e4cf18 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/VP8LDecoder.java @@ -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 { diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorIndexingTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorIndexingTransform.java index 32d16009..5d75e947 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorIndexingTransform.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorIndexingTransform.java @@ -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); + + } + } } } diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorTransform.java index 2623e946..9e90e2b6 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorTransform.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/ColorTransform.java @@ -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); } } + } diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/PredictorTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/PredictorTransform.java index 711e1cf3..e5d7fe70 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/PredictorTransform.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/PredictorTransform.java @@ -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]; } } diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/SubtractGreenTransform.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/SubtractGreenTransform.java index 157f723b..0de60314 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/SubtractGreenTransform.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/lossless/transform/SubtractGreenTransform.java @@ -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); + } + } } }