-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add static Grayscale and Inverted RGB Filter.
PiperOrigin-RevId: 471017440
- Loading branch information
1 parent
cb60f50
commit 9f67ce4
Showing
8 changed files
with
252 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
...edia/libraries/effect/src/androidTest/java/androidx/media3/effect/RgbFilterPixelTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* Copyright 2022 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package androidx.media3.effect; | ||
|
||
import static androidx.media3.effect.BitmapTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE; | ||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext; | ||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; | ||
import static com.google.common.truth.Truth.assertThat; | ||
|
||
import android.content.Context; | ||
import android.graphics.Bitmap; | ||
import android.opengl.EGLContext; | ||
import android.opengl.EGLDisplay; | ||
import android.opengl.EGLSurface; | ||
import android.util.Pair; | ||
import androidx.media3.common.FrameProcessingException; | ||
import androidx.test.ext.junit.runners.AndroidJUnit4; | ||
import com.google.android.exoplayer2.util.GlUtil; | ||
import java.io.IOException; | ||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
/** | ||
* Pixel tests for {@link RgbFilter}. | ||
* | ||
* <p>Expected images are taken from an emulator, so tests on different emulators or physical | ||
* devices may fail. To test on other devices, please increase the {@link | ||
* BitmapTestUtil#MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE} and/or inspect the saved output bitmaps | ||
* as recommended in {@link GlEffectsFrameProcessorPixelTest}. | ||
*/ | ||
@RunWith(AndroidJUnit4.class) | ||
public final class RgbFilterPixelTest { | ||
public static final String ORIGINAL_PNG_ASSET_PATH = | ||
"media/bitmap/sample_mp4_first_frame/original.png"; | ||
public static final String GRAYSCALE_PNG_ASSET_PATH = | ||
"media/bitmap/sample_mp4_first_frame/grayscale.png"; | ||
public static final String INVERT_PNG_ASSET_PATH = | ||
"media/bitmap/sample_mp4_first_frame/invert.png"; | ||
|
||
private final Context context = getApplicationContext(); | ||
|
||
private @MonotonicNonNull EGLDisplay eglDisplay; | ||
private @MonotonicNonNull EGLContext eglContext; | ||
private @MonotonicNonNull SingleFrameGlTextureProcessor rgbMatrixProcessor; | ||
private @MonotonicNonNull EGLSurface placeholderEglSurface; | ||
private int inputTexId; | ||
private int outputTexId; | ||
private int inputWidth; | ||
private int inputHeight; | ||
|
||
@Before | ||
public void createGlObjects() throws IOException, GlUtil.GlException { | ||
eglDisplay = GlUtil.createEglDisplay(); | ||
eglContext = GlUtil.createEglContext(eglDisplay); | ||
Bitmap inputBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH); | ||
inputWidth = inputBitmap.getWidth(); | ||
inputHeight = inputBitmap.getHeight(); | ||
placeholderEglSurface = GlUtil.createPlaceholderEglSurface(eglDisplay); | ||
GlUtil.focusEglSurface(eglDisplay, eglContext, placeholderEglSurface, inputWidth, inputHeight); | ||
inputTexId = BitmapTestUtil.createGlTextureFromBitmap(inputBitmap); | ||
|
||
outputTexId = | ||
GlUtil.createTexture(inputWidth, inputHeight, /* useHighPrecisionColorComponents= */ false); | ||
int frameBuffer = GlUtil.createFboForTexture(outputTexId); | ||
GlUtil.focusFramebuffer( | ||
checkNotNull(eglDisplay), | ||
checkNotNull(eglContext), | ||
checkNotNull(placeholderEglSurface), | ||
frameBuffer, | ||
inputWidth, | ||
inputHeight); | ||
} | ||
|
||
@After | ||
public void release() throws GlUtil.GlException, FrameProcessingException { | ||
if (rgbMatrixProcessor != null) { | ||
rgbMatrixProcessor.release(); | ||
} | ||
GlUtil.destroyEglContext(eglDisplay, eglContext); | ||
} | ||
|
||
@Test | ||
public void drawFrame_grayscale_producesGrayscaleImage() throws Exception { | ||
String testId = "drawFrame_grayscale"; | ||
RgbMatrix grayscaleMatrix = RgbFilter.createGrayscaleFilter(); | ||
rgbMatrixProcessor = new RgbMatrixProcessor(context, grayscaleMatrix, /* useHdr= */ false); | ||
Pair<Integer, Integer> outputSize = rgbMatrixProcessor.configure(inputWidth, inputHeight); | ||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(GRAYSCALE_PNG_ASSET_PATH); | ||
|
||
rgbMatrixProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0); | ||
Bitmap actualBitmap = | ||
BitmapTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer( | ||
outputSize.first, outputSize.second); | ||
|
||
BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory( | ||
testId, /* bitmapLabel= */ "actual", actualBitmap); | ||
float averagePixelAbsoluteDifference = | ||
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888( | ||
expectedBitmap, actualBitmap, testId); | ||
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); | ||
} | ||
|
||
@Test | ||
public void drawFrame_inverted_producesInvertedFrame() throws Exception { | ||
String testId = "drawFrame_inverted"; | ||
RgbMatrix invertedMatrix = RgbFilter.createInvertedFilter(); | ||
rgbMatrixProcessor = new RgbMatrixProcessor(context, invertedMatrix, /* useHdr= */ false); | ||
Pair<Integer, Integer> outputSize = rgbMatrixProcessor.configure(inputWidth, inputHeight); | ||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(INVERT_PNG_ASSET_PATH); | ||
|
||
rgbMatrixProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0); | ||
Bitmap actualBitmap = | ||
BitmapTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer( | ||
outputSize.first, outputSize.second); | ||
|
||
BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory( | ||
testId, /* bitmapLabel= */ "actual", actualBitmap); | ||
float averagePixelAbsoluteDifference = | ||
BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888( | ||
expectedBitmap, actualBitmap, testId); | ||
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
...c/android_libs/media/libraries/effect/src/main/java/androidx/media3/effect/RgbFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright 2022 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package androidx.media3.effect; | ||
|
||
import static com.google.android.exoplayer2.util.Assertions.checkState; | ||
|
||
import android.content.Context; | ||
import androidx.media3.common.FrameProcessingException; | ||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; | ||
|
||
/** Provides common color filters. */ | ||
public class RgbFilter implements RgbMatrix { | ||
private static final int COLOR_FILTER_GRAYSCALE_INDEX = 1; | ||
private static final int COLOR_FILTER_INVERTED_INDEX = 2; | ||
|
||
// Grayscale transformation matrix using the BT.709 luminance coefficients from | ||
// https://en.wikipedia.org/wiki/Grayscale#Converting_colour_to_grayscale | ||
private static final float[] FILTER_MATRIX_GRAYSCALE_SDR = { | ||
0.2126f, 0.2126f, 0.2126f, 0, 0.7152f, 0.7152f, 0.7152f, 0, 0.0722f, 0.0722f, 0.0722f, 0, 0, 0, | ||
0, 1 | ||
}; | ||
// Grayscale transformation using the BT.2020 primary colors from | ||
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf | ||
// TODO(b/241240659): Add HDR tests once infrastructure supports it. | ||
private static final float[] FILTER_MATRIX_GRAYSCALE_HDR = { | ||
0.2627f, 0.2627f, 0.2627f, 0, 0.6780f, 0.6780f, 0.6780f, 0, 0.0593f, 0.0593f, 0.0593f, 0, 0, 0, | ||
0, 1 | ||
}; | ||
// Inverted filter uses the transformation R' = -R + 1 = 1 - R. | ||
private static final float[] FILTER_MATRIX_INVERTED = { | ||
-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 1, 1, 1, 1 | ||
}; | ||
|
||
private final int colorFilter; | ||
/** | ||
* Ensures that the usage of HDR is consistent. {@code null} indicates that HDR has not yet been | ||
* set. | ||
*/ | ||
private @MonotonicNonNull Boolean useHdr; | ||
|
||
/** Creates a new grayscale {@code RgbFilter} instance. */ | ||
public static RgbFilter createGrayscaleFilter() { | ||
return new RgbFilter(COLOR_FILTER_GRAYSCALE_INDEX); | ||
} | ||
|
||
/** Creates a new inverted {@code RgbFilter} instance. */ | ||
public static RgbFilter createInvertedFilter() { | ||
return new RgbFilter(COLOR_FILTER_INVERTED_INDEX); | ||
} | ||
|
||
private RgbFilter(int colorFilter) { | ||
this.colorFilter = colorFilter; | ||
} | ||
|
||
private void checkForConsistentHdrSetting(boolean useHdr) { | ||
if (this.useHdr == null) { | ||
this.useHdr = useHdr; | ||
} else { | ||
checkState(this.useHdr == useHdr, "Changing HDR setting is not supported."); | ||
} | ||
} | ||
|
||
@Override | ||
public float[] getMatrix(long presentationTimeUs, boolean useHdr) { | ||
checkForConsistentHdrSetting(useHdr); | ||
switch (colorFilter) { | ||
case COLOR_FILTER_GRAYSCALE_INDEX: | ||
return useHdr ? FILTER_MATRIX_GRAYSCALE_HDR : FILTER_MATRIX_GRAYSCALE_SDR; | ||
case COLOR_FILTER_INVERTED_INDEX: | ||
return FILTER_MATRIX_INVERTED; | ||
default: | ||
// Should never happen. | ||
throw new IllegalStateException("Invalid color filter " + colorFilter); | ||
} | ||
} | ||
|
||
@Override | ||
public RgbMatrixProcessor toGlTextureProcessor(Context context, boolean useHdr) | ||
throws FrameProcessingException { | ||
checkForConsistentHdrSetting(useHdr); | ||
return new RgbMatrixProcessor(context, /* rgbMatrix= */ this, useHdr); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+527 KB
testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/invert.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.