Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add outlineWidth to Cue #1840

Open
wants to merge 5 commits into
base: release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
Expand Down Expand Up @@ -272,6 +273,11 @@ public final class Cue {
*/
public final float size;

/**
* The size of the text outline stroke in pixels or {@link #DIMEN_UNSET} for default width.
*/
public final @Px float outlineWidth;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to the whole Cue

But for google/ExoPlayer#9391 I think we will need to be able to apply different outlining to different parts of text within a cue (in order to properly implement the TTML spec).

I think this points towards to using a custom styling span instead (google/ExoPlayer#9391 (comment)).

With a styling span, the answer to your WebView question then becomes to modify this class I think: https://github.com/androidx/media/blob/main/libraries/ui/src/main/java/androidx/media3/ui/SpannedToHtmlConverter.java

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bruh, that makes it a lot more complicated. However I assume that no extra paramater is needed in Cue then, as SpannableString is a CharSequence and outlinewidth can therefore piggyback from CharSequence text. That just makes the drawing a pita as we need to loop all spans.

Would something in the style of https://stackoverflow.com/questions/43275159/how-to-draw-a-spanned-string-with-canvas-drawtext-in-android work for SubtitlePainter? If so, what properties would this custom span have? Just outline width, or would I also need to support additional properties like color, or would that be a separate span that also would need support?


/**
* The bitmap height as a fraction of the of the viewport size, or {@link #DIMEN_UNSET} if the
* bitmap should be displayed at its natural height given the bitmap dimensions and the specified
Expand Down Expand Up @@ -326,7 +332,8 @@ private Cue(
boolean windowColorSet,
int windowColor,
@VerticalType int verticalType,
float shearDegrees) {
float shearDegrees,
@Px float outlineWidth) {
// Exactly one of text or bitmap should be set.
if (text == null) {
Assertions.checkNotNull(bitmap);
Expand Down Expand Up @@ -356,6 +363,7 @@ private Cue(
this.textSize = textSize;
this.verticalType = verticalType;
this.shearDegrees = shearDegrees;
this.outlineWidth = outlineWidth;
}

/** Returns a new {@link Cue.Builder} initialized with the same values as this Cue. */
Expand Down Expand Up @@ -391,7 +399,8 @@ public boolean equals(@Nullable Object obj) {
&& textSizeType == that.textSizeType
&& textSize == that.textSize
&& verticalType == that.verticalType
&& shearDegrees == that.shearDegrees;
&& shearDegrees == that.shearDegrees
&& outlineWidth == that.outlineWidth;
}

@Override
Expand All @@ -413,7 +422,8 @@ public int hashCode() {
textSizeType,
textSize,
verticalType,
shearDegrees);
shearDegrees,
outlineWidth);
}

/** A builder for {@link Cue} objects. */
Expand All @@ -436,6 +446,7 @@ public static final class Builder {
@ColorInt private int windowColor;
private @VerticalType int verticalType;
private float shearDegrees;
private @Px float outlineWidth;

public Builder() {
text = null;
Expand All @@ -454,6 +465,7 @@ public Builder() {
windowColorSet = false;
windowColor = Color.BLACK;
verticalType = TYPE_UNSET;
outlineWidth = DIMEN_UNSET;
}

private Builder(Cue cue) {
Expand All @@ -474,6 +486,7 @@ private Builder(Cue cue) {
windowColor = cue.windowColor;
verticalType = cue.verticalType;
shearDegrees = cue.shearDegrees;
outlineWidth = cue.outlineWidth;
}

/**
Expand Down Expand Up @@ -715,6 +728,27 @@ public float getSize() {
return size;
}

/**
* Sets the outline width in pixels
*
* @see Cue#outlineWidth
*/
@CanIgnoreReturnValue
public Builder setOutlineWidth(@Px float outlineWidth) {
this.outlineWidth = outlineWidth;
return this;
}

/**
* Gets the outline width in pixels
*
* @see Cue#outlineWidth
*/
@Pure
public float getOutlineWidth() {
return outlineWidth;
}

/**
* Sets the bitmap height as a fraction of the viewport size.
*
Expand Down Expand Up @@ -825,7 +859,8 @@ public Cue build() {
windowColorSet,
windowColor,
verticalType,
shearDegrees);
shearDegrees,
outlineWidth);
}
}

Expand All @@ -848,6 +883,7 @@ public Cue build() {
private static final String FIELD_WINDOW_COLOR_SET = Util.intToStringMaxRadix(14);
private static final String FIELD_VERTICAL_TYPE = Util.intToStringMaxRadix(15);
private static final String FIELD_SHEAR_DEGREES = Util.intToStringMaxRadix(16);
private static final String FIELD_OUTLINE_WIDTH = Util.intToStringMaxRadix(19);

/**
* Returns a {@link Bundle} that can be serialized to bytes.
Expand Down Expand Up @@ -923,6 +959,7 @@ private Bundle toBundleWithoutBitmap() {
bundle.putInt(FIELD_WINDOW_COLOR, windowColor);
bundle.putInt(FIELD_VERTICAL_TYPE, verticalType);
bundle.putFloat(FIELD_SHEAR_DEGREES, shearDegrees);
bundle.putFloat(FIELD_OUTLINE_WIDTH, outlineWidth);
return bundle;
}

Expand Down Expand Up @@ -995,6 +1032,9 @@ public static Cue fromBundle(Bundle bundle) {
if (bundle.containsKey(FIELD_SHEAR_DEGREES)) {
builder.setShearDegrees(bundle.getFloat(FIELD_SHEAR_DEGREES));
}
if (bundle.containsKey(FIELD_OUTLINE_WIDTH)) {
builder.setOutlineWidth(bundle.getFloat(FIELD_OUTLINE_WIDTH));
}
return builder.build();
}
}
37 changes: 26 additions & 11 deletions libraries/ui/src/main/java/androidx/media3/ui/SubtitlePainter.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,12 @@
private static final float INNER_PADDING_RATIO = 0.125f;

// Styled dimensions.
private final float outlineWidth;
private final float shadowRadius;
private final float shadowOffset;
private float outlineWidth;
private float shadowRadius;
private float shadowOffset;
private final float defaultOutlineWidth;
private final float defaultShadowRadius;
private final float defaultShadowOffset;
private final float spacingMult;
private final float spacingAdd;

Expand Down Expand Up @@ -105,9 +108,14 @@ public SubtitlePainter(Context context) {
Resources resources = context.getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
outlineWidth = twoDpInPx;
shadowRadius = twoDpInPx;
shadowOffset = twoDpInPx;

outlineWidth = Cue.DIMEN_UNSET;
shadowRadius = Cue.DIMEN_UNSET;
shadowOffset = Cue.DIMEN_UNSET;

defaultOutlineWidth = twoDpInPx;
defaultShadowRadius = twoDpInPx;
defaultShadowOffset = twoDpInPx;

textPaint = new TextPaint();
textPaint.setAntiAlias(true);
Expand Down Expand Up @@ -213,6 +221,9 @@ public void draw(
this.parentTop = cueBoxTop;
this.parentRight = cueBoxRight;
this.parentBottom = cueBoxBottom;
this.outlineWidth = cue.outlineWidth;
this.shadowRadius = cue.outlineWidth;
this.shadowOffset = cue.outlineWidth;

if (isTextCue) {
Assertions.checkNotNull(cueText);
Expand Down Expand Up @@ -425,24 +436,28 @@ private void drawTextLayout(Canvas canvas) {
}

if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
float localWidth = outlineWidth != Cue.DIMEN_UNSET ? outlineWidth : defaultOutlineWidth;
textPaint.setStrokeJoin(Join.ROUND);
textPaint.setStrokeWidth(outlineWidth);
textPaint.setStrokeWidth(localWidth);
textPaint.setColor(edgeColor);
textPaint.setStyle(Style.FILL_AND_STROKE);
edgeLayout.draw(canvas);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor);
float localOffset = shadowOffset != Cue.DIMEN_UNSET ? shadowOffset : defaultShadowOffset;
float localRadius = shadowRadius != Cue.DIMEN_UNSET ? shadowRadius : defaultShadowRadius;
textPaint.setShadowLayer(localRadius, localOffset, localOffset, edgeColor);
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
|| edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
int colorUp = raised ? Color.WHITE : edgeColor;
int colorDown = raised ? edgeColor : Color.WHITE;
float offset = shadowRadius / 2f;
float localRadius = shadowRadius != Cue.DIMEN_UNSET ? shadowRadius : defaultShadowRadius;
float offset = localRadius / 2f;
textPaint.setColor(foregroundColor);
textPaint.setStyle(Style.FILL);
textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp);
textPaint.setShadowLayer(localRadius, -offset, -offset, colorUp);
edgeLayout.draw(canvas);
textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown);
textPaint.setShadowLayer(localRadius, offset, offset, colorDown);
}

textPaint.setColor(foregroundColor);
Expand Down