Update OverlaySettings
to explicitly state overlay transformations
Alters the OverlayShaderProgram implementation to support rotations, however we need to apply the transformations separately in order for them to work as expected so the matrix is removed from the interface in favour of explicit methods. Adds a rotation test to ensure this ability doesn't regress PiperOrigin-RevId: 549890847
This commit is contained in:
parent
d658de5944
commit
7eee15ecb4
@ -43,6 +43,7 @@
|
|||||||
* Remove `TransformationRequest.HdrMode` annotation type and its
|
* Remove `TransformationRequest.HdrMode` annotation type and its
|
||||||
associated constants. Use `Composition.HdrMode` and its associated
|
associated constants. Use `Composition.HdrMode` and its associated
|
||||||
constants instead.
|
constants instead.
|
||||||
|
* Simplify the `OverlaySettings` to fix rotation issues.
|
||||||
* Track Selection:
|
* Track Selection:
|
||||||
* Extractors:
|
* Extractors:
|
||||||
* MPEG-TS: Ensure the last frame is rendered by passing the last access
|
* MPEG-TS: Ensure the last frame is rendered by passing the last access
|
||||||
|
@ -16,12 +16,10 @@
|
|||||||
package androidx.media3.demo.transformer;
|
package androidx.media3.demo.transformer;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.opengl.Matrix;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.util.GlUtil;
|
|
||||||
import androidx.media3.effect.OverlaySettings;
|
import androidx.media3.effect.OverlaySettings;
|
||||||
import androidx.media3.effect.TextOverlay;
|
import androidx.media3.effect.TextOverlay;
|
||||||
import androidx.media3.effect.TextureOverlay;
|
import androidx.media3.effect.TextureOverlay;
|
||||||
@ -36,13 +34,12 @@ import java.util.Locale;
|
|||||||
private final OverlaySettings overlaySettings;
|
private final OverlaySettings overlaySettings;
|
||||||
|
|
||||||
public TimerOverlay() {
|
public TimerOverlay() {
|
||||||
float[] positioningMatrix = GlUtil.create4x4IdentityMatrix();
|
|
||||||
Matrix.translateM(
|
|
||||||
positioningMatrix, /* mOffset= */ 0, /* x= */ -0.7f, /* y= */ -0.95f, /* z= */ 1);
|
|
||||||
overlaySettings =
|
overlaySettings =
|
||||||
new OverlaySettings.Builder()
|
new OverlaySettings.Builder()
|
||||||
.setAnchor(/* x= */ -1f, /* y= */ -1f)
|
// Place the timer in the bottom left corner of the screen with some padding from the
|
||||||
.setMatrix(positioningMatrix)
|
// edges.
|
||||||
|
.setOverlayAnchor(/* x= */ 1f, /* y= */ 1f)
|
||||||
|
.setVideoFrameAnchor(/* x= */ -0.7f, /* y= */ -0.95f)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.opengl.Matrix;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
@ -58,7 +57,6 @@ import androidx.media3.common.audio.ChannelMixingAudioProcessor;
|
|||||||
import androidx.media3.common.audio.ChannelMixingMatrix;
|
import androidx.media3.common.audio.ChannelMixingMatrix;
|
||||||
import androidx.media3.common.audio.SonicAudioProcessor;
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.common.util.BitmapLoader;
|
import androidx.media3.common.util.BitmapLoader;
|
||||||
import androidx.media3.common.util.GlUtil;
|
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||||
import androidx.media3.effect.BitmapOverlay;
|
import androidx.media3.effect.BitmapOverlay;
|
||||||
@ -580,13 +578,12 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
private OverlayEffect createOverlayEffectFromBundle(Bundle bundle, boolean[] selectedEffects) {
|
private OverlayEffect createOverlayEffectFromBundle(Bundle bundle, boolean[] selectedEffects) {
|
||||||
ImmutableList.Builder<TextureOverlay> overlaysBuilder = new ImmutableList.Builder<>();
|
ImmutableList.Builder<TextureOverlay> overlaysBuilder = new ImmutableList.Builder<>();
|
||||||
if (selectedEffects[ConfigurationActivity.OVERLAY_LOGO_AND_TIMER_INDEX]) {
|
if (selectedEffects[ConfigurationActivity.OVERLAY_LOGO_AND_TIMER_INDEX]) {
|
||||||
float[] logoPositioningMatrix = GlUtil.create4x4IdentityMatrix();
|
|
||||||
Matrix.translateM(
|
|
||||||
logoPositioningMatrix, /* mOffset= */ 0, /* x= */ -0.95f, /* y= */ -0.95f, /* z= */ 1);
|
|
||||||
OverlaySettings logoSettings =
|
OverlaySettings logoSettings =
|
||||||
new OverlaySettings.Builder()
|
new OverlaySettings.Builder()
|
||||||
.setMatrix(logoPositioningMatrix)
|
// Place the logo in the bottom left corner of the screen with some padding from the
|
||||||
.setAnchor(/* x= */ -1f, /* y= */ -1f)
|
// edges.
|
||||||
|
.setOverlayAnchor(/* x= */ 1f, /* y= */ 1f)
|
||||||
|
.setVideoFrameAnchor(/* x= */ -0.95f, /* y= */ -0.95f)
|
||||||
.build();
|
.build();
|
||||||
Drawable logo;
|
Drawable logo;
|
||||||
try {
|
try {
|
||||||
|
@ -31,7 +31,6 @@ import android.graphics.Color;
|
|||||||
import android.opengl.EGLContext;
|
import android.opengl.EGLContext;
|
||||||
import android.opengl.EGLDisplay;
|
import android.opengl.EGLDisplay;
|
||||||
import android.opengl.EGLSurface;
|
import android.opengl.EGLSurface;
|
||||||
import android.opengl.Matrix;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
@ -64,10 +63,15 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/original.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/original.png";
|
||||||
private static final String OVERLAY_BITMAP_DEFAULT =
|
private static final String OVERLAY_BITMAP_DEFAULT =
|
||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_default.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_default.png";
|
||||||
private static final String OVERLAY_BITMAP_ANCHORED =
|
private static final String OVERLAY_BITMAP_DOUBLY_ANCHORED =
|
||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_anchored.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_anchored.png";
|
||||||
|
|
||||||
|
private static final String OVERLAY_BITMAP_OVERLAY_ANCHORED =
|
||||||
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_overlayAnchored.png";
|
||||||
private static final String OVERLAY_BITMAP_SCALED =
|
private static final String OVERLAY_BITMAP_SCALED =
|
||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_scaled.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_scaled.png";
|
||||||
|
private static final String OVERLAY_BITMAP_ROTATED90 =
|
||||||
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_rotated90.png";
|
||||||
private static final String OVERLAY_BITMAP_TRANSLUCENT =
|
private static final String OVERLAY_BITMAP_TRANSLUCENT =
|
||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/overlay_bitmap_translucent.png";
|
||||||
private static final String OVERLAY_TEXT_DEFAULT =
|
private static final String OVERLAY_TEXT_DEFAULT =
|
||||||
@ -154,39 +158,23 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void drawFrame_scaledBitmapOverlay_letterboxStretchesOverlay() throws Exception {
|
public void drawFrame_anchoredAndTranslatedBitmapOverlay_blendsBitmapIntoTopLeftOfFrame()
|
||||||
String testId = "drawFrame_scaledBitmapOverlay";
|
throws Exception {
|
||||||
|
String testId = "drawFrame_anchoredAndTranslatedBitmapOverlay";
|
||||||
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
float[] scaleMatrix = GlUtil.create4x4IdentityMatrix();
|
OverlaySettings overlaySettings =
|
||||||
OverlaySettings overlaySettings = new OverlaySettings.Builder().setMatrix(scaleMatrix).build();
|
new OverlaySettings.Builder()
|
||||||
|
.setOverlayAnchor(/* x= */ 1f, /* y= */ -1f)
|
||||||
|
.setVideoFrameAnchor(/* x= */ -1f, /* y= */ 1f)
|
||||||
|
.build();
|
||||||
BitmapOverlay staticBitmapOverlay =
|
BitmapOverlay staticBitmapOverlay =
|
||||||
new BitmapOverlay() {
|
BitmapOverlay.createStaticBitmapOverlay(overlayBitmap, overlaySettings);
|
||||||
@Override
|
|
||||||
public Bitmap getBitmap(long presentationTimeUs) {
|
|
||||||
return overlayBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(Size videoSize) {
|
|
||||||
Matrix.scaleM(
|
|
||||||
scaleMatrix,
|
|
||||||
/* mOffset= */ 0,
|
|
||||||
/* x= */ videoSize.getWidth() / (float) overlayBitmap.getWidth(),
|
|
||||||
/* y= */ 1,
|
|
||||||
/* z= */ 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
|
|
||||||
return overlaySettings;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
overlayShaderProgram =
|
overlayShaderProgram =
|
||||||
new OverlayEffect(ImmutableList.of(staticBitmapOverlay))
|
new OverlayEffect(ImmutableList.of(staticBitmapOverlay))
|
||||||
.toGlShaderProgram(context, /* useHdr= */ false);
|
.toGlShaderProgram(context, /* useHdr= */ false);
|
||||||
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
||||||
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
||||||
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_SCALED);
|
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_DOUBLY_ANCHORED);
|
||||||
|
|
||||||
overlayShaderProgram.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
|
overlayShaderProgram.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
|
||||||
Bitmap actualBitmap =
|
Bitmap actualBitmap =
|
||||||
@ -199,13 +187,12 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void drawFrame_anchoredBitmapOverlay_blendsBitmapIntoTopLeftOfFrame() throws Exception {
|
public void drawFrame_overlayAnchoredOnlyBitmapOverlay_anchorsOverlayFromTopLeftCornerOfFrame()
|
||||||
String testId = "drawFrame_anchoredBitmapOverlay";
|
throws Exception {
|
||||||
|
String testId = "drawFrame_anchoredAndTranslatedBitmapOverlay";
|
||||||
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
float[] translateMatrix = GlUtil.create4x4IdentityMatrix();
|
|
||||||
Matrix.translateM(translateMatrix, /* mOffset= */ 0, /* x= */ -1f, /* y= */ 1f, /* z= */ 1);
|
|
||||||
OverlaySettings overlaySettings =
|
OverlaySettings overlaySettings =
|
||||||
new OverlaySettings.Builder().setMatrix(translateMatrix).setAnchor(-1f, 1f).build();
|
new OverlaySettings.Builder().setOverlayAnchor(/* x= */ 1f, /* y= */ -1f).build();
|
||||||
BitmapOverlay staticBitmapOverlay =
|
BitmapOverlay staticBitmapOverlay =
|
||||||
BitmapOverlay.createStaticBitmapOverlay(overlayBitmap, overlaySettings);
|
BitmapOverlay.createStaticBitmapOverlay(overlayBitmap, overlaySettings);
|
||||||
overlayShaderProgram =
|
overlayShaderProgram =
|
||||||
@ -213,7 +200,31 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
.toGlShaderProgram(context, /* useHdr= */ false);
|
.toGlShaderProgram(context, /* useHdr= */ false);
|
||||||
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
||||||
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
||||||
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_ANCHORED);
|
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_OVERLAY_ANCHORED);
|
||||||
|
|
||||||
|
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_rotatedBitmapOverlay_blendsBitmapRotated90degrees() throws Exception {
|
||||||
|
String testId = "drawFrame_rotatedBitmapOverlay";
|
||||||
|
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
|
OverlaySettings overlaySettings = new OverlaySettings.Builder().setRotationDegrees(90f).build();
|
||||||
|
BitmapOverlay staticBitmapOverlay =
|
||||||
|
BitmapOverlay.createStaticBitmapOverlay(overlayBitmap, overlaySettings);
|
||||||
|
overlayShaderProgram =
|
||||||
|
new OverlayEffect(ImmutableList.of(staticBitmapOverlay))
|
||||||
|
.toGlShaderProgram(context, /* useHdr= */ false);
|
||||||
|
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
||||||
|
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
||||||
|
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_ROTATED90);
|
||||||
|
|
||||||
overlayShaderProgram.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
|
overlayShaderProgram.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
|
||||||
Bitmap actualBitmap =
|
Bitmap actualBitmap =
|
||||||
@ -333,10 +344,9 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void drawFrame_translatedTextOverlay_blendsTextIntoFrame() throws Exception {
|
public void drawFrame_anchoredTextOverlay_blendsTextIntoTheTopRightQuadrantOfFrame()
|
||||||
String testId = "drawFrame_translatedTextOverlay";
|
throws Exception {
|
||||||
float[] translateMatrix = GlUtil.create4x4IdentityMatrix();
|
String testId = "drawFrame_anchoredTextOverlay";
|
||||||
Matrix.translateM(translateMatrix, /* mOffset= */ 0, /* x= */ 0.5f, /* y= */ 0.5f, /* z= */ 1);
|
|
||||||
SpannableString overlayText = new SpannableString(/* source= */ "Text styling");
|
SpannableString overlayText = new SpannableString(/* source= */ "Text styling");
|
||||||
overlayText.setSpan(
|
overlayText.setSpan(
|
||||||
new ForegroundColorSpan(Color.GRAY),
|
new ForegroundColorSpan(Color.GRAY),
|
||||||
@ -344,7 +354,7 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
/* end= */ 4,
|
/* end= */ 4,
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
OverlaySettings overlaySettings =
|
OverlaySettings overlaySettings =
|
||||||
new OverlaySettings.Builder().setMatrix(translateMatrix).build();
|
new OverlaySettings.Builder().setVideoFrameAnchor(0.5f, 0.5f).build();
|
||||||
TextOverlay staticTextOverlay =
|
TextOverlay staticTextOverlay =
|
||||||
TextOverlay.createStaticTextOverlay(overlayText, overlaySettings);
|
TextOverlay.createStaticTextOverlay(overlayText, overlaySettings);
|
||||||
overlayShaderProgram =
|
overlayShaderProgram =
|
||||||
@ -367,8 +377,6 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
@Test
|
@Test
|
||||||
public void drawFrame_multipleOverlays_blendsBothIntoFrame() throws Exception {
|
public void drawFrame_multipleOverlays_blendsBothIntoFrame() throws Exception {
|
||||||
String testId = "drawFrame_multipleOverlays";
|
String testId = "drawFrame_multipleOverlays";
|
||||||
float[] translateMatrix1 = GlUtil.create4x4IdentityMatrix();
|
|
||||||
Matrix.translateM(translateMatrix1, /* mOffset= */ 0, /* x= */ 0.5f, /* y= */ 0.5f, /* z= */ 1);
|
|
||||||
SpannableString overlayText = new SpannableString(/* source= */ "Overlay 1");
|
SpannableString overlayText = new SpannableString(/* source= */ "Overlay 1");
|
||||||
overlayText.setSpan(
|
overlayText.setSpan(
|
||||||
new ForegroundColorSpan(Color.GRAY),
|
new ForegroundColorSpan(Color.GRAY),
|
||||||
@ -376,7 +384,7 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
/* end= */ 4,
|
/* end= */ 4,
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
OverlaySettings overlaySettings1 =
|
OverlaySettings overlaySettings1 =
|
||||||
new OverlaySettings.Builder().setMatrix(translateMatrix1).build();
|
new OverlaySettings.Builder().setVideoFrameAnchor(0.5f, 0.5f).build();
|
||||||
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings1);
|
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings1);
|
||||||
Bitmap bitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
Bitmap bitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
OverlaySettings overlaySettings2 = new OverlaySettings.Builder().setAlpha(0.5f).build();
|
OverlaySettings overlaySettings2 = new OverlaySettings.Builder().setAlpha(0.5f).build();
|
||||||
@ -407,15 +415,12 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
/* start= */ 0,
|
/* start= */ 0,
|
||||||
/* end= */ overlayText.length(),
|
/* end= */ overlayText.length(),
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
float[] scaleTextMatrix = GlUtil.create4x4IdentityMatrix();
|
|
||||||
Matrix.scaleM(scaleTextMatrix, /* mOffset= */ 0, /* x= */ 0.5f, /* y= */ 0.5f, /* z= */ 1);
|
|
||||||
OverlaySettings overlaySettings1 =
|
OverlaySettings overlaySettings1 =
|
||||||
new OverlaySettings.Builder().setMatrix(scaleTextMatrix).build();
|
new OverlaySettings.Builder().setScale(/* x= */ 0.5f, /* y= */ 0.5f).build();
|
||||||
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings1);
|
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings1);
|
||||||
Bitmap bitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
Bitmap bitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
float[] scaleMatrix = GlUtil.create4x4IdentityMatrix();
|
OverlaySettings overlaySettings2 =
|
||||||
Matrix.scaleM(scaleMatrix, /* mOffset= */ 0, /* x= */ 3, /* y= */ 3, /* z= */ 1);
|
new OverlaySettings.Builder().setScale(/* x= */ 3, /* y= */ 3).build();
|
||||||
OverlaySettings overlaySettings2 = new OverlaySettings.Builder().setMatrix(scaleMatrix).build();
|
|
||||||
BitmapOverlay bitmapOverlay = BitmapOverlay.createStaticBitmapOverlay(bitmap, overlaySettings2);
|
BitmapOverlay bitmapOverlay = BitmapOverlay.createStaticBitmapOverlay(bitmap, overlaySettings2);
|
||||||
|
|
||||||
overlayShaderProgram =
|
overlayShaderProgram =
|
||||||
@ -435,6 +440,57 @@ public class OverlayShaderProgramPixelTest {
|
|||||||
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void drawFrame_scaledBitmapOverlay_letterboxStretchesOverlay() throws Exception {
|
||||||
|
String testId = "drawFrame_scaledBitmapOverlay";
|
||||||
|
Bitmap overlayBitmap = readBitmap(OVERLAY_PNG_ASSET_PATH);
|
||||||
|
overlayShaderProgram =
|
||||||
|
new OverlayEffect(ImmutableList.of(new LetterBoxStretchedBitmapOverlay(overlayBitmap)))
|
||||||
|
.toGlShaderProgram(context, /* useHdr= */ false);
|
||||||
|
Size outputSize = overlayShaderProgram.configure(inputWidth, inputHeight);
|
||||||
|
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
||||||
|
Bitmap expectedBitmap = readBitmap(OVERLAY_BITMAP_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LetterBoxStretchedBitmapOverlay extends BitmapOverlay {
|
||||||
|
|
||||||
|
private final OverlaySettings.Builder overlaySettingsBuilder;
|
||||||
|
private final Bitmap overlayBitmap;
|
||||||
|
|
||||||
|
private @MonotonicNonNull OverlaySettings overlaySettings;
|
||||||
|
|
||||||
|
public LetterBoxStretchedBitmapOverlay(Bitmap overlayBitmap) {
|
||||||
|
this.overlayBitmap = overlayBitmap;
|
||||||
|
overlaySettingsBuilder = new OverlaySettings.Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap getBitmap(long presentationTimeUs) {
|
||||||
|
return overlayBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(Size videoSize) {
|
||||||
|
overlaySettingsBuilder.setScale(
|
||||||
|
/* x= */ videoSize.getWidth() / (float) overlayBitmap.getWidth(), /* y= */ 1);
|
||||||
|
overlaySettings = overlaySettingsBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
|
||||||
|
return checkNotNull(overlaySettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setupOutputTexture(int outputWidth, int outputHeight) throws GlUtil.GlException {
|
private void setupOutputTexture(int outputWidth, int outputHeight) throws GlUtil.GlException {
|
||||||
int outputTexId =
|
int outputTexId =
|
||||||
GlUtil.createTexture(
|
GlUtil.createTexture(
|
||||||
|
@ -19,37 +19,50 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
|||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.media3.common.util.GlUtil;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
|
||||||
/** Contains information to control how an {@link TextureOverlay} is displayed on the screen. */
|
/** Contains information to control how a {@link TextureOverlay} is displayed on the screen. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class OverlaySettings {
|
public final class OverlaySettings {
|
||||||
public final boolean useHdr;
|
public final boolean useHdr;
|
||||||
public final float alpha;
|
public final float alpha;
|
||||||
public final float[] matrix;
|
public final Pair<Float, Float> videoFrameAnchor;
|
||||||
public final Pair<Float, Float> anchor;
|
public final Pair<Float, Float> overlayAnchor;
|
||||||
|
public final Pair<Float, Float> scale;
|
||||||
|
public final float rotationDegrees;
|
||||||
|
|
||||||
private OverlaySettings(boolean useHdr, float alpha, float[] matrix, Pair<Float, Float> anchor) {
|
private OverlaySettings(
|
||||||
|
boolean useHdr,
|
||||||
|
float alpha,
|
||||||
|
Pair<Float, Float> videoFrameAnchor,
|
||||||
|
Pair<Float, Float> overlayAnchor,
|
||||||
|
Pair<Float, Float> scale,
|
||||||
|
float rotationDegrees) {
|
||||||
this.useHdr = useHdr;
|
this.useHdr = useHdr;
|
||||||
this.alpha = alpha;
|
this.alpha = alpha;
|
||||||
this.matrix = matrix;
|
this.videoFrameAnchor = videoFrameAnchor;
|
||||||
this.anchor = anchor;
|
this.overlayAnchor = overlayAnchor;
|
||||||
|
this.scale = scale;
|
||||||
|
this.rotationDegrees = rotationDegrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A builder for {@link OverlaySettings} instances. */
|
/** A builder for {@link OverlaySettings} instances. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
private boolean useHdr;
|
private boolean useHdr;
|
||||||
private float alpha;
|
private float alpha;
|
||||||
private float[] matrix;
|
private Pair<Float, Float> videoFrameAnchor;
|
||||||
private Pair<Float, Float> anchor;
|
private Pair<Float, Float> overlayAnchor;
|
||||||
|
private Pair<Float, Float> scale;
|
||||||
|
private float rotationDegrees;
|
||||||
|
|
||||||
/** Creates a new {@link Builder}. */
|
/** Creates a new {@link Builder}. */
|
||||||
public Builder() {
|
public Builder() {
|
||||||
alpha = 1f;
|
alpha = 1f;
|
||||||
matrix = GlUtil.create4x4IdentityMatrix();
|
videoFrameAnchor = Pair.create(0f, 0f);
|
||||||
anchor = Pair.create(0f, 0f);
|
overlayAnchor = Pair.create(0f, 0f);
|
||||||
|
scale = Pair.create(1f, 1f);
|
||||||
|
rotationDegrees = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,18 +77,6 @@ public final class OverlaySettings {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link android.opengl.Matrix} used to transform the overlay before applying it to a
|
|
||||||
* frame.
|
|
||||||
*
|
|
||||||
* <p>Set to always return the identity matrix by default.
|
|
||||||
*/
|
|
||||||
@CanIgnoreReturnValue
|
|
||||||
public Builder setMatrix(float[] matrix) {
|
|
||||||
this.matrix = matrix;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the alpha value of the overlay, altering its transparency.
|
* Sets the alpha value of the overlay, altering its transparency.
|
||||||
*
|
*
|
||||||
@ -91,28 +92,79 @@ public final class OverlaySettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the coordinates for the anchor point of the overlay.
|
* Sets the coordinates for the anchor point of the overlay within the video frame.
|
||||||
*
|
*
|
||||||
* <p>The anchor point is the point inside the overlay that the overlay is positioned from.
|
* <p>The coordinates are specified in Normalised Device Coordinates (NDCs) relative to the
|
||||||
|
* video frame. Set to always return {@code (0,0)} (the center of the video frame) by default.
|
||||||
*
|
*
|
||||||
* <p>The coordinates are specified in Normalised Device Coordinates (NDCs). Set to always
|
* <p>For example, a value of {@code (+1,+1)} will move the overlay's {@linkplain
|
||||||
* return {@code (0,0)} (the center) by default.
|
* #setOverlayAnchor anchor point} to the top right corner of the video frame.
|
||||||
*
|
*
|
||||||
* @param x The NDC x-coordinate in the range [-1, 1].
|
* @param x The NDC x-coordinate in the range [-1, 1].
|
||||||
* @param y The NDC y-coordinate in the range [-1, 1].
|
* @param y The NDC y-coordinate in the range [-1, 1].
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setAnchor(
|
public Builder setVideoFrameAnchor(
|
||||||
@FloatRange(from = -1, to = 1) float x, @FloatRange(from = -1, to = 1) float y) {
|
@FloatRange(from = -1, to = 1) float x, @FloatRange(from = -1, to = 1) float y) {
|
||||||
checkArgument(-1 <= x && x <= 1);
|
checkArgument(-1 <= x && x <= 1);
|
||||||
checkArgument(-1 <= y && y <= 1);
|
checkArgument(-1 <= y && y <= 1);
|
||||||
this.anchor = Pair.create(x, y);
|
this.videoFrameAnchor = Pair.create(x, y);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the coordinates for the anchor point of the overlay.
|
||||||
|
*
|
||||||
|
* <p>The anchor point is the point inside the overlay that is placed on the {@linkplain
|
||||||
|
* #setVideoFrameAnchor video frame anchor}
|
||||||
|
*
|
||||||
|
* <p>The coordinates are specified in Normalised Device Coordinates (NDCs) relative to the
|
||||||
|
* overlay frame. Set to return {@code (0,0)} (the center of the overlay) by default.
|
||||||
|
*
|
||||||
|
* <p>For example, a value of {@code (+1,-1)} will result in the overlay being positioned from
|
||||||
|
* the bottom right corner of its frame.
|
||||||
|
*
|
||||||
|
* @param x The NDC x-coordinate in the range [-1, 1].
|
||||||
|
* @param y The NDC y-coordinate in the range [-1, 1].
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setOverlayAnchor(
|
||||||
|
@FloatRange(from = -1, to = 1) float x, @FloatRange(from = -1, to = 1) float y) {
|
||||||
|
checkArgument(-1 <= x && x <= 1);
|
||||||
|
checkArgument(-1 <= y && y <= 1);
|
||||||
|
this.overlayAnchor = Pair.create(x, y);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scaling of the overlay.
|
||||||
|
*
|
||||||
|
* @param x The desired scaling in the x axis of the overlay.
|
||||||
|
* @param y The desired scaling in the y axis of the overlay.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setScale(float x, float y) {
|
||||||
|
this.scale = Pair.create(x, y);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the rotation of the overlay, counter-clockwise.
|
||||||
|
*
|
||||||
|
* <p>The overlay is rotated at the center of its frame.
|
||||||
|
*
|
||||||
|
* @param rotationDegree The desired degrees of rotation, counter-clockwise.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setRotationDegrees(float rotationDegree) {
|
||||||
|
this.rotationDegrees = rotationDegree;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates an instance of {@link OverlaySettings}, using defaults if values are unset. */
|
/** Creates an instance of {@link OverlaySettings}, using defaults if values are unset. */
|
||||||
public OverlaySettings build() {
|
public OverlaySettings build() {
|
||||||
return new OverlaySettings(useHdr, alpha, matrix, anchor);
|
return new OverlaySettings(
|
||||||
|
useHdr, alpha, videoFrameAnchor, overlayAnchor, scale, rotationDegrees);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,16 @@ import com.google.common.collect.ImmutableList;
|
|||||||
|
|
||||||
private final GlProgram glProgram;
|
private final GlProgram glProgram;
|
||||||
private final ImmutableList<TextureOverlay> overlays;
|
private final ImmutableList<TextureOverlay> overlays;
|
||||||
|
private final float[] videoFrameAnchorMatrix;
|
||||||
|
private final float[] videoFrameAnchorMatrixInv;
|
||||||
private final float[] aspectRatioMatrix;
|
private final float[] aspectRatioMatrix;
|
||||||
private final float[] overlayMatrix;
|
private final float[] scaleMatrix;
|
||||||
private final float[] anchorMatrix;
|
private final float[] scaleMatrixInv;
|
||||||
|
private final float[] overlayAnchorMatrix;
|
||||||
|
private final float[] overlayAnchorMatrixInv;
|
||||||
|
private final float[] rotateMatrix;
|
||||||
|
private final float[] overlayAspectRatioMatrix;
|
||||||
|
private final float[] overlayAspectRatioMatrixInv;
|
||||||
private final float[] transformationMatrix;
|
private final float[] transformationMatrix;
|
||||||
|
|
||||||
private int videoWidth;
|
private int videoWidth;
|
||||||
@ -63,8 +70,15 @@ import com.google.common.collect.ImmutableList;
|
|||||||
"OverlayShaderProgram does not support more than 15 overlays in the same instance.");
|
"OverlayShaderProgram does not support more than 15 overlays in the same instance.");
|
||||||
this.overlays = overlays;
|
this.overlays = overlays;
|
||||||
aspectRatioMatrix = GlUtil.create4x4IdentityMatrix();
|
aspectRatioMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
overlayMatrix = GlUtil.create4x4IdentityMatrix();
|
videoFrameAnchorMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
anchorMatrix = GlUtil.create4x4IdentityMatrix();
|
videoFrameAnchorMatrixInv = GlUtil.create4x4IdentityMatrix();
|
||||||
|
overlayAnchorMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
overlayAnchorMatrixInv = GlUtil.create4x4IdentityMatrix();
|
||||||
|
rotateMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
scaleMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
scaleMatrixInv = GlUtil.create4x4IdentityMatrix();
|
||||||
|
overlayAspectRatioMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
overlayAspectRatioMatrixInv = GlUtil.create4x4IdentityMatrix();
|
||||||
transformationMatrix = GlUtil.create4x4IdentityMatrix();
|
transformationMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
try {
|
try {
|
||||||
glProgram =
|
glProgram =
|
||||||
@ -105,43 +119,163 @@ import com.google.common.collect.ImmutableList;
|
|||||||
texUnitIndex);
|
texUnitIndex);
|
||||||
|
|
||||||
GlUtil.setToIdentity(aspectRatioMatrix);
|
GlUtil.setToIdentity(aspectRatioMatrix);
|
||||||
|
GlUtil.setToIdentity(videoFrameAnchorMatrix);
|
||||||
|
GlUtil.setToIdentity(videoFrameAnchorMatrixInv);
|
||||||
|
GlUtil.setToIdentity(overlayAnchorMatrix);
|
||||||
|
GlUtil.setToIdentity(overlayAnchorMatrixInv);
|
||||||
|
GlUtil.setToIdentity(scaleMatrix);
|
||||||
|
GlUtil.setToIdentity(scaleMatrixInv);
|
||||||
|
GlUtil.setToIdentity(rotateMatrix);
|
||||||
|
GlUtil.setToIdentity(overlayAspectRatioMatrix);
|
||||||
|
GlUtil.setToIdentity(overlayAspectRatioMatrixInv);
|
||||||
|
GlUtil.setToIdentity(transformationMatrix);
|
||||||
|
|
||||||
|
// Anchor point of overlay within output frame.
|
||||||
|
Pair<Float, Float> videoFrameAnchor =
|
||||||
|
overlay.getOverlaySettings(presentationTimeUs).videoFrameAnchor;
|
||||||
|
Matrix.translateM(
|
||||||
|
videoFrameAnchorMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
videoFrameAnchor.first,
|
||||||
|
videoFrameAnchor.second,
|
||||||
|
/* z= */ 0f);
|
||||||
|
Matrix.invertM(
|
||||||
|
videoFrameAnchorMatrixInv, MATRIX_OFFSET, videoFrameAnchorMatrix, MATRIX_OFFSET);
|
||||||
|
|
||||||
Matrix.scaleM(
|
Matrix.scaleM(
|
||||||
aspectRatioMatrix,
|
aspectRatioMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
videoWidth / (float) overlay.getTextureSize(presentationTimeUs).getWidth(),
|
videoWidth / (float) overlay.getTextureSize(presentationTimeUs).getWidth(),
|
||||||
videoHeight / (float) overlay.getTextureSize(presentationTimeUs).getHeight(),
|
videoHeight / (float) overlay.getTextureSize(presentationTimeUs).getHeight(),
|
||||||
/* z= */ 1);
|
/* z= */ 1f);
|
||||||
Matrix.invertM(
|
|
||||||
overlayMatrix,
|
// Scale the image.
|
||||||
|
Pair<Float, Float> scale = overlay.getOverlaySettings(presentationTimeUs).scale;
|
||||||
|
Matrix.setIdentityM(scaleMatrix, MATRIX_OFFSET);
|
||||||
|
Matrix.scaleM(
|
||||||
|
scaleMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
overlay.getOverlaySettings(presentationTimeUs).matrix,
|
scaleMatrix,
|
||||||
MATRIX_OFFSET);
|
MATRIX_OFFSET,
|
||||||
Pair<Float, Float> overlayAnchor = overlay.getOverlaySettings(presentationTimeUs).anchor;
|
scale.first,
|
||||||
GlUtil.setToIdentity(anchorMatrix);
|
scale.second,
|
||||||
|
/* z= */ 1f);
|
||||||
|
Matrix.invertM(scaleMatrixInv, MATRIX_OFFSET, scaleMatrix, MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Translate the overlay within its frame.
|
||||||
|
Pair<Float, Float> overlayAnchor =
|
||||||
|
overlay.getOverlaySettings(presentationTimeUs).overlayAnchor;
|
||||||
|
Matrix.setIdentityM(overlayAnchorMatrix, MATRIX_OFFSET);
|
||||||
Matrix.translateM(
|
Matrix.translateM(
|
||||||
anchorMatrix,
|
overlayAnchorMatrix,
|
||||||
/* mOffset= */ 0,
|
MATRIX_OFFSET,
|
||||||
overlayAnchor.first
|
overlayAnchor.first,
|
||||||
* overlay.getTextureSize(presentationTimeUs).getWidth()
|
overlayAnchor.second,
|
||||||
/ videoWidth,
|
/* z= */ 0f);
|
||||||
overlayAnchor.second
|
Matrix.invertM(overlayAnchorMatrixInv, MATRIX_OFFSET, overlayAnchorMatrix, MATRIX_OFFSET);
|
||||||
* overlay.getTextureSize(presentationTimeUs).getHeight()
|
|
||||||
/ videoHeight,
|
// Rotate the image.
|
||||||
/* z= */ 1);
|
Matrix.setIdentityM(rotateMatrix, MATRIX_OFFSET);
|
||||||
|
Matrix.rotateM(
|
||||||
|
rotateMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
rotateMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
overlay.getOverlaySettings(presentationTimeUs).rotationDegrees,
|
||||||
|
/* x= */ 0f,
|
||||||
|
/* y= */ 0f,
|
||||||
|
/* z= */ 1f);
|
||||||
|
Matrix.invertM(rotateMatrix, MATRIX_OFFSET, rotateMatrix, MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Rotation matrix needs to account for overlay aspect ratio to prevent stretching.
|
||||||
|
Matrix.scaleM(
|
||||||
|
overlayAspectRatioMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
(float) overlay.getTextureSize(presentationTimeUs).getHeight()
|
||||||
|
/ (float) overlay.getTextureSize(presentationTimeUs).getWidth(),
|
||||||
|
/* y= */ 1f,
|
||||||
|
/* z= */ 1f);
|
||||||
|
Matrix.invertM(
|
||||||
|
overlayAspectRatioMatrixInv, MATRIX_OFFSET, overlayAspectRatioMatrix, MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Rotation needs to be agnostic of the scaling matrix and the aspect ratios.
|
||||||
Matrix.multiplyMM(
|
Matrix.multiplyMM(
|
||||||
transformationMatrix,
|
transformationMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
overlayMatrix,
|
transformationMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
anchorMatrix,
|
scaleMatrixInv,
|
||||||
MATRIX_OFFSET);
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
Matrix.multiplyMM(
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
overlayAspectRatioMatrix,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Rotation matrix.
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
rotateMatrix,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
overlayAspectRatioMatrixInv,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
scaleMatrix,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Translate image.
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
overlayAnchorMatrixInv,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Scale image.
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
scaleMatrixInv,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Correct for aspect ratio of image in output frame.
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
transformationMatrix,
|
transformationMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
aspectRatioMatrix,
|
aspectRatioMatrix,
|
||||||
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
|
// Anchor position in output frame.
|
||||||
|
Matrix.multiplyMM(
|
||||||
|
transformationMatrix,
|
||||||
MATRIX_OFFSET,
|
MATRIX_OFFSET,
|
||||||
transformationMatrix,
|
transformationMatrix,
|
||||||
|
MATRIX_OFFSET,
|
||||||
|
videoFrameAnchorMatrixInv,
|
||||||
MATRIX_OFFSET);
|
MATRIX_OFFSET);
|
||||||
|
|
||||||
glProgram.setFloatsUniform(
|
glProgram.setFloatsUniform(
|
||||||
Util.formatInvariant("uTransformationMatrix%d", texUnitIndex), transformationMatrix);
|
Util.formatInvariant("uTransformationMatrix%d", texUnitIndex), transformationMatrix);
|
||||||
|
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 509 KiB |
Binary file not shown.
After Width: | Height: | Size: 504 KiB |
Loading…
x
Reference in New Issue
Block a user