diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayShaderProgramPixelTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayShaderProgramPixelTest.java index 33cbcdea74..fa3f9a4aeb 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayShaderProgramPixelTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/OverlayShaderProgramPixelTest.java @@ -35,6 +35,7 @@ import android.opengl.Matrix; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.Size; @@ -71,6 +72,8 @@ public class OverlayShaderProgramPixelTest { "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png"; private static final String OVERLAY_TEXT_DEFAULT = "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_default.png"; + private static final String OVERLAY_TEXT_SPAN_SCALED = + "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_span_scaled.png"; private static final String OVERLAY_TEXT_TRANSLATE = "media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_translate.png"; private static final String OVERLAY_MULTIPLE = @@ -302,6 +305,33 @@ public class OverlayShaderProgramPixelTest { assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); } + @Test + public void drawFrame_textOverlayWithRelativeScaleSpan_blendsTextIntoFrame() throws Exception { + String testId = "drawFrame_textOverlayWithRelativeScaleSpan"; + SpannableString overlayText = new SpannableString(/* source= */ "helllllloooo!!!"); + overlayText.setSpan( + new RelativeSizeSpan(2f), + /* start= */ 0, + /* end= */ overlayText.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + TextOverlay staticTextOverlay = TextOverlay.createStaticTextOverlay(overlayText); + overlayShaderProgram = + new OverlayEffect(ImmutableList.of(staticTextOverlay)) + .toGlShaderProgram(context, /* useHdr= */ false); + Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight); + setupOutputTexture(outputSize.getWidth(), outputSize.getHeight()); + Bitmap expectedBitmap = readBitmap(OVERLAY_TEXT_SPAN_SCALED); + + overlayShaderProgram.drawFrame(inputTexId, /* presentationTimeUs= */ 0); + Bitmap actualBitmap = + createArgb8888BitmapFromFocusedGlFramebuffer(outputSize.getWidth(), outputSize.getHeight()); + + maybeSaveTestBitmap(testId, /* bitmapLabel= */ "actual", actualBitmap, /* path= */ null); + float averagePixelAbsoluteDifference = + getBitmapAveragePixelAbsoluteDifferenceArgb8888(expectedBitmap, actualBitmap, testId); + assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); + } + @Test public void drawFrame_translatedTextOverlay_blendsTextIntoFrame() throws Exception { String testId = "drawFrame_translatedTextOverlay"; diff --git a/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java b/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java index e0a95bdeaa..3b8d17793d 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java @@ -17,6 +17,7 @@ package androidx.media3.effect; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Util.SDK_INT; +import static java.lang.Math.ceil; import android.annotation.SuppressLint; import android.graphics.Bitmap; @@ -87,7 +88,6 @@ public abstract class TextOverlay extends BitmapOverlay { */ public abstract SpannableString getText(long presentationTimeUs); - @SuppressLint("InlinedApi") // Inlined Layout constants. @Override public Bitmap getBitmap(long presentationTimeUs) { SpannableString overlayText = getText(presentationTimeUs); @@ -95,21 +95,8 @@ public abstract class TextOverlay extends BitmapOverlay { lastText = overlayText; TextPaint textPaint = new TextPaint(); textPaint.setTextSize(TEXT_SIZE_PIXELS); - StaticLayout staticLayout; - int width = (int) textPaint.measureText(overlayText, /* start= */ 0, overlayText.length()); - if (SDK_INT >= 23) { - staticLayout = Api23.getStaticLayout(overlayText, textPaint, width); - } else { - staticLayout = - new StaticLayout( - overlayText, - textPaint, - width, - Layout.Alignment.ALIGN_NORMAL, - Layout.DEFAULT_LINESPACING_MULTIPLIER, - Layout.DEFAULT_LINESPACING_ADDITION, - /* includepad= */ true); - } + StaticLayout staticLayout = + createStaticLayout(overlayText, textPaint, getSpannedTextWidth(overlayText, textPaint)); lastBitmap = Bitmap.createBitmap( staticLayout.getWidth(), staticLayout.getHeight(), Bitmap.Config.ARGB_8888); @@ -119,6 +106,33 @@ public abstract class TextOverlay extends BitmapOverlay { return checkNotNull(lastBitmap); } + private int getSpannedTextWidth(SpannableString text, TextPaint textPaint) { + // measureText doesn't take scaling spans into account so using a StaticLayout to measure + // the actual text width, then use a different StaticLayout to draw the text onto a Bitmap. + int measureTextWidth = (int) textPaint.measureText(text, /* start= */ 0, text.length()); + StaticLayout widthMeasuringLayout = createStaticLayout(text, textPaint, measureTextWidth); + int lineCount = widthMeasuringLayout.getLineCount(); + float realTextWidth = 0; + for (int i = 0; i < lineCount; i++) { + realTextWidth += widthMeasuringLayout.getLineWidth(i); + } + return (int) ceil(realTextWidth); + } + + @SuppressLint("InlinedApi") // Inlined Layout constants. + private StaticLayout createStaticLayout(SpannableString text, TextPaint textPaint, int width) { + return SDK_INT >= 23 + ? Api23.getStaticLayout(text, textPaint, width) + : new StaticLayout( + text, + textPaint, + width, + Layout.Alignment.ALIGN_NORMAL, + Layout.DEFAULT_LINESPACING_MULTIPLIER, + Layout.DEFAULT_LINESPACING_ADDITION, + /* includepad= */ true); + } + @RequiresApi(23) private static final class Api23 { @DoNotInline diff --git a/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_span_scaled.png b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_span_scaled.png new file mode 100644 index 0000000000..162590a98f Binary files /dev/null and b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_text_span_scaled.png differ