diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/RgbaMatrixPixelTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/RgbAdjustmentPixelTest.java similarity index 79% rename from libraries/effect/src/androidTest/java/androidx/media3/effect/RgbaMatrixPixelTest.java rename to libraries/effect/src/androidTest/java/androidx/media3/effect/RgbAdjustmentPixelTest.java index 1af29d6950..e39b3ddfd9 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/RgbaMatrixPixelTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/RgbAdjustmentPixelTest.java @@ -27,7 +27,6 @@ import android.graphics.Color; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; -import android.opengl.Matrix; import android.util.Pair; import androidx.media3.common.FrameProcessingException; import androidx.media3.common.util.GlUtil; @@ -48,19 +47,17 @@ import org.junit.runner.RunWith; * as recommended in {@link GlEffectsFrameProcessorPixelTest}. */ @RunWith(AndroidJUnit4.class) -public final class RgbaMatrixPixelTest { +public final class RgbAdjustmentPixelTest { public static final String ORIGINAL_PNG_ASSET_PATH = "media/bitmap/sample_mp4_first_frame/original.png"; public static final String ONLY_RED_CHANNEL_PNG_ASSET_PATH = "media/bitmap/sample_mp4_first_frame/only_red_channel.png"; + public static final String INCREASE_RED_CHANNEL_PNG_ASSET_PATH = + "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"; - public static final int COLOR_MATRIX_RED_INDEX = 0; - public static final int COLOR_MATRIX_GREEN_INDEX = 5; - public static final int COLOR_MATRIX_BLUE_INDEX = 10; - public static final int COLOR_MATRIX_ALPHA_INDEX = 15; private final Context context = getApplicationContext(); @@ -113,9 +110,8 @@ public final class RgbaMatrixPixelTest { @Test public void drawFrame_identityMatrix_leavesFrameUnchanged() throws Exception { String testId = "drawFrame_identityMatrix"; - float[] identityMatrix = new float[16]; - Matrix.setIdentityM(identityMatrix, /* smOffset= */ 0); - rgbaMatrixProcessor = createRgbaMatrixProcessor(context, identityMatrix); + RgbaMatrix identityMatrix = new RgbAdjustment.Builder().build(); + rgbaMatrixProcessor = new RgbaMatrixProcessor(context, identityMatrix, /* useHdr= */ false); Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH); @@ -135,12 +131,9 @@ public final class RgbaMatrixPixelTest { @Test public void drawFrame_removeColors_producesBlackFrame() throws Exception { String testId = "drawFrame_removeColors"; - float[] removeColorFilter = new float[16]; - Matrix.setIdentityM(removeColorFilter, /* smOffset= */ 0); - removeColorFilter[COLOR_MATRIX_RED_INDEX] = 0; - removeColorFilter[COLOR_MATRIX_GREEN_INDEX] = 0; - removeColorFilter[COLOR_MATRIX_BLUE_INDEX] = 0; - rgbaMatrixProcessor = createRgbaMatrixProcessor(context, removeColorFilter); + RgbaMatrix removeColorMatrix = + new RgbAdjustment.Builder().setRedScale(0).setGreenScale(0).setBlueScale(0).build(); + rgbaMatrixProcessor = new RgbaMatrixProcessor(context, removeColorMatrix, /* useHdr= */ false); Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); Bitmap expectedBitmap = BitmapTestUtil.createArgb8888BitmapWithSolidColor( @@ -162,11 +155,8 @@ public final class RgbaMatrixPixelTest { @Test public void drawFrame_redOnlyFilter_setsBlueAndGreenValuesToZero() throws Exception { String testId = "drawFrame_redOnlyFilter"; - float[] redOnlyFilter = new float[16]; - Matrix.setIdentityM(redOnlyFilter, /* smOffset= */ 0); - redOnlyFilter[COLOR_MATRIX_GREEN_INDEX] = 0; - redOnlyFilter[COLOR_MATRIX_BLUE_INDEX] = 0; - rgbaMatrixProcessor = createRgbaMatrixProcessor(context, redOnlyFilter); + RgbaMatrix redOnlyMatrix = new RgbAdjustment.Builder().setBlueScale(0).setGreenScale(0).build(); + rgbaMatrixProcessor = new RgbaMatrixProcessor(context, redOnlyMatrix, /* useHdr= */ false); Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ONLY_RED_CHANNEL_PNG_ASSET_PATH); @@ -183,13 +173,34 @@ public final class RgbaMatrixPixelTest { assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); } + @Test + public void drawFrame_increaseRedChannel_producesBrighterAndRedderFrame() throws Exception { + String testId = "drawFrame_increaseRedChannel"; + RgbaMatrix increaseRedMatrix = new RgbAdjustment.Builder().setRedScale(5).build(); + rgbaMatrixProcessor = new RgbaMatrixProcessor(context, increaseRedMatrix, /* useHdr= */ false); + Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); + Bitmap expectedBitmap = BitmapTestUtil.readBitmap(INCREASE_RED_CHANNEL_PNG_ASSET_PATH); + + rgbaMatrixProcessor.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_increaseBrightness_increasesAllValues() throws Exception { String testId = "drawFrame_increaseBrightness"; - float[] increaseBrightnessMatrix = new float[16]; - Matrix.setIdentityM(increaseBrightnessMatrix, /* smOffset= */ 0); - Matrix.scaleM(increaseBrightnessMatrix, /* mOffset= */ 0, /* x= */ 5, /* y= */ 5, /* z= */ 5); - rgbaMatrixProcessor = createRgbaMatrixProcessor(context, increaseBrightnessMatrix); + RgbaMatrix increaseBrightnessMatrix = + new RgbAdjustment.Builder().setRedScale(5).setGreenScale(5).setBlueScale(5).build(); + rgbaMatrixProcessor = + new RgbaMatrixProcessor(context, increaseBrightnessMatrix, /* useHdr = */ false); Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(INCREASE_BRIGHTNESS_PNG_ASSET_PATH); @@ -207,15 +218,16 @@ public final class RgbaMatrixPixelTest { } @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[] grayscaleFilter = { + 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 }; - rgbaMatrixProcessor = createRgbaMatrixProcessor(context, grayscaleFilter); + rgbaMatrixProcessor = createRgbaMatrixProcessor(/* context= */ context, grayscaleMatrix); Pair outputSize = rgbaMatrixProcessor.configure(inputWidth, inputHeight); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(GRAYSCALE_PNG_ASSET_PATH); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/RgbAdjustment.java b/libraries/effect/src/main/java/androidx/media3/effect/RgbAdjustment.java new file mode 100644 index 0000000000..8673131e55 --- /dev/null +++ b/libraries/effect/src/main/java/androidx/media3/effect/RgbAdjustment.java @@ -0,0 +1,106 @@ +/* + * 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.common.util.Assertions.checkArgument; + +import android.opengl.Matrix; +import androidx.media3.common.util.UnstableApi; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** Scales the red, green, and blue color channels of a frame. */ +@UnstableApi +public final class RgbAdjustment implements RgbaMatrix { + + /** A builder for {@link RgbAdjustment} instances. */ + public static final class Builder { + private float redScale; + private float greenScale; + private float blueScale; + + /** Creates a new instance with default values. */ + public Builder() { + redScale = 1; + greenScale = 1; + blueScale = 1; + } + + /** + * Scales the red channel of the frame by {@param redScale}. + * + * @param redScale The scale to apply to the red channel. Needs to be non-negative and the + * default value is {@code 1}. + */ + @CanIgnoreReturnValue + public Builder setRedScale(float redScale) { + checkArgument(0 <= redScale, "Red scale needs to be non-negative."); + this.redScale = redScale; + return this; + } + + /** + * Scales the green channel of the frame by {@param greenScale}. + * + * @param greenScale The scale to apply to the green channel. Needs to be non-negative and the + * default value is {@code 1}. + */ + @CanIgnoreReturnValue + public Builder setGreenScale(float greenScale) { + checkArgument(0 <= greenScale, "Green scale needs to be non-negative."); + this.greenScale = greenScale; + return this; + } + + /** + * Scales the blue channel of the frame by {@param blueScale}. + * + * @param blueScale The scale to apply to the blue channel. Needs to be non-negative and the + * default value is {@code 1}. + */ + @CanIgnoreReturnValue + public Builder setBlueScale(float blueScale) { + checkArgument(0 <= blueScale, "Blue scale needs to be non-negative."); + this.blueScale = blueScale; + return this; + } + + /** Creates a new {@link RgbAdjustment} instance. */ + public RgbAdjustment build() { + float[] rgbaMatrix = new float[16]; + Matrix.setIdentityM(rgbaMatrix, /* smOffset= */ 0); + Matrix.scaleM( + rgbaMatrix, + /* smOffset= */ 0, + /* x= */ redScale, + /* y= */ greenScale, + /* z= */ blueScale); + + return new RgbAdjustment(rgbaMatrix); + } + } + + private final float[] rgbaMatrix; + + private RgbAdjustment(float[] rgbaMatrix) { + this.rgbaMatrix = rgbaMatrix; + } + + @Override + public float[] getMatrix(long presentationTimeUs) { + return rgbaMatrix; + } +} diff --git a/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/increase_red_channel.png b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/increase_red_channel.png new file mode 100644 index 0000000000..3c45b94b52 Binary files /dev/null and b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/increase_red_channel.png differ