diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayTextureProcessorPixelTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayTextureProcessorPixelTest.java index 97b9e32c9e..c00bba009f 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayTextureProcessorPixelTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayTextureProcessorPixelTest.java @@ -64,6 +64,8 @@ public class OverlayTextureProcessorPixelTest { "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_default.png"; public static final String OVERLAY_BITMAP_SCALED = "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_scaled.png"; + public static final String OVERLAY_BITMAP_TRANSLUCENT = + "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png"; public static final String OVERLAY_TEXT_DEFAULT = "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_default.png"; public static final String OVERLAY_TEXT_TRANSLATE = @@ -167,6 +169,59 @@ public class OverlayTextureProcessorPixelTest { assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); } + @Test + public void drawFrame_translucentBitmapOverlay_blendsBitmapIntoFrame() throws Exception { + String testId = "drawFrame_translucentBitmapOverlay"; + Bitmap bitmap = readBitmap(OVERLAY_PNG_ASSET_PATH); + OverlaySettings overlaySettings = new OverlaySettings.Builder().setAlpha(0.5f).build(); + BitmapOverlay translucentBitmapOverlay = + BitmapOverlay.createStaticBitmapOverlay(bitmap, overlaySettings); + overlayTextureProcessor = + new OverlayEffect(ImmutableList.of(translucentBitmapOverlay)) + .toGlTextureProcessor(context, false); + Pair outputSize = overlayTextureProcessor.configure(inputWidth, inputHeight); + setupOutputTexture(outputSize.first, outputSize.second); + Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_TRANSLUCENT); + + overlayTextureProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0); + Bitmap actualBitmap = + createArgb8888BitmapFromCurrentGlFramebuffer(outputSize.first, outputSize.second); + + maybeSaveTestBitmapToCacheDirectory(testId, /* bitmapLabel= */ "actual", actualBitmap); + float averagePixelAbsoluteDifference = + getBitmapAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, actualBitmap, testId); + assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); + } + + @Test + public void drawFrame_transparentTextOverlay_blendsBitmapIntoFrame() throws Exception { + String testId = "drawFrame_transparentTextOverlay"; + SpannableString overlayText = new SpannableString(/* source= */ "Text styling"); + OverlaySettings overlaySettings = new OverlaySettings.Builder().setAlpha(0f).build(); + overlayText.setSpan( + new ForegroundColorSpan(Color.GRAY), + /* start= */ 0, + /* end= */ 4, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + TextOverlay staticTextOverlay = + TextOverlay.createStaticTextOverlay(overlayText, overlaySettings); + overlayTextureProcessor = + new OverlayEffect(ImmutableList.of(staticTextOverlay)) + .toGlTextureProcessor(context, /* useHdr= */ false); + Pair outputSize = overlayTextureProcessor.configure(inputWidth, inputHeight); + setupOutputTexture(outputSize.first, outputSize.second); + Bitmap expectedBitmap = readBitmap(ORIGINAL_PNG_ASSET_PATH); + + overlayTextureProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0); + Bitmap actualBitmap = + createArgb8888BitmapFromCurrentGlFramebuffer(outputSize.first, outputSize.second); + + maybeSaveTestBitmapToCacheDirectory(testId, /* bitmapLabel= */ "actual", actualBitmap); + float averagePixelAbsoluteDifference = + getBitmapAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, actualBitmap, testId); + assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); + } + @Test public void drawFrame_textOverlay_blendsTextIntoFrame() throws Exception { String testId = "drawFrame_textOverlay"; diff --git a/libraries/effect/src/main/assets/shaders/fragment_shader_overlay_es2.glsl b/libraries/effect/src/main/assets/shaders/fragment_shader_overlay_es2.glsl index 436edb4555..9723f70e83 100644 --- a/libraries/effect/src/main/assets/shaders/fragment_shader_overlay_es2.glsl +++ b/libraries/effect/src/main/assets/shaders/fragment_shader_overlay_es2.glsl @@ -20,6 +20,9 @@ precision mediump float; uniform sampler2D uVideoTexSampler0; // Texture containing the overlay bitmap. uniform sampler2D uOverlayTexSampler1; +// The alpha values for the texture. +uniform float uOverlayAlpha1; + varying vec2 vVideoTexSamplingCoord; varying vec2 vOverlayTexSamplingCoord1; @@ -27,15 +30,18 @@ varying vec2 vOverlayTexSamplingCoord1; // (https://open.gl/textures) since it's not implemented until OpenGL ES 3.2. vec4 getClampToBorderOverlayColor() { if (vOverlayTexSamplingCoord1.x > 1.0 || vOverlayTexSamplingCoord1.x < 0.0 - || vOverlayTexSamplingCoord1.y > 1.0 || vOverlayTexSamplingCoord1.y < 0.0){ + || vOverlayTexSamplingCoord1.y > 1.0 || vOverlayTexSamplingCoord1.y < 0.0) { return vec4(0.0, 0.0, 0.0, 0.0); } else { - return vec4(texture2D(uOverlayTexSampler1, vOverlayTexSamplingCoord1)); + vec4 overlayColor = vec4( + texture2D(uOverlayTexSampler1, vOverlayTexSamplingCoord1)); + overlayColor.a = uOverlayAlpha1 * overlayColor.a; + return overlayColor; } } float getMixAlpha(float videoAlpha, float overlayAlpha) { - if (videoAlpha == 0.0){ + if (videoAlpha == 0.0) { return 1.0; } else { return clamp(overlayAlpha/videoAlpha, 0.0, 1.0); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/OverlaySettings.java b/libraries/effect/src/main/java/androidx/media3/effect/OverlaySettings.java index 934af28f8f..0471ba1f75 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/OverlaySettings.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/OverlaySettings.java @@ -14,6 +14,9 @@ package androidx.media3.effect; * See the License for the specific language governing permissions and * limitations under the License. */ +import static androidx.media3.common.util.Assertions.checkArgument; + +import androidx.annotation.FloatRange; import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.UnstableApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -22,16 +25,19 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; @UnstableApi public final class OverlaySettings { public final boolean useHdr; + public final float alpha; public final float[] matrix; - private OverlaySettings(boolean useHdr, float[] matrix) { + private OverlaySettings(boolean useHdr, float alpha, float[] matrix) { this.useHdr = useHdr; + this.alpha = alpha; this.matrix = matrix; } /** A builder for {@link OverlaySettings} instances. */ public static final class Builder { private boolean useHdr; + private float alpha = 1; private float[] matrix; /** Creates a new {@link Builder}. */ @@ -63,9 +69,23 @@ public final class OverlaySettings { return this; } + /** + * Sets the alpha value of the overlay, altering its transparency. + * + *

Alpha values range from 0 (all transparent) to 1 (completely opaque). + * + *

Set to always return {@code 1} by default. + */ + @CanIgnoreReturnValue + public Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) { + checkArgument(0 <= alpha && alpha <= 1, "Alpha needs to be in the interval [0, 1]."); + this.alpha = alpha; + return this; + } + /** Creates an instance of {@link OverlaySettings}, using defaults if values are unset. */ public OverlaySettings build() { - return new OverlaySettings(useHdr, matrix); + return new OverlaySettings(useHdr, alpha, matrix); } } } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/OverlayTextureProcessor.java b/libraries/effect/src/main/java/androidx/media3/effect/OverlayTextureProcessor.java index bd6b6a8a13..b35ff897db 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/OverlayTextureProcessor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/OverlayTextureProcessor.java @@ -99,13 +99,14 @@ import java.io.IOException; videoHeight / (float) overlayTextureSize.second, /* z= */ 1); glProgram.setFloatsUniform("uAspectRatioMatrix", aspectRatioMatrix); - Matrix.invertM( overlayMatrix, MATRIX_OFFSET, overlay.getOverlaySettings(presentationTimeUs).matrix, MATRIX_OFFSET); glProgram.setFloatsUniform("uOverlayMatrix", overlayMatrix); + glProgram.setFloatUniform( + "uOverlayAlpha1", overlay.getOverlaySettings(presentationTimeUs).alpha); } else { glProgram.setSamplerTexIdUniform( diff --git a/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png new file mode 100644 index 0000000000..0a8490d988 Binary files /dev/null and b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png differ