diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MssimCalculator.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MssimCalculator.java index e7af68e689..c3d4371fdf 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MssimCalculator.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/MssimCalculator.java @@ -44,23 +44,47 @@ import static java.lang.Math.pow; private MssimCalculator() {} + /** + * Calculates the Mean Structural Similarity (MSSIM) between two images with window skipping. + * + * @see #calculate(byte[], byte[], int, int, boolean). + */ + public static double calculate( + byte[] referenceBuffer, byte[] distortedBuffer, int width, int height) { + return calculate( + referenceBuffer, distortedBuffer, width, height, /* enableWindowSkipping= */ true); + } + /** * Calculates the Mean Structural Similarity (MSSIM) between two images. * + *
The images are split into a grid of windows. For each window, the structural similarity + * (SSIM) is calculated. The MSSIM returned from this method is the mean of these SSIM values. If + * window skipping is enabled, only every other row and column are considered, thereby only one in + * four windows are evaluated. + * * @param referenceBuffer The luma channel (Y) buffer of the reference image. * @param distortedBuffer The luma channel (Y) buffer of the distorted image. * @param width The image width in pixels. * @param height The image height in pixels. + * @param enableWindowSkipping Whether to skip every other row and column when evaluating windows + * for SSIM calculation. * @return The MSSIM score between the input images. */ public static double calculate( - byte[] referenceBuffer, byte[] distortedBuffer, int width, int height) { + byte[] referenceBuffer, + byte[] distortedBuffer, + int width, + int height, + boolean enableWindowSkipping) { double totalSsim = 0; int windowsCount = 0; - for (int currentWindowY = 0; currentWindowY < height; currentWindowY += WINDOW_SIZE) { + int dimensionIncrement = WINDOW_SIZE * (enableWindowSkipping ? 2 : 1); + + for (int currentWindowY = 0; currentWindowY < height; currentWindowY += dimensionIncrement) { int windowHeight = computeWindowSize(currentWindowY, height); - for (int currentWindowX = 0; currentWindowX < width; currentWindowX += WINDOW_SIZE) { + for (int currentWindowX = 0; currentWindowX < width; currentWindowX += dimensionIncrement) { windowsCount++; int windowWidth = computeWindowSize(currentWindowX, width); int bufferIndexOffset = diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/MssimCalculatorTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/MssimCalculatorTest.java index 8393c0c4e2..623aa7d192 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/MssimCalculatorTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/MssimCalculatorTest.java @@ -62,6 +62,34 @@ public class MssimCalculatorTest { .isEqualTo(63); } + @Test + public void calculateSsim_withWindowSkipping_similarToWithout() throws Exception { + Bitmap referenceBitmap = readBitmap("media/bitmap/sample_mp4_first_frame/original.png"); + Bitmap distortedBitmap = + readBitmap("media/bitmap/sample_mp4_first_frame/increase_brightness.png"); + byte[] referenceLuminosity = bitmapToLuminosityArray(referenceBitmap); + byte[] distortedLuminosity = bitmapToLuminosityArray(distortedBitmap); + + assertThat( + (int) + (MssimCalculator.calculate( + referenceLuminosity, + distortedLuminosity, + referenceBitmap.getWidth(), + referenceBitmap.getHeight(), + /* enableWindowSkipping= */ false) + * 100)) + .isEqualTo( + (int) + (MssimCalculator.calculate( + referenceLuminosity, + distortedLuminosity, + referenceBitmap.getWidth(), + referenceBitmap.getHeight(), + /* enableWindowSkipping= */ true) + * 100)); + } + private static Bitmap readBitmap(String assetString) throws IOException { try (InputStream inputStream = getApplicationContext().getAssets().open(assetString)) { return BitmapFactory.decodeStream(inputStream);