Commit 3701e805 by hschlueter Committed by Ian Baker

Use MatrixTransformation instead of wrapping its GlFrameProcssor.

ScaleToFitFrameProcessor, PresentationFrameProcessor,
and EncoderCompatibilityFrameProcessor now each implement
MatrixTransformation instead of wrapping
MatrixTransformationFrameProcessor.

PiperOrigin-RevId: 446480286
parent 3730c1e4
Showing with 201 additions and 291 deletions
...@@ -154,7 +154,7 @@ public final class FrameProcessorChainPixelTest { ...@@ -154,7 +154,7 @@ public final class FrameProcessorChainPixelTest {
setUpAndPrepareFirstFrame( setUpAndPrepareFirstFrame(
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
(MatrixTransformation) (long presentationTimeUs) -> translateRightMatrix, (MatrixTransformation) (long presentationTimeUs) -> translateRightMatrix,
() -> new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build()); new ScaleToFitTransformation.Builder().setRotationDegrees(45).build());
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(TRANSLATE_THEN_ROTATE_PNG_ASSET_PATH); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(TRANSLATE_THEN_ROTATE_PNG_ASSET_PATH);
Bitmap actualBitmap = processFirstFrameAndEnd(); Bitmap actualBitmap = processFirstFrameAndEnd();
...@@ -176,7 +176,7 @@ public final class FrameProcessorChainPixelTest { ...@@ -176,7 +176,7 @@ public final class FrameProcessorChainPixelTest {
translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0); translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0);
setUpAndPrepareFirstFrame( setUpAndPrepareFirstFrame(
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
() -> new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build(), new ScaleToFitTransformation.Builder().setRotationDegrees(45).build(),
(MatrixTransformation) (long presentationTimeUs) -> translateRightMatrix); (MatrixTransformation) (long presentationTimeUs) -> translateRightMatrix);
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE_THEN_TRANSLATE_PNG_ASSET_PATH); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE_THEN_TRANSLATE_PNG_ASSET_PATH);
...@@ -195,8 +195,7 @@ public final class FrameProcessorChainPixelTest { ...@@ -195,8 +195,7 @@ public final class FrameProcessorChainPixelTest {
public void processData_withPresentation_setResolution_producesExpectedOutput() throws Exception { public void processData_withPresentation_setResolution_producesExpectedOutput() throws Exception {
String testId = "processData_withPresentation_setResolution"; String testId = "processData_withPresentation_setResolution";
setUpAndPrepareFirstFrame( setUpAndPrepareFirstFrame(
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, new Presentation.Builder().setResolution(480).build());
() -> new PresentationFrameProcessor.Builder().setResolution(480).build());
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH);
Bitmap actualBitmap = processFirstFrameAndEnd(); Bitmap actualBitmap = processFirstFrameAndEnd();
...@@ -216,7 +215,7 @@ public final class FrameProcessorChainPixelTest { ...@@ -216,7 +215,7 @@ public final class FrameProcessorChainPixelTest {
String testId = "processData_withScaleToFitTransformation_rotate45"; String testId = "processData_withScaleToFitTransformation_rotate45";
setUpAndPrepareFirstFrame( setUpAndPrepareFirstFrame(
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
() -> new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build()); new ScaleToFitTransformation.Builder().setRotationDegrees(45).build());
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE45_SCALE_TO_FIT_PNG_ASSET_PATH); Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE45_SCALE_TO_FIT_PNG_ASSET_PATH);
Bitmap actualBitmap = processFirstFrameAndEnd(); Bitmap actualBitmap = processFirstFrameAndEnd();
......
...@@ -35,7 +35,7 @@ import org.junit.Test; ...@@ -35,7 +35,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** /**
* Pixel test for frame processing via {@link PresentationFrameProcessor}. * Pixel test for frame processing via {@link Presentation}.
* *
* <p>Expected images are taken from an emulator, so tests on different emulators or physical * <p>Expected images are taken from an emulator, so tests on different emulators or physical
* devices may fail. To test on other devices, please increase the {@link * devices may fail. To test on other devices, please increase the {@link
...@@ -43,7 +43,7 @@ import org.junit.runner.RunWith; ...@@ -43,7 +43,7 @@ import org.junit.runner.RunWith;
* as recommended in {@link FrameProcessorChainPixelTest}. * as recommended in {@link FrameProcessorChainPixelTest}.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class PresentationFrameProcessorPixelTest { public final class PresentationPixelTest {
public static final String ORIGINAL_PNG_ASSET_PATH = public static final String ORIGINAL_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/original.png"; "media/bitmap/sample_mp4_first_frame/original.png";
public static final String CROP_SMALLER_PNG_ASSET_PATH = public static final String CROP_SMALLER_PNG_ASSET_PATH =
...@@ -97,7 +97,7 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -97,7 +97,7 @@ public final class PresentationFrameProcessorPixelTest {
@Test @Test
public void drawFrame_noEdits_producesExpectedOutput() throws Exception { public void drawFrame_noEdits_producesExpectedOutput() throws Exception {
String testId = "drawFrame_noEdits"; String testId = "drawFrame_noEdits";
presentationFrameProcessor = new PresentationFrameProcessor.Builder().build(); presentationFrameProcessor = new Presentation.Builder().build().toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -122,9 +122,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -122,9 +122,10 @@ public final class PresentationFrameProcessorPixelTest {
public void drawFrame_cropSmaller_producesExpectedOutput() throws Exception { public void drawFrame_cropSmaller_producesExpectedOutput() throws Exception {
String testId = "drawFrame_cropSmaller"; String testId = "drawFrame_cropSmaller";
GlFrameProcessor presentationFrameProcessor = GlFrameProcessor presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setCrop(/* left= */ -.9f, /* right= */ .1f, /* bottom= */ -1f, /* top= */ .5f) .setCrop(/* left= */ -.9f, /* right= */ .1f, /* bottom= */ -1f, /* top= */ .5f)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -149,9 +150,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -149,9 +150,10 @@ public final class PresentationFrameProcessorPixelTest {
public void drawFrame_cropLarger_producesExpectedOutput() throws Exception { public void drawFrame_cropLarger_producesExpectedOutput() throws Exception {
String testId = "drawFrame_cropSmaller"; String testId = "drawFrame_cropSmaller";
GlFrameProcessor presentationFrameProcessor = GlFrameProcessor presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setCrop(/* left= */ -2f, /* right= */ 2f, /* bottom= */ -1f, /* top= */ 2f) .setCrop(/* left= */ -2f, /* right= */ 2f, /* bottom= */ -1f, /* top= */ 2f)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -177,9 +179,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -177,9 +179,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_scaleToFit_narrow"; String testId = "drawFrame_changeAspectRatio_scaleToFit_narrow";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT) .setAspectRatio(1f, Presentation.LAYOUT_SCALE_TO_FIT)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -206,9 +209,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -206,9 +209,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_scaleToFit_wide"; String testId = "drawFrame_changeAspectRatio_scaleToFit_wide";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT) .setAspectRatio(2f, Presentation.LAYOUT_SCALE_TO_FIT)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -235,9 +239,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -235,9 +239,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_narrow"; String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_narrow";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT_WITH_CROP) .setAspectRatio(1f, Presentation.LAYOUT_SCALE_TO_FIT_WITH_CROP)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -264,9 +269,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -264,9 +269,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_wide"; String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_wide";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT_WITH_CROP) .setAspectRatio(2f, Presentation.LAYOUT_SCALE_TO_FIT_WITH_CROP)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -293,9 +299,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -293,9 +299,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_stretchToFit_narrow"; String testId = "drawFrame_changeAspectRatio_stretchToFit_narrow";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_STRETCH_TO_FIT) .setAspectRatio(1f, Presentation.LAYOUT_STRETCH_TO_FIT)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -322,9 +329,10 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -322,9 +329,10 @@ public final class PresentationFrameProcessorPixelTest {
throws Exception { throws Exception {
String testId = "drawFrame_changeAspectRatio_stretchToFit_wide"; String testId = "drawFrame_changeAspectRatio_stretchToFit_wide";
presentationFrameProcessor = presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_STRETCH_TO_FIT) .setAspectRatio(2f, Presentation.LAYOUT_STRETCH_TO_FIT)
.build(); .build()
.toGlFrameProcessor();
presentationFrameProcessor.initialize( presentationFrameProcessor.initialize(
getApplicationContext(), inputTexId, inputWidth, inputHeight); getApplicationContext(), inputTexId, inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize(); Size outputSize = presentationFrameProcessor.getOutputSize();
...@@ -346,7 +354,7 @@ public final class PresentationFrameProcessorPixelTest { ...@@ -346,7 +354,7 @@ public final class PresentationFrameProcessorPixelTest {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE); assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
} }
private void setupOutputTexture(int outputWidth, int outputHeight) throws IOException { private void setupOutputTexture(int outputWidth, int outputHeight) {
outputTexId = GlUtil.createTexture(outputWidth, outputHeight); outputTexId = GlUtil.createTexture(outputWidth, outputHeight);
int frameBuffer = GlUtil.createFboForTexture(outputTexId); int frameBuffer = GlUtil.createFboForTexture(outputTexId);
GlUtil.focusFramebuffer( GlUtil.focusFramebuffer(
......
...@@ -20,23 +20,22 @@ import static com.google.android.exoplayer2.util.Assertions.checkState; ...@@ -20,23 +20,22 @@ import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.graphics.Matrix;
import android.util.Size; import android.util.Size;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Copies frames from a texture and applies {@link Format#rotationDegrees} for encoder * Specifies a {@link Format#rotationDegrees} to apply to each frame for encoder compatibility, if
* compatibility, if needed. * needed.
* *
* <p>Encoders commonly support higher maximum widths than maximum heights. This may rotate the * <p>Encoders commonly support higher maximum widths than maximum heights. This may rotate the
* decoded frame before encoding, so the encoded frame's width >= height, and set {@link * decoded frame before encoding, so the encoded frame's width >= height, and set {@link
* Format#rotationDegrees} to ensure the frame is displayed in the correct orientation. * Format#rotationDegrees} to ensure the frame is displayed in the correct orientation.
*/ */
/* package */ class EncoderCompatibilityFrameProcessor implements GlFrameProcessor { /* package */ class EncoderCompatibilityTransformation implements MatrixTransformation {
// TODO(b/218488308): Allow reconfiguration of the output size, as encoders may not support the // TODO(b/218488308): Allow reconfiguration of the output size, as encoders may not support the
// requested output resolution. // requested output resolution.
...@@ -45,26 +44,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -45,26 +44,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private int outputRotationDegrees; private int outputRotationDegrees;
private @MonotonicNonNull ScaleToFitFrameProcessor rotateFrameProcessor; private @MonotonicNonNull Matrix transformationMatrix;
/** Creates a new instance. */ /** Creates a new instance. */
/* package */ EncoderCompatibilityFrameProcessor() { public EncoderCompatibilityTransformation() {
outputRotationDegrees = C.LENGTH_UNSET; outputRotationDegrees = C.LENGTH_UNSET;
} }
@Override @Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) public Size configure(int inputWidth, int inputHeight) {
throws IOException { checkArgument(inputWidth > 0, "inputWidth must be positive");
configureOutputSizeAndRotation(inputWidth, inputHeight); checkArgument(inputHeight > 0, "inputHeight must be positive");
rotateFrameProcessor =
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(outputRotationDegrees).build(); transformationMatrix = new Matrix();
rotateFrameProcessor.initialize(context, inputTexId, inputWidth, inputHeight); if (inputHeight > inputWidth) {
outputRotationDegrees = 90;
transformationMatrix.postRotate(outputRotationDegrees);
return new Size(inputHeight, inputWidth);
} else {
outputRotationDegrees = 0;
return new Size(inputWidth, inputHeight);
}
} }
@Override @Override
public Size getOutputSize() { public Matrix getMatrix(long presentationTimeUs) {
return checkStateNotNull(rotateFrameProcessor).getOutputSize(); return checkStateNotNull(transformationMatrix, "configure must be called first");
} }
/** /**
...@@ -78,28 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -78,28 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public int getOutputRotationDegrees() { public int getOutputRotationDegrees() {
checkState( checkState(
outputRotationDegrees != C.LENGTH_UNSET, outputRotationDegrees != C.LENGTH_UNSET,
"configureOutputSizeAndTransformationMatrix must be called before" "configure must be called before getOutputRotationDegrees");
+ " getOutputRotationDegrees");
return outputRotationDegrees; return outputRotationDegrees;
} }
@Override
public void drawFrame(long presentationTimeUs) {
checkStateNotNull(rotateFrameProcessor).drawFrame(presentationTimeUs);
}
@Override
public void release() {
if (rotateFrameProcessor != null) {
rotateFrameProcessor.release();
}
}
@VisibleForTesting // Allows robolectric testing of output size calculation without OpenGL.
/* package */ void configureOutputSizeAndRotation(int inputWidth, int inputHeight) {
checkArgument(inputWidth > 0, "inputWidth must be positive");
checkArgument(inputHeight > 0, "inputHeight must be positive");
outputRotationDegrees = (inputHeight > inputWidth) ? 90 : 0;
}
} }
...@@ -190,14 +190,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -190,14 +190,16 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// Scale to expand the frame to apply the pixelWidthHeightRatio. // Scale to expand the frame to apply the pixelWidthHeightRatio.
if (pixelWidthHeightRatio > 1f) { if (pixelWidthHeightRatio > 1f) {
frameProcessors.add( frameProcessors.add(
new ScaleToFitFrameProcessor.Builder() new ScaleToFitTransformation.Builder()
.setScale(/* scaleX= */ pixelWidthHeightRatio, /* scaleY= */ 1f) .setScale(/* scaleX= */ pixelWidthHeightRatio, /* scaleY= */ 1f)
.build()); .build()
.toGlFrameProcessor());
} else if (pixelWidthHeightRatio < 1f) { } else if (pixelWidthHeightRatio < 1f) {
frameProcessors.add( frameProcessors.add(
new ScaleToFitFrameProcessor.Builder() new ScaleToFitTransformation.Builder()
.setScale(/* scaleX= */ 1f, /* scaleY= */ 1f / pixelWidthHeightRatio) .setScale(/* scaleX= */ 1f, /* scaleY= */ 1f / pixelWidthHeightRatio)
.build()); .build()
.toGlFrameProcessor());
} }
for (int i = 0; i < effects.size(); i++) { for (int i = 0; i < effects.size(); i++) {
......
...@@ -31,15 +31,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -31,15 +31,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Applies a transformation matrix in the vertex shader, and copies input pixels into an output * Applies a transformation matrix in the vertex shader, and copies input pixels into an output
* frame based on their locations after applying this matrix. * frame based on their locations after applying this matrix.
* *
* <p>Operations are done on normalized device coordinates (-1 to 1 on x and y axes). No automatic * <p>Operations are done on normalized device coordinates (-1 to 1 on x and y axes).
* adjustments (like done in {@link ScaleToFitFrameProcessor}) are applied on the transformation.
* *
* <p>The background color of the output frame will be black. * <p>The background color of the output frame will be black.
*/ */
// TODO(b/227625423): Compose multiple transformation matrices in a single shader with clipping // TODO(b/227625423): Compose multiple transformation matrices in a single shader with clipping
// after each matrix. // after each matrix.
@SuppressWarnings("FunctionalInterfaceClash") // b/228192298 @SuppressWarnings("FunctionalInterfaceClash") // b/228192298
public final class MatrixTransformationFrameProcessor implements GlFrameProcessor { /* package */ final class MatrixTransformationFrameProcessor implements GlFrameProcessor {
static { static {
GlUtil.glAssertionsEnabled = true; GlUtil.glAssertionsEnabled = true;
......
...@@ -21,34 +21,28 @@ import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; ...@@ -21,34 +21,28 @@ import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.util.Size; import android.util.Size;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Controls how a frame is presented, by copying input pixels into an output frame, with options to * Controls how a frame is presented with options to set the output resolution, crop the input, and
* set the output resolution, crop the input, and choose how to map the input pixels onto the output * choose how to map the input pixels onto the output frame geometry (for example, by stretching the
* frame geometry (for example, by stretching the input frame to match the specified output frame, * input frame to match the specified output frame, or fitting the input frame using letterboxing).
* or fitting the input frame using letterboxing).
* *
* <p>Cropping or aspect ratio is applied before setting resolution. * <p>Cropping or aspect ratio is applied before setting resolution.
* *
* <p>The background color of the output frame will be black. * <p>The background color of the output frame will be black.
*/ */
// TODO(b/227625423): Implement MatrixTransformation instead of wrapping public final class Presentation implements MatrixTransformation {
// MatrixTransformationFrameProcessor.
public final class PresentationFrameProcessor implements GlFrameProcessor {
/** /**
* Strategies controlling the layout of input pixels in the output frame. * Strategies controlling the layout of input pixels in the output frame.
* *
...@@ -106,7 +100,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -106,7 +100,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
*/ */
public static final int LAYOUT_STRETCH_TO_FIT = 2; public static final int LAYOUT_STRETCH_TO_FIT = 2;
/** A builder for {@link PresentationFrameProcessor} instances. */ /** A builder for {@link Presentation} instances. */
public static final class Builder { public static final class Builder {
// Optional fields. // Optional fields.
...@@ -159,7 +153,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -159,7 +153,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
* applied after cropping changes. * applied after cropping changes.
* *
* <p>Only one of {@code setCrop} or {@link #setAspectRatio(float, int)} can be called for one * <p>Only one of {@code setCrop} or {@link #setAspectRatio(float, int)} can be called for one
* {@link PresentationFrameProcessor}. * {@link Presentation}.
* *
* @param left The left edge of the output frame, in NDC. Must be less than {@code right}. * @param left The left edge of the output frame, in NDC. Must be less than {@code right}.
* @param right The right edge of the output frame, in NDC. Must be greater than {@code left}. * @param right The right edge of the output frame, in NDC. Must be greater than {@code left}.
...@@ -194,7 +188,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -194,7 +188,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
* applied after aspect ratio changes. * applied after aspect ratio changes.
* *
* <p>Only one of {@link #setCrop(float, float, float, float)} or {@code setAspectRatio} can be * <p>Only one of {@link #setCrop(float, float, float, float)} or {@code setAspectRatio} can be
* called for one {@link PresentationFrameProcessor}. * called for one {@link Presentation}.
* *
* @param aspectRatio The aspect ratio (width/height ratio) of the output frame. Must be * @param aspectRatio The aspect ratio (width/height ratio) of the output frame. Must be
* positive. * positive.
...@@ -215,8 +209,8 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -215,8 +209,8 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
return this; return this;
} }
public PresentationFrameProcessor build() { public Presentation build() {
return new PresentationFrameProcessor( return new Presentation(
heightPixels, cropLeft, cropRight, cropBottom, cropTop, aspectRatio, layout); heightPixels, cropLeft, cropRight, cropBottom, cropTop, aspectRatio, layout);
} }
} }
...@@ -235,12 +229,10 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -235,12 +229,10 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
private float outputWidth; private float outputWidth;
private float outputHeight; private float outputHeight;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull Matrix transformationMatrix; private @MonotonicNonNull Matrix transformationMatrix;
private @MonotonicNonNull MatrixTransformationFrameProcessor matrixTransformationFrameProcessor;
/** Creates a new instance. */ /** Creates a new instance. */
private PresentationFrameProcessor( private Presentation(
int requestedHeightPixels, int requestedHeightPixels,
float cropLeft, float cropLeft,
float cropRight, float cropRight,
...@@ -262,39 +254,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -262,39 +254,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
} }
@Override @Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) public Size configure(int inputWidth, int inputHeight) {
throws IOException {
configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
matrixTransformationFrameProcessor =
new MatrixTransformationFrameProcessor(
/* matrixTransformation= */ (long presentationTimeUs) ->
checkStateNotNull(transformationMatrix));
matrixTransformationFrameProcessor.initialize(context, inputTexId, inputWidth, inputHeight);
}
@Override
public Size getOutputSize() {
checkStateNotNull(
outputSize,
"configureOutputSizeAndTransformationMatrix must be called before getOutputSize");
return outputSize;
}
@Override
public void drawFrame(long presentationTimeUs) {
checkStateNotNull(matrixTransformationFrameProcessor).drawFrame(presentationTimeUs);
}
@Override
public void release() {
if (matrixTransformationFrameProcessor != null) {
matrixTransformationFrameProcessor.release();
}
}
@EnsuresNonNull("transformationMatrix")
@VisibleForTesting // Allows robolectric testing of output size calculation without OpenGL.
/* package */ void configureOutputSizeAndTransformationMatrix(int inputWidth, int inputHeight) {
checkArgument(inputWidth > 0, "inputWidth must be positive"); checkArgument(inputWidth > 0, "inputWidth must be positive");
checkArgument(inputHeight > 0, "inputHeight must be positive"); checkArgument(inputHeight > 0, "inputHeight must be positive");
...@@ -316,7 +276,12 @@ public final class PresentationFrameProcessor implements GlFrameProcessor { ...@@ -316,7 +276,12 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
outputWidth = requestedHeightPixels * outputWidth / outputHeight; outputWidth = requestedHeightPixels * outputWidth / outputHeight;
outputHeight = requestedHeightPixels; outputHeight = requestedHeightPixels;
} }
outputSize = new Size(Math.round(outputWidth), Math.round(outputHeight)); return new Size(Math.round(outputWidth), Math.round(outputHeight));
}
@Override
public Matrix getMatrix(long presentationTimeUs) {
return checkStateNotNull(transformationMatrix, "configure must be called first");
} }
@RequiresNonNull("transformationMatrix") @RequiresNonNull("transformationMatrix")
......
...@@ -20,28 +20,22 @@ import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; ...@@ -20,28 +20,22 @@ import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
import android.content.Context;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.util.Size; import android.util.Size;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Applies a simple rotation and/or scale in the vertex shader. * Specifies a simple rotation and/or scale to apply in the vertex shader.
* *
* <p>All input frames' pixels will be preserved and copied into an output frame, potentially * <p>All input frames' pixels will be preserved and copied into an output frame, potentially
* changing the width and height of the frame by scaling dimensions to fit. * changing the width and height of the frame by scaling dimensions to fit.
* *
* <p>The background color of the output frame will be black. * <p>The background color of the output frame will be black.
*/ */
// TODO(b/227625423): Implement MatrixTransformation instead of wrapping public final class ScaleToFitTransformation implements MatrixTransformation {
// MatrixTransformationFrameProcessor.
public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
/** A builder for {@link ScaleToFitFrameProcessor} instances. */ /** A builder for {@link ScaleToFitTransformation} instances. */
public static final class Builder { public static final class Builder {
// Optional fields. // Optional fields.
...@@ -84,8 +78,8 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor { ...@@ -84,8 +78,8 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
return this; return this;
} }
public ScaleToFitFrameProcessor build() { public ScaleToFitTransformation build() {
return new ScaleToFitFrameProcessor(scaleX, scaleY, rotationDegrees); return new ScaleToFitTransformation(scaleX, scaleY, rotationDegrees);
} }
} }
...@@ -94,9 +88,6 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor { ...@@ -94,9 +88,6 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
} }
private final Matrix transformationMatrix; private final Matrix transformationMatrix;
private @MonotonicNonNull MatrixTransformationFrameProcessor matrixTransformationFrameProcessor;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull Matrix adjustedTransformationMatrix; private @MonotonicNonNull Matrix adjustedTransformationMatrix;
/** /**
...@@ -106,51 +97,21 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor { ...@@ -106,51 +97,21 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
* @param scaleY The multiplier by which the frame will scale vertically, along the y-axis. * @param scaleY The multiplier by which the frame will scale vertically, along the y-axis.
* @param rotationDegrees How much to rotate the frame counterclockwise, in degrees. * @param rotationDegrees How much to rotate the frame counterclockwise, in degrees.
*/ */
private ScaleToFitFrameProcessor(float scaleX, float scaleY, float rotationDegrees) { private ScaleToFitTransformation(float scaleX, float scaleY, float rotationDegrees) {
this.transformationMatrix = new Matrix(); this.transformationMatrix = new Matrix();
this.transformationMatrix.postScale(scaleX, scaleY); this.transformationMatrix.postScale(scaleX, scaleY);
this.transformationMatrix.postRotate(rotationDegrees); this.transformationMatrix.postRotate(rotationDegrees);
} }
@Override @Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) public Size configure(int inputWidth, int inputHeight) {
throws IOException {
configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
matrixTransformationFrameProcessor =
new MatrixTransformationFrameProcessor(
/* matrixTransformation= */ (long presentationTimeUs) ->
checkStateNotNull(adjustedTransformationMatrix));
matrixTransformationFrameProcessor.initialize(context, inputTexId, inputWidth, inputHeight);
}
@Override
public Size getOutputSize() {
return checkStateNotNull(outputSize);
}
@Override
public void drawFrame(long presentationTimeUs) {
checkStateNotNull(matrixTransformationFrameProcessor).drawFrame(presentationTimeUs);
}
@Override
public void release() {
if (matrixTransformationFrameProcessor != null) {
matrixTransformationFrameProcessor.release();
}
}
@EnsuresNonNull("adjustedTransformationMatrix")
@VisibleForTesting // Allows robolectric testing of output size calculation without OpenGL.
/* package */ void configureOutputSizeAndTransformationMatrix(int inputWidth, int inputHeight) {
checkArgument(inputWidth > 0, "inputWidth must be positive"); checkArgument(inputWidth > 0, "inputWidth must be positive");
checkArgument(inputHeight > 0, "inputHeight must be positive"); checkArgument(inputHeight > 0, "inputHeight must be positive");
adjustedTransformationMatrix = new Matrix(transformationMatrix); adjustedTransformationMatrix = new Matrix(transformationMatrix);
if (transformationMatrix.isIdentity()) { if (transformationMatrix.isIdentity()) {
outputSize = new Size(inputWidth, inputHeight); return new Size(inputWidth, inputHeight);
return;
} }
float inputAspectRatio = (float) inputWidth / inputHeight; float inputAspectRatio = (float) inputWidth / inputHeight;
...@@ -179,6 +140,11 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor { ...@@ -179,6 +140,11 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
float scaleX = (maxX - minX) / GlUtil.LENGTH_NDC; float scaleX = (maxX - minX) / GlUtil.LENGTH_NDC;
float scaleY = (maxY - minY) / GlUtil.LENGTH_NDC; float scaleY = (maxY - minY) / GlUtil.LENGTH_NDC;
adjustedTransformationMatrix.postScale(1f / scaleX, 1f / scaleY); adjustedTransformationMatrix.postScale(1f / scaleX, 1f / scaleY);
outputSize = new Size(Math.round(inputWidth * scaleX), Math.round(inputHeight * scaleY)); return new Size(Math.round(inputWidth * scaleX), Math.round(inputHeight * scaleY));
}
@Override
public Matrix getMatrix(long presentationTimeUs) {
return checkStateNotNull(adjustedTransformationMatrix, "configure must be called first");
} }
} }
...@@ -74,22 +74,18 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -74,22 +74,18 @@ import org.checkerframework.dataflow.qual.Pure;
|| transformationRequest.scaleY != 1f || transformationRequest.scaleY != 1f
|| transformationRequest.rotationDegrees != 0f) { || transformationRequest.rotationDegrees != 0f) {
effectsListBuilder.add( effectsListBuilder.add(
() -> new ScaleToFitTransformation.Builder()
new ScaleToFitFrameProcessor.Builder()
.setScale(transformationRequest.scaleX, transformationRequest.scaleY) .setScale(transformationRequest.scaleX, transformationRequest.scaleY)
.setRotationDegrees(transformationRequest.rotationDegrees) .setRotationDegrees(transformationRequest.rotationDegrees)
.build()); .build());
} }
if (transformationRequest.outputHeight != C.LENGTH_UNSET) { if (transformationRequest.outputHeight != C.LENGTH_UNSET) {
effectsListBuilder.add( effectsListBuilder.add(
() -> new Presentation.Builder().setResolution(transformationRequest.outputHeight).build());
new PresentationFrameProcessor.Builder()
.setResolution(transformationRequest.outputHeight)
.build());
} }
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor = EncoderCompatibilityTransformation encoderCompatibilityTransformation =
new EncoderCompatibilityFrameProcessor(); new EncoderCompatibilityTransformation();
effectsListBuilder.add(() -> encoderCompatibilityFrameProcessor); effectsListBuilder.add(encoderCompatibilityTransformation);
frameProcessorChain = frameProcessorChain =
FrameProcessorChain.create( FrameProcessorChain.create(
context, context,
...@@ -99,7 +95,7 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -99,7 +95,7 @@ import org.checkerframework.dataflow.qual.Pure;
effectsListBuilder.build(), effectsListBuilder.build(),
transformationRequest.enableHdrEditing); transformationRequest.enableHdrEditing);
Size requestedEncoderSize = frameProcessorChain.getOutputSize(); Size requestedEncoderSize = frameProcessorChain.getOutputSize();
outputRotationDegrees = encoderCompatibilityFrameProcessor.getOutputRotationDegrees(); outputRotationDegrees = encoderCompatibilityTransformation.getOutputRotationDegrees();
Format requestedEncoderFormat = Format requestedEncoderFormat =
new Format.Builder() new Format.Builder()
......
...@@ -18,56 +18,63 @@ package com.google.android.exoplayer2.transformer; ...@@ -18,56 +18,63 @@ package com.google.android.exoplayer2.transformer;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import android.util.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** Unit tests for {@link EncoderCompatibilityFrameProcessor}. */ /** Unit tests for {@link EncoderCompatibilityTransformation}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class EncoderCompatibilityFrameProcessorTest { public final class EncoderCompatibilityTransformationTest {
@Test @Test
public void getOutputSize_noEditsLandscape_leavesOrientationUnchanged() { public void configure_noEditsLandscape_leavesOrientationUnchanged() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor = EncoderCompatibilityTransformation encoderCompatibilityTransformation =
new EncoderCompatibilityFrameProcessor(); new EncoderCompatibilityTransformation();
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight); Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(0); assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(0);
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_noEditsSquare_leavesOrientationUnchanged() { public void configure_noEditsSquare_leavesOrientationUnchanged() {
int inputWidth = 150; int inputWidth = 150;
int inputHeight = 150; int inputHeight = 150;
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor = EncoderCompatibilityTransformation encoderCompatibilityTransformation =
new EncoderCompatibilityFrameProcessor(); new EncoderCompatibilityTransformation();
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight); Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(0); assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(0);
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_noEditsPortrait_flipsOrientation() { public void configure_noEditsPortrait_flipsOrientation() {
int inputWidth = 150; int inputWidth = 150;
int inputHeight = 200; int inputHeight = 200;
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor = EncoderCompatibilityTransformation encoderCompatibilityTransformation =
new EncoderCompatibilityFrameProcessor(); new EncoderCompatibilityTransformation();
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight); Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(90); assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(90);
assertThat(outputSize.getWidth()).isEqualTo(inputHeight);
assertThat(outputSize.getHeight()).isEqualTo(inputWidth);
} }
@Test @Test
public void getOutputRotationDegreesBeforeConfigure_throwsIllegalStateException() { public void getOutputRotationDegreesBeforeConfigure_throwsIllegalStateException() {
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor = EncoderCompatibilityTransformation encoderCompatibilityTransformation =
new EncoderCompatibilityFrameProcessor(); new EncoderCompatibilityTransformation();
// configureOutputSize not called before getOutputRotationDegrees. // configure not called before getOutputRotationDegrees.
assertThrows( assertThrows(
IllegalStateException.class, encoderCompatibilityFrameProcessor::getOutputRotationDegrees); IllegalStateException.class, encoderCompatibilityTransformation::getOutputRotationDegrees);
} }
} }
...@@ -25,55 +25,49 @@ import org.junit.Test; ...@@ -25,55 +25,49 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** /**
* Unit tests for {@link PresentationFrameProcessor}. * Unit tests for {@link Presentation}.
* *
* <p>See {@code PresentationFrameProcessorPixelTest} for pixel tests testing {@link * <p>See {@code PresentationFrameProcessorPixelTest} for pixel tests testing {@link Presentation}.
* PresentationFrameProcessor}.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class PresentationFrameProcessorTest { public final class PresentationTest {
@Test @Test
public void getOutputSize_noEdits_leavesFramesUnchanged() { public void configure_noEdits_leavesFramesUnchanged() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation = new Presentation.Builder().build();
new PresentationFrameProcessor.Builder().build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_setResolution_changesDimensions() { public void configure_setResolution_changesDimensions() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
int requestedHeight = 300; int requestedHeight = 300;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation = new Presentation.Builder().setResolution(requestedHeight).build();
new PresentationFrameProcessor.Builder().setResolution(requestedHeight).build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(requestedHeight * inputWidth / inputHeight); assertThat(outputSize.getWidth()).isEqualTo(requestedHeight * inputWidth / inputHeight);
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight); assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
} }
@Test @Test
public void getOutputSize_setCrop_changesDimensions() { public void configure_setCrop_changesDimensions() {
int inputWidth = 300; int inputWidth = 300;
int inputHeight = 200; int inputHeight = 200;
float left = -.5f; float left = -.5f;
float right = .5f; float right = .5f;
float bottom = .5f; float bottom = .5f;
float top = 1f; float top = 1f;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation =
new PresentationFrameProcessor.Builder().setCrop(left, right, bottom, top).build(); new Presentation.Builder().setCrop(left, right, bottom, top).build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC); int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC); int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
...@@ -82,7 +76,7 @@ public final class PresentationFrameProcessorTest { ...@@ -82,7 +76,7 @@ public final class PresentationFrameProcessorTest {
} }
@Test @Test
public void getOutputSize_setCropAndSetResolution_changesDimensions() { public void configure_setCropAndSetResolution_changesDimensions() {
int inputWidth = 300; int inputWidth = 300;
int inputHeight = 200; int inputHeight = 200;
float left = -.5f; float left = -.5f;
...@@ -90,14 +84,13 @@ public final class PresentationFrameProcessorTest { ...@@ -90,14 +84,13 @@ public final class PresentationFrameProcessorTest {
float bottom = .5f; float bottom = .5f;
float top = 1f; float top = 1f;
int requestedHeight = 100; int requestedHeight = 100;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setCrop(left, right, bottom, top) .setCrop(left, right, bottom, top)
.setResolution(requestedHeight) .setResolution(requestedHeight)
.build(); .build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC); int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC); int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
...@@ -108,7 +101,7 @@ public final class PresentationFrameProcessorTest { ...@@ -108,7 +101,7 @@ public final class PresentationFrameProcessorTest {
} }
@Test @Test
public void getOutputSize_setResolutionAndCrop_changesDimensions() { public void configure_setResolutionAndCrop_changesDimensions() {
int inputWidth = 300; int inputWidth = 300;
int inputHeight = 200; int inputHeight = 200;
float left = -.5f; float left = -.5f;
...@@ -116,14 +109,13 @@ public final class PresentationFrameProcessorTest { ...@@ -116,14 +109,13 @@ public final class PresentationFrameProcessorTest {
float bottom = .5f; float bottom = .5f;
float top = 1f; float top = 1f;
int requestedHeight = 100; int requestedHeight = 100;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setResolution(requestedHeight) .setResolution(requestedHeight)
.setCrop(left, right, bottom, top) .setCrop(left, right, bottom, top)
.build(); .build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC); int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC); int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
...@@ -134,46 +126,44 @@ public final class PresentationFrameProcessorTest { ...@@ -134,46 +126,44 @@ public final class PresentationFrameProcessorTest {
} }
@Test @Test
public void getOutputSize_setAspectRatio_changesDimensions() { public void configure_setAspectRatio_changesDimensions() {
int inputWidth = 300; int inputWidth = 300;
int inputHeight = 200; int inputHeight = 200;
float aspectRatio = 2f; float aspectRatio = 2f;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(aspectRatio, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT) .setAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT)
.build(); .build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * inputHeight)); assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * inputHeight));
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_setAspectRatioAndResolution_changesDimensions() { public void configure_setAspectRatioAndResolution_changesDimensions() {
int inputWidth = 300; int inputWidth = 300;
int inputHeight = 200; int inputHeight = 200;
float aspectRatio = 2f; float aspectRatio = 2f;
int requestedHeight = 100; int requestedHeight = 100;
PresentationFrameProcessor presentationFrameProcessor = Presentation presentation =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(aspectRatio, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT) .setAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT)
.setResolution(requestedHeight) .setResolution(requestedHeight)
.build(); .build();
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
Size outputSize = presentationFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * requestedHeight)); assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * requestedHeight));
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight); assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
} }
@Test @Test
public void getOutputSize_setAspectRatioAndCrop_throwsIllegalStateException() { public void configure_setAspectRatioAndCrop_throwsIllegalStateException() {
PresentationFrameProcessor.Builder presentationFrameProcessor = Presentation.Builder presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setAspectRatio(/* aspectRatio= */ 2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT); .setAspectRatio(/* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT);
assertThrows( assertThrows(
IllegalStateException.class, IllegalStateException.class,
...@@ -183,15 +173,15 @@ public final class PresentationFrameProcessorTest { ...@@ -183,15 +173,15 @@ public final class PresentationFrameProcessorTest {
} }
@Test @Test
public void getOutputSize_setCropAndAspectRatio_throwsIllegalStateException() { public void configure_setCropAndAspectRatio_throwsIllegalStateException() {
PresentationFrameProcessor.Builder presentationFrameProcessor = Presentation.Builder presentationFrameProcessor =
new PresentationFrameProcessor.Builder() new Presentation.Builder()
.setCrop(/* left= */ -.5f, /* right= */ .5f, /* bottom= */ .5f, /* top= */ 1f); .setCrop(/* left= */ -.5f, /* right= */ .5f, /* bottom= */ .5f, /* top= */ 1f);
assertThrows( assertThrows(
IllegalStateException.class, IllegalStateException.class,
() -> () ->
presentationFrameProcessor.setAspectRatio( presentationFrameProcessor.setAspectRatio(
/* aspectRatio= */ 2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT)); /* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT));
} }
} }
...@@ -23,96 +23,90 @@ import org.junit.Test; ...@@ -23,96 +23,90 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** /**
* Unit tests for {@link ScaleToFitFrameProcessor}. * Unit tests for {@link ScaleToFitTransformation}.
* *
* <p>See {@code AdvancedFrameProcessorPixelTest} for pixel tests testing {@link * <p>See {@code MatrixTransformationFrameProcessorText} for pixel tests testing {@link
* AdvancedFrameProcessor} given a transformation matrix. * MatrixTransformationFrameProcessor} given a transformation matrix.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class ScaleToFitFrameProcessorTest { public final class ScaleToFitTransformationTest {
@Test @Test
public void getOutputSize_noEdits_leavesFramesUnchanged() { public void configure_noEdits_leavesFramesUnchanged() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder().build(); new ScaleToFitTransformation.Builder().build();
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_scaleNarrow_decreasesWidth() { public void configure_scaleNarrow_decreasesWidth() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder() new ScaleToFitTransformation.Builder()
.setScale(/* scaleX= */ .5f, /* scaleY= */ 1f) .setScale(/* scaleX= */ .5f, /* scaleY= */ 1f)
.build(); .build();
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(Math.round(inputWidth * .5f)); assertThat(outputSize.getWidth()).isEqualTo(Math.round(inputWidth * .5f));
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_scaleWide_increasesWidth() { public void configure_scaleWide_increasesWidth() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build(); new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build();
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth * 2); assertThat(outputSize.getWidth()).isEqualTo(inputWidth * 2);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@Test @Test
public void getOutputSize_scaleTall_increasesHeight() { public void configure_scaleTall_increasesHeight() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build(); new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build();
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight * 2); assertThat(outputSize.getHeight()).isEqualTo(inputHeight * 2);
} }
@Test @Test
public void getOutputSize_rotate90_swapsDimensions() { public void configure_rotate90_swapsDimensions() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(90).build(); new ScaleToFitTransformation.Builder().setRotationDegrees(90).build();
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(inputHeight); assertThat(outputSize.getWidth()).isEqualTo(inputHeight);
assertThat(outputSize.getHeight()).isEqualTo(inputWidth); assertThat(outputSize.getHeight()).isEqualTo(inputWidth);
} }
@Test @Test
public void getOutputSize_rotate45_changesDimensions() { public void configure_rotate45_changesDimensions() {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
ScaleToFitFrameProcessor scaleToFitFrameProcessor = ScaleToFitTransformation scaleToFitTransformation =
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build(); new ScaleToFitTransformation.Builder().setRotationDegrees(45).build();
long expectedOutputWidthHeight = 247; long expectedOutputWidthHeight = 247;
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
assertThat(outputSize.getWidth()).isEqualTo(expectedOutputWidthHeight); assertThat(outputSize.getWidth()).isEqualTo(expectedOutputWidthHeight);
assertThat(outputSize.getHeight()).isEqualTo(expectedOutputWidthHeight); assertThat(outputSize.getHeight()).isEqualTo(expectedOutputWidthHeight);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment