Commit 41e1b11c by samrobinson Committed by Ian Baker

Allow the difference Bitmap to be saved to device cache.

When investigating how 'bad' a failure is, it's useful to see the diff
between input and output bitmaps.

PiperOrigin-RevId: 430917732
parent 6ed72dcd
...@@ -26,6 +26,7 @@ import android.graphics.BitmapFactory; ...@@ -26,6 +26,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.media.Image; import android.media.Image;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
...@@ -92,12 +93,20 @@ public class BitmapTestUtil { ...@@ -92,12 +93,20 @@ public class BitmapTestUtil {
} }
/** /**
* Returns the sum of the absolute differences between the expected and actual bitmaps, calculated * Returns the average difference between the expected and actual bitmaps, calculated using the
* using the maximum difference across all color channels for each pixel, then divided by the * maximum difference across all color channels for each pixel, then divided by the total number
* total number of pixels in the image. The bitmap resolutions must match and they must use * of pixels in the image. The bitmap resolutions must match and they must use configuration
* configuration {@link Bitmap.Config#ARGB_8888}. * {@link Bitmap.Config#ARGB_8888}.
*
* @param expected The expected {@link Bitmap}.
* @param actual The actual {@link Bitmap} produced by the test.
* @param testId The name of the test that produced the {@link Bitmap}, or {@code null} if the
* differences bitmap should not be saved to cache.
* @return The average of the maximum absolute pixel-wise differences between the expected and
* actual bitmaps.
*/ */
public static float getAveragePixelAbsoluteDifferenceArgb8888(Bitmap expected, Bitmap actual) { public static float getAveragePixelAbsoluteDifferenceArgb8888(
Bitmap expected, Bitmap actual, @Nullable String testId) {
int width = actual.getWidth(); int width = actual.getWidth();
int height = actual.getHeight(); int height = actual.getHeight();
assertThat(width).isEqualTo(expected.getWidth()); assertThat(width).isEqualTo(expected.getWidth());
...@@ -107,7 +116,7 @@ public class BitmapTestUtil { ...@@ -107,7 +116,7 @@ public class BitmapTestUtil {
// Debug-only image diff without alpha. To use, set a breakpoint right before the method return // Debug-only image diff without alpha. To use, set a breakpoint right before the method return
// to view the difference between the expected and actual bitmaps. A passing test should show // to view the difference between the expected and actual bitmaps. A passing test should show
// an image that is completely black (color == 0). // an image that is completely black (color == 0).
Bitmap debugDiff = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Bitmap differencesBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
int actualColor = actual.getPixel(x, y); int actualColor = actual.getPixel(x, y);
...@@ -117,7 +126,7 @@ public class BitmapTestUtil { ...@@ -117,7 +126,7 @@ public class BitmapTestUtil {
int redDifference = abs(Color.red(actualColor) - Color.red(expectedColor)); int redDifference = abs(Color.red(actualColor) - Color.red(expectedColor));
int blueDifference = abs(Color.blue(actualColor) - Color.blue(expectedColor)); int blueDifference = abs(Color.blue(actualColor) - Color.blue(expectedColor));
int greenDifference = abs(Color.green(actualColor) - Color.green(expectedColor)); int greenDifference = abs(Color.green(actualColor) - Color.green(expectedColor));
debugDiff.setPixel(x, y, Color.rgb(redDifference, blueDifference, greenDifference)); differencesBitmap.setPixel(x, y, Color.rgb(redDifference, blueDifference, greenDifference));
int maximumAbsoluteDifference = 0; int maximumAbsoluteDifference = 0;
maximumAbsoluteDifference = max(maximumAbsoluteDifference, alphaDifference); maximumAbsoluteDifference = max(maximumAbsoluteDifference, alphaDifference);
...@@ -128,23 +137,34 @@ public class BitmapTestUtil { ...@@ -128,23 +137,34 @@ public class BitmapTestUtil {
sumMaximumAbsoluteDifferences += maximumAbsoluteDifference; sumMaximumAbsoluteDifferences += maximumAbsoluteDifference;
} }
} }
if (testId != null) {
try {
saveTestBitmapToCacheDirectory(
testId, "diff", differencesBitmap, /* throwOnFailure= */ false);
} catch (IOException impossible) {
throw new IllegalStateException(impossible);
}
}
return (float) sumMaximumAbsoluteDifferences / (width * height); return (float) sumMaximumAbsoluteDifferences / (width * height);
} }
/** /**
* Saves the {@link Bitmap} to the {@link Context#getCacheDir() cache directory} as a PNG. * Saves the {@link Bitmap} to the {@link Context#getCacheDir() cache directory} as a PNG.
* *
* <p>File name will be {@code <testId>_output.png}. If {@code throwOnFailure} is {@code false}, * <p>File name will be {@code <testId>_<bitmapLabel>.png}. If {@code throwOnFailure} is {@code
* any {@link IOException} will be caught and logged. * false}, any {@link IOException} will be caught and logged.
* *
* @param testId Name of the test that produced the {@link Bitmap}. * @param testId Name of the test that produced the {@link Bitmap}.
* @param bitmapLabel Label to identify the bitmap.
* @param bitmap The {@link Bitmap} to save. * @param bitmap The {@link Bitmap} to save.
* @param throwOnFailure Whether to throw an exception if the bitmap can't be saved. * @param throwOnFailure Whether to throw an exception if the bitmap can't be saved.
* @throws IOException If the bitmap can't be saved and {@code throwOnFailure} is {@code true}. * @throws IOException If the bitmap can't be saved and {@code throwOnFailure} is {@code true}.
*/ */
public static void saveTestBitmapToCacheDirectory( public static void saveTestBitmapToCacheDirectory(
String testId, Bitmap bitmap, boolean throwOnFailure) throws IOException { String testId, String bitmapLabel, Bitmap bitmap, boolean throwOnFailure) throws IOException {
File file = new File(getApplicationContext().getExternalCacheDir(), testId + "_output.png"); File file =
new File(
getApplicationContext().getExternalCacheDir(), testId + "_" + bitmapLabel + ".png");
try (FileOutputStream outputStream = new FileOutputStream(file)) { try (FileOutputStream outputStream = new FileOutputStream(file)) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, outputStream); bitmap.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, outputStream);
} catch (IOException e) { } catch (IOException e) {
......
...@@ -87,25 +87,28 @@ public final class FrameEditorDataProcessingTest { ...@@ -87,25 +87,28 @@ public final class FrameEditorDataProcessingTest {
@Test @Test
public void processData_noEdits_producesExpectedOutput() throws Exception { public void processData_noEdits_producesExpectedOutput() throws Exception {
final String testId = "processData_noEdits";
Matrix identityMatrix = new Matrix(); Matrix identityMatrix = new Matrix();
setUpAndPrepareFirstFrame(identityMatrix); setUpAndPrepareFirstFrame(identityMatrix);
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(FIRST_FRAME_PNG_ASSET_STRING); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(FIRST_FRAME_PNG_ASSET_STRING);
checkNotNull(frameEditor).processData(); checkNotNull(frameEditor).processData();
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage(); Image editorOutputImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
Bitmap editedBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editedImage); Bitmap actualBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editorOutputImage);
editedImage.close(); editorOutputImage.close();
// TODO(b/207848601): switch to using proper tooling for testing against golden data. // TODO(b/207848601): switch to using proper tooling for testing against golden data.
float averagePixelAbsoluteDifference = float averagePixelAbsoluteDifference =
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, editedBitmap); BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
expectedBitmap, actualBitmap, testId);
BitmapTestUtil.saveTestBitmapToCacheDirectory( BitmapTestUtil.saveTestBitmapToCacheDirectory(
"processData_noEdits", editedBitmap, /* throwOnFailure= */ false); testId, /* bitmapLabel= */ "actual", actualBitmap, /* throwOnFailure= */ false);
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
@Test @Test
public void processData_translateRight_producesExpectedOutput() throws Exception { public void processData_translateRight_producesExpectedOutput() throws Exception {
final String testId = "processData_translateRight";
Matrix translateRightMatrix = new Matrix(); Matrix translateRightMatrix = new Matrix();
translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0); translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0);
setUpAndPrepareFirstFrame(translateRightMatrix); setUpAndPrepareFirstFrame(translateRightMatrix);
...@@ -113,20 +116,22 @@ public final class FrameEditorDataProcessingTest { ...@@ -113,20 +116,22 @@ public final class FrameEditorDataProcessingTest {
BitmapTestUtil.readBitmap(TRANSLATE_RIGHT_EXPECTED_OUTPUT_PNG_ASSET_STRING); BitmapTestUtil.readBitmap(TRANSLATE_RIGHT_EXPECTED_OUTPUT_PNG_ASSET_STRING);
checkNotNull(frameEditor).processData(); checkNotNull(frameEditor).processData();
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage(); Image editorOutputImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
Bitmap editedBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editedImage); Bitmap actualBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editorOutputImage);
editedImage.close(); editorOutputImage.close();
// TODO(b/207848601): switch to using proper tooling for testing against golden data. // TODO(b/207848601): switch to using proper tooling for testing against golden data.
float averagePixelAbsoluteDifference = float averagePixelAbsoluteDifference =
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, editedBitmap); BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
expectedBitmap, actualBitmap, testId);
BitmapTestUtil.saveTestBitmapToCacheDirectory( BitmapTestUtil.saveTestBitmapToCacheDirectory(
"processData_translateRight", editedBitmap, /* throwOnFailure= */ false); testId, /* bitmapLabel= */ "actual", actualBitmap, /* throwOnFailure= */ false);
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
@Test @Test
public void processData_scaleNarrow_producesExpectedOutput() throws Exception { public void processData_scaleNarrow_producesExpectedOutput() throws Exception {
final String testId = "processData_scaleNarrow";
Matrix scaleNarrowMatrix = new Matrix(); Matrix scaleNarrowMatrix = new Matrix();
scaleNarrowMatrix.postScale(.5f, 1.2f); scaleNarrowMatrix.postScale(.5f, 1.2f);
setUpAndPrepareFirstFrame(scaleNarrowMatrix); setUpAndPrepareFirstFrame(scaleNarrowMatrix);
...@@ -134,20 +139,22 @@ public final class FrameEditorDataProcessingTest { ...@@ -134,20 +139,22 @@ public final class FrameEditorDataProcessingTest {
BitmapTestUtil.readBitmap(SCALE_NARROW_EXPECTED_OUTPUT_PNG_ASSET_STRING); BitmapTestUtil.readBitmap(SCALE_NARROW_EXPECTED_OUTPUT_PNG_ASSET_STRING);
checkNotNull(frameEditor).processData(); checkNotNull(frameEditor).processData();
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage(); Image editorOutputImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
Bitmap editedBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editedImage); Bitmap actualBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editorOutputImage);
editedImage.close(); editorOutputImage.close();
// TODO(b/207848601): switch to using proper tooling for testing against golden data. // TODO(b/207848601): switch to using proper tooling for testing against golden data.
float averagePixelAbsoluteDifference = float averagePixelAbsoluteDifference =
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, editedBitmap); BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
expectedBitmap, actualBitmap, testId);
BitmapTestUtil.saveTestBitmapToCacheDirectory( BitmapTestUtil.saveTestBitmapToCacheDirectory(
"processData_scaleNarrow", editedBitmap, /* throwOnFailure= */ false); testId, /* bitmapLabel= */ "actual", actualBitmap, /* throwOnFailure= */ false);
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
@Test @Test
public void processData_rotate90_producesExpectedOutput() throws Exception { public void processData_rotate90_producesExpectedOutput() throws Exception {
final String testId = "processData_rotate90";
// TODO(b/213190310): After creating a Presentation class, move VideoSamplePipeline // TODO(b/213190310): After creating a Presentation class, move VideoSamplePipeline
// resolution-based adjustments (ex. in cl/419619743) to that Presentation class, so we can // resolution-based adjustments (ex. in cl/419619743) to that Presentation class, so we can
// test that rotation doesn't distort the image. // test that rotation doesn't distort the image.
...@@ -157,15 +164,16 @@ public final class FrameEditorDataProcessingTest { ...@@ -157,15 +164,16 @@ public final class FrameEditorDataProcessingTest {
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE_90_EXPECTED_OUTPUT_PNG_ASSET_STRING); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE_90_EXPECTED_OUTPUT_PNG_ASSET_STRING);
checkNotNull(frameEditor).processData(); checkNotNull(frameEditor).processData();
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage(); Image editorOutputImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
Bitmap editedBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editedImage); Bitmap actualBitmap = BitmapTestUtil.createArgb8888BitmapFromRgba8888Image(editorOutputImage);
editedImage.close(); editorOutputImage.close();
// TODO(b/207848601): switch to using proper tooling for testing against golden data. // TODO(b/207848601): switch to using proper tooling for testing against golden data.
float averagePixelAbsoluteDifference = float averagePixelAbsoluteDifference =
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, editedBitmap); BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
expectedBitmap, actualBitmap, testId);
BitmapTestUtil.saveTestBitmapToCacheDirectory( BitmapTestUtil.saveTestBitmapToCacheDirectory(
"processData_rotate90", editedBitmap, /* throwOnFailure= */ false); testId, /* bitmapLabel= */ "actual", actualBitmap, /* throwOnFailure= */ false);
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment