Skip to content

Commit

Permalink
Add static Grayscale and Inverted RGB Filter.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 471017440
  • Loading branch information
leonwind authored and marcbaechinger committed Oct 19, 2022
1 parent cb60f50 commit 9f67ce4
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -400,17 +400,9 @@ public void processData_fullRotationIncreaseBrightnessAndCenterCrop_producesExpe
public void drawFrame_grayscaleAndIncreaseRedChannel_producesGrayscaleAndRedImage()
throws Exception {
String testId = "drawFrame_grayscale";
// Grayscale transformation matrix with the BT.709 standard from
// https://en.wikipedia.org/wiki/Grayscale#Converting_colour_to_grayscale
// TODO(b/241240659): Use static grayscale filter from RgbFilter once it exists.
float[] grayscaleMatrix = {
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
};
ImmutableList<Effect> grayscaleThenIncreaseRed =
ImmutableList.of(
(RgbMatrix) presentationTimeUs -> grayscaleMatrix,
new RgbAdjustment.Builder().setRedScale(3).build());
RgbFilter.createGrayscaleFilter(), new RgbAdjustment.Builder().setRedScale(3).build());
setUpAndPrepareFirstFrame(DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, grayscaleThenIncreaseRed);
Bitmap expectedBitmap =
BitmapTestUtil.readBitmap(GRAYSCALE_THEN_INCREASE_RED_CHANNEL_PNG_ASSET_PATH);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ public final class RgbAdjustmentPixelTest {
"media/bitmap/sample_mp4_first_frame/increase_red_channel.png";
public static final String INCREASE_BRIGHTNESS_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/increase_brightness.png";
public static final String GRAYSCALE_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/grayscale.png";

private final Context context = getApplicationContext();

Expand Down Expand Up @@ -102,12 +100,6 @@ public void release() throws GlUtil.GlException, FrameProcessingException {
GlUtil.destroyEglContext(eglDisplay, eglContext);
}

private static RgbMatrixProcessor createRgbMatrixProcessor(Context context, float[] rgbMatrix)
throws FrameProcessingException {
return ((RgbMatrix) presentationTimeUs -> rgbMatrix)
.toGlTextureProcessor(context, /* useHdr= */ false);
}

@Test
public void drawFrame_identityMatrix_leavesFrameUnchanged() throws Exception {
String testId = "drawFrame_identityMatrix";
Expand Down Expand Up @@ -218,33 +210,6 @@ public void drawFrame_increaseBrightness_increasesAllValues() throws Exception {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
}

@Test
// TODO(b/239430283): Move test to RgbFilterPixelTest once it exists.
public void drawFrame_grayscale_producesGrayscaleImage() throws Exception {
String testId = "drawFrame_grayscale";
// Grayscale transformation matrix with the BT.709 standard from
// https://en.wikipedia.org/wiki/Grayscale#Converting_colour_to_grayscale
float[] grayscaleMatrix = {
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
};
rgbMatrixProcessor = createRgbMatrixProcessor(/* context= */ context, grayscaleMatrix);
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_removeRedGreenAndBlueValuesInAChain_producesBlackImage() throws Exception {
String testId = "drawFrame_removeRedGreenBlueValuesInAChain";
Expand Down
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private RgbAdjustment(float[] rgbMatrix) {
}

@Override
public float[] getMatrix(long presentationTimeUs) {
public float[] getMatrix(long presentationTimeUs, boolean useHdr) {
return rgbMatrix;
}
}
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ public interface RgbMatrix extends GlEffect {
/**
* Returns the 4x4 RGB transformation {@linkplain android.opengl.Matrix matrix} to apply to the
* color values of each pixel in the frame with the given timestamp.
*
* @param presentationTimeUs The timestamp of the frame to apply the matrix on.
* @param useHdr If {@code true}, colors will be in linear RGB BT.2020. If {@code false}, colors
* will be in gamma RGB BT.709. Must be consistent with {@code useHdr} in {@link
* #toGlTextureProcessor(Context, boolean)}.
* @return The {@code RgbMatrix} to apply to the frame.
*/
float[] getMatrix(long presentationTimeUs);
float[] getMatrix(long presentationTimeUs, boolean useHdr);

@Override
default RgbMatrixProcessor toGlTextureProcessor(Context context, boolean useHdr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

private final GlProgram glProgram;
private final ImmutableList<RgbMatrix> rgbMatrices;
private final boolean useHdr;

// TODO(b/239757183): Merge RgbMatrixProcessor with MatrixTransformationProcessor.
/**
Expand Down Expand Up @@ -70,6 +71,7 @@ public RgbMatrixProcessor(Context context, ImmutableList<RgbMatrix> rgbMatrices,
throws FrameProcessingException {
super(useHdr);
this.rgbMatrices = rgbMatrices;
this.useHdr = useHdr;

try {
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
Expand All @@ -95,7 +97,7 @@ public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
}

private static float[] createCompositeRgbaMatrixArray(
ImmutableList<RgbMatrix> rgbMatrices, long presentationTimeUs) {
ImmutableList<RgbMatrix> rgbMatrices, boolean useHdr, long presentationTimeUs) {
float[] tempResultMatrix = new float[16];
float[] compositeRgbaMatrix = new float[16];
Matrix.setIdentityM(compositeRgbaMatrix, /* smOffset= */ 0);
Expand All @@ -104,7 +106,7 @@ private static float[] createCompositeRgbaMatrixArray(
Matrix.multiplyMM(
/* result= */ tempResultMatrix,
/* resultOffset= */ 0,
/* lhs= */ rgbMatrices.get(i).getMatrix(presentationTimeUs),
/* lhs= */ rgbMatrices.get(i).getMatrix(presentationTimeUs, useHdr),
/* lhsOffset= */ 0,
/* rhs= */ compositeRgbaMatrix,
/* rhsOffset= */ 0);
Expand All @@ -122,7 +124,8 @@ private static float[] createCompositeRgbaMatrixArray(
@Override
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
// TODO(b/239431666): Add caching for compacting Matrices.
float[] rgbMatrixArray = createCompositeRgbaMatrixArray(rgbMatrices, presentationTimeUs);
float[] rgbMatrixArray =
createCompositeRgbaMatrixArray(rgbMatrices, useHdr, presentationTimeUs);
try {
glProgram.use();
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9f67ce4

Please sign in to comment.