Fix TextOverlay's overlay width measuring strategy

Before this CL, the text with a scale span would wrap text, with because the scale wasn't taken into account when measuring the width of the overlay.

PiperOrigin-RevId: 548123626
This commit is contained in:
tofunmi 2023-07-14 15:42:16 +01:00 committed by Ian Baker
parent 42998d6400
commit 453431fef2
3 changed files with 60 additions and 16 deletions

View File

@ -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";

View File

@ -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