Use MatrixTransformation instead of wrapping its GlFrameProcssor.
ScaleToFitFrameProcessor, PresentationFrameProcessor, and EncoderCompatibilityFrameProcessor now each implement MatrixTransformation instead of wrapping MatrixTransformationFrameProcessor. PiperOrigin-RevId: 446480286
This commit is contained in:
parent
337b7f769d
commit
4b8ff72d30
@ -154,7 +154,7 @@ public final class FrameProcessorChainPixelTest {
|
||||
setUpAndPrepareFirstFrame(
|
||||
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
|
||||
(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 actualBitmap = processFirstFrameAndEnd();
|
||||
@ -176,7 +176,7 @@ public final class FrameProcessorChainPixelTest {
|
||||
translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0);
|
||||
setUpAndPrepareFirstFrame(
|
||||
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
|
||||
() -> new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build(),
|
||||
new ScaleToFitTransformation.Builder().setRotationDegrees(45).build(),
|
||||
(MatrixTransformation) (long presentationTimeUs) -> translateRightMatrix);
|
||||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ROTATE_THEN_TRANSLATE_PNG_ASSET_PATH);
|
||||
|
||||
@ -195,8 +195,7 @@ public final class FrameProcessorChainPixelTest {
|
||||
public void processData_withPresentation_setResolution_producesExpectedOutput() throws Exception {
|
||||
String testId = "processData_withPresentation_setResolution";
|
||||
setUpAndPrepareFirstFrame(
|
||||
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
|
||||
() -> new PresentationFrameProcessor.Builder().setResolution(480).build());
|
||||
DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO, new Presentation.Builder().setResolution(480).build());
|
||||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH);
|
||||
|
||||
Bitmap actualBitmap = processFirstFrameAndEnd();
|
||||
@ -216,7 +215,7 @@ public final class FrameProcessorChainPixelTest {
|
||||
String testId = "processData_withScaleToFitTransformation_rotate45";
|
||||
setUpAndPrepareFirstFrame(
|
||||
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 actualBitmap = processFirstFrameAndEnd();
|
||||
|
@ -35,7 +35,7 @@ import org.junit.Test;
|
||||
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
|
||||
* devices may fail. To test on other devices, please increase the {@link
|
||||
@ -43,7 +43,7 @@ import org.junit.runner.RunWith;
|
||||
* as recommended in {@link FrameProcessorChainPixelTest}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class PresentationFrameProcessorPixelTest {
|
||||
public final class PresentationPixelTest {
|
||||
public static final String ORIGINAL_PNG_ASSET_PATH =
|
||||
"media/bitmap/sample_mp4_first_frame/original.png";
|
||||
public static final String CROP_SMALLER_PNG_ASSET_PATH =
|
||||
@ -97,7 +97,7 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
@Test
|
||||
public void drawFrame_noEdits_producesExpectedOutput() throws Exception {
|
||||
String testId = "drawFrame_noEdits";
|
||||
presentationFrameProcessor = new PresentationFrameProcessor.Builder().build();
|
||||
presentationFrameProcessor = new Presentation.Builder().build().toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -122,9 +122,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
public void drawFrame_cropSmaller_producesExpectedOutput() throws Exception {
|
||||
String testId = "drawFrame_cropSmaller";
|
||||
GlFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
new Presentation.Builder()
|
||||
.setCrop(/* left= */ -.9f, /* right= */ .1f, /* bottom= */ -1f, /* top= */ .5f)
|
||||
.build();
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -149,9 +150,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
public void drawFrame_cropLarger_producesExpectedOutput() throws Exception {
|
||||
String testId = "drawFrame_cropSmaller";
|
||||
GlFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
new Presentation.Builder()
|
||||
.setCrop(/* left= */ -2f, /* right= */ 2f, /* bottom= */ -1f, /* top= */ 2f)
|
||||
.build();
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -177,9 +179,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_scaleToFit_narrow";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(1f, Presentation.LAYOUT_SCALE_TO_FIT)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -206,9 +209,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_scaleToFit_wide";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(2f, Presentation.LAYOUT_SCALE_TO_FIT)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -235,9 +239,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_narrow";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT_WITH_CROP)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(1f, Presentation.LAYOUT_SCALE_TO_FIT_WITH_CROP)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -264,9 +269,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_scaleToFitWithCrop_wide";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT_WITH_CROP)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(2f, Presentation.LAYOUT_SCALE_TO_FIT_WITH_CROP)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -293,9 +299,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_stretchToFit_narrow";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(1f, PresentationFrameProcessor.LAYOUT_STRETCH_TO_FIT)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(1f, Presentation.LAYOUT_STRETCH_TO_FIT)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -322,9 +329,10 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
throws Exception {
|
||||
String testId = "drawFrame_changeAspectRatio_stretchToFit_wide";
|
||||
presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(2f, PresentationFrameProcessor.LAYOUT_STRETCH_TO_FIT)
|
||||
.build();
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(2f, Presentation.LAYOUT_STRETCH_TO_FIT)
|
||||
.build()
|
||||
.toGlFrameProcessor();
|
||||
presentationFrameProcessor.initialize(
|
||||
getApplicationContext(), inputTexId, inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
@ -346,7 +354,7 @@ public final class PresentationFrameProcessorPixelTest {
|
||||
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);
|
||||
int frameBuffer = GlUtil.createFboForTexture(outputTexId);
|
||||
GlUtil.focusFramebuffer(
|
@ -20,23 +20,22 @@ import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import java.io.IOException;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Copies frames from a texture and applies {@link Format#rotationDegrees} for encoder
|
||||
* compatibility, if needed.
|
||||
* Specifies a {@link Format#rotationDegrees} to apply to each frame for encoder compatibility, if
|
||||
* needed.
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
// requested output resolution.
|
||||
|
||||
@ -45,26 +44,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
private int outputRotationDegrees;
|
||||
private @MonotonicNonNull ScaleToFitFrameProcessor rotateFrameProcessor;
|
||||
private @MonotonicNonNull Matrix transformationMatrix;
|
||||
|
||||
/** Creates a new instance. */
|
||||
/* package */ EncoderCompatibilityFrameProcessor() {
|
||||
|
||||
public EncoderCompatibilityTransformation() {
|
||||
outputRotationDegrees = C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
|
||||
throws IOException {
|
||||
configureOutputSizeAndRotation(inputWidth, inputHeight);
|
||||
rotateFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(outputRotationDegrees).build();
|
||||
rotateFrameProcessor.initialize(context, inputTexId, inputWidth, inputHeight);
|
||||
public Size configure(int inputWidth, int inputHeight) {
|
||||
checkArgument(inputWidth > 0, "inputWidth must be positive");
|
||||
checkArgument(inputHeight > 0, "inputHeight must be positive");
|
||||
|
||||
transformationMatrix = new Matrix();
|
||||
if (inputHeight > inputWidth) {
|
||||
outputRotationDegrees = 90;
|
||||
transformationMatrix.postRotate(outputRotationDegrees);
|
||||
return new Size(inputHeight, inputWidth);
|
||||
} else {
|
||||
outputRotationDegrees = 0;
|
||||
return new Size(inputWidth, inputHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size getOutputSize() {
|
||||
return checkStateNotNull(rotateFrameProcessor).getOutputSize();
|
||||
public Matrix getMatrix(long presentationTimeUs) {
|
||||
return checkStateNotNull(transformationMatrix, "configure must be called first");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,28 +83,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
public int getOutputRotationDegrees() {
|
||||
checkState(
|
||||
outputRotationDegrees != C.LENGTH_UNSET,
|
||||
"configureOutputSizeAndTransformationMatrix must be called before"
|
||||
+ " getOutputRotationDegrees");
|
||||
"configure must be called before getOutputRotationDegrees");
|
||||
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;
|
||||
// Scale to expand the frame to apply the pixelWidthHeightRatio.
|
||||
if (pixelWidthHeightRatio > 1f) {
|
||||
frameProcessors.add(
|
||||
new ScaleToFitFrameProcessor.Builder()
|
||||
new ScaleToFitTransformation.Builder()
|
||||
.setScale(/* scaleX= */ pixelWidthHeightRatio, /* scaleY= */ 1f)
|
||||
.build());
|
||||
.build()
|
||||
.toGlFrameProcessor());
|
||||
} else if (pixelWidthHeightRatio < 1f) {
|
||||
frameProcessors.add(
|
||||
new ScaleToFitFrameProcessor.Builder()
|
||||
new ScaleToFitTransformation.Builder()
|
||||
.setScale(/* scaleX= */ 1f, /* scaleY= */ 1f / pixelWidthHeightRatio)
|
||||
.build());
|
||||
.build()
|
||||
.toGlFrameProcessor());
|
||||
}
|
||||
|
||||
for (int i = 0; i < effects.size(); i++) {
|
||||
|
@ -32,8 +32,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* Applies a transformation matrix in the vertex shader, and copies input pixels into an output
|
||||
* 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
|
||||
* adjustments (like done in {@link ScaleToFitFrameProcessor}) are applied on the transformation.
|
||||
* <p>Operations are done on normalized device coordinates (-1 to 1 on x and y axes).
|
||||
*
|
||||
* <p>The background color of the output frame will be black.
|
||||
*/
|
||||
@ -41,7 +40,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
// after each matrix.
|
||||
@UnstableApi
|
||||
@SuppressWarnings("FunctionalInterfaceClash") // b/228192298
|
||||
public final class MatrixTransformationFrameProcessor implements GlFrameProcessor {
|
||||
/* package */ final class MatrixTransformationFrameProcessor implements GlFrameProcessor {
|
||||
|
||||
static {
|
||||
GlUtil.glAssertionsEnabled = true;
|
||||
|
@ -21,36 +21,30 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
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.RequiresNonNull;
|
||||
|
||||
/**
|
||||
* Controls how a frame is presented, by copying input pixels into an output frame, with options to
|
||||
* set the output resolution, crop the input, and choose how to map the input pixels onto the output
|
||||
* frame geometry (for example, by stretching the input frame to match the specified output frame,
|
||||
* or fitting the input frame using letterboxing).
|
||||
* Controls how a frame is presented with options to set the output resolution, crop the input, and
|
||||
* choose how to map the input pixels onto the output frame geometry (for example, by stretching the
|
||||
* input frame to match the specified output frame, or fitting the input frame using letterboxing).
|
||||
*
|
||||
* <p>Cropping or aspect ratio is applied before setting resolution.
|
||||
*
|
||||
* <p>The background color of the output frame will be black.
|
||||
*/
|
||||
@UnstableApi
|
||||
// TODO(b/227625423): Implement MatrixTransformation instead of wrapping
|
||||
// MatrixTransformationFrameProcessor.
|
||||
public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
public final class Presentation implements MatrixTransformation {
|
||||
|
||||
/**
|
||||
* Strategies controlling the layout of input pixels in the output frame.
|
||||
*
|
||||
@ -108,7 +102,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
*/
|
||||
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 {
|
||||
|
||||
// Optional fields.
|
||||
@ -161,7 +155,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
* applied after cropping changes.
|
||||
*
|
||||
* <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 right The right edge of the output frame, in NDC. Must be greater than {@code left}.
|
||||
@ -196,7 +190,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
* applied after aspect ratio changes.
|
||||
*
|
||||
* <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
|
||||
* positive.
|
||||
@ -217,8 +211,8 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PresentationFrameProcessor build() {
|
||||
return new PresentationFrameProcessor(
|
||||
public Presentation build() {
|
||||
return new Presentation(
|
||||
heightPixels, cropLeft, cropRight, cropBottom, cropTop, aspectRatio, layout);
|
||||
}
|
||||
}
|
||||
@ -237,12 +231,10 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
|
||||
private float outputWidth;
|
||||
private float outputHeight;
|
||||
private @MonotonicNonNull Size outputSize;
|
||||
private @MonotonicNonNull Matrix transformationMatrix;
|
||||
private @MonotonicNonNull MatrixTransformationFrameProcessor matrixTransformationFrameProcessor;
|
||||
|
||||
/** Creates a new instance. */
|
||||
private PresentationFrameProcessor(
|
||||
private Presentation(
|
||||
int requestedHeightPixels,
|
||||
float cropLeft,
|
||||
float cropRight,
|
||||
@ -264,39 +256,7 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Context context, int inputTexId, 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) {
|
||||
public Size configure(int inputWidth, int inputHeight) {
|
||||
checkArgument(inputWidth > 0, "inputWidth must be positive");
|
||||
checkArgument(inputHeight > 0, "inputHeight must be positive");
|
||||
|
||||
@ -318,7 +278,12 @@ public final class PresentationFrameProcessor implements GlFrameProcessor {
|
||||
outputWidth = requestedHeightPixels * outputWidth / outputHeight;
|
||||
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")
|
@ -20,18 +20,14 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.io.IOException;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
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
|
||||
* changing the width and height of the frame by scaling dimensions to fit.
|
||||
@ -39,11 +35,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* <p>The background color of the output frame will be black.
|
||||
*/
|
||||
@UnstableApi
|
||||
// TODO(b/227625423): Implement MatrixTransformation instead of wrapping
|
||||
// MatrixTransformationFrameProcessor.
|
||||
public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
|
||||
public final class ScaleToFitTransformation implements MatrixTransformation {
|
||||
|
||||
/** A builder for {@link ScaleToFitFrameProcessor} instances. */
|
||||
/** A builder for {@link ScaleToFitTransformation} instances. */
|
||||
public static final class Builder {
|
||||
|
||||
// Optional fields.
|
||||
@ -86,8 +80,8 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScaleToFitFrameProcessor build() {
|
||||
return new ScaleToFitFrameProcessor(scaleX, scaleY, rotationDegrees);
|
||||
public ScaleToFitTransformation build() {
|
||||
return new ScaleToFitTransformation(scaleX, scaleY, rotationDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,9 +90,6 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
|
||||
}
|
||||
|
||||
private final Matrix transformationMatrix;
|
||||
|
||||
private @MonotonicNonNull MatrixTransformationFrameProcessor matrixTransformationFrameProcessor;
|
||||
private @MonotonicNonNull Size outputSize;
|
||||
private @MonotonicNonNull Matrix adjustedTransformationMatrix;
|
||||
|
||||
/**
|
||||
@ -108,51 +99,21 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
|
||||
* @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.
|
||||
*/
|
||||
private ScaleToFitFrameProcessor(float scaleX, float scaleY, float rotationDegrees) {
|
||||
private ScaleToFitTransformation(float scaleX, float scaleY, float rotationDegrees) {
|
||||
this.transformationMatrix = new Matrix();
|
||||
this.transformationMatrix.postScale(scaleX, scaleY);
|
||||
this.transformationMatrix.postRotate(rotationDegrees);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Context context, int inputTexId, 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) {
|
||||
public Size configure(int inputWidth, int inputHeight) {
|
||||
checkArgument(inputWidth > 0, "inputWidth must be positive");
|
||||
checkArgument(inputHeight > 0, "inputHeight must be positive");
|
||||
|
||||
adjustedTransformationMatrix = new Matrix(transformationMatrix);
|
||||
|
||||
if (transformationMatrix.isIdentity()) {
|
||||
outputSize = new Size(inputWidth, inputHeight);
|
||||
return;
|
||||
return new Size(inputWidth, inputHeight);
|
||||
}
|
||||
|
||||
float inputAspectRatio = (float) inputWidth / inputHeight;
|
||||
@ -181,6 +142,11 @@ public final class ScaleToFitFrameProcessor implements GlFrameProcessor {
|
||||
float scaleX = (maxX - minX) / GlUtil.LENGTH_NDC;
|
||||
float scaleY = (maxY - minY) / GlUtil.LENGTH_NDC;
|
||||
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;
|
||||
|| transformationRequest.scaleY != 1f
|
||||
|| transformationRequest.rotationDegrees != 0f) {
|
||||
effectsListBuilder.add(
|
||||
() ->
|
||||
new ScaleToFitFrameProcessor.Builder()
|
||||
.setScale(transformationRequest.scaleX, transformationRequest.scaleY)
|
||||
.setRotationDegrees(transformationRequest.rotationDegrees)
|
||||
.build());
|
||||
new ScaleToFitTransformation.Builder()
|
||||
.setScale(transformationRequest.scaleX, transformationRequest.scaleY)
|
||||
.setRotationDegrees(transformationRequest.rotationDegrees)
|
||||
.build());
|
||||
}
|
||||
if (transformationRequest.outputHeight != C.LENGTH_UNSET) {
|
||||
effectsListBuilder.add(
|
||||
() ->
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setResolution(transformationRequest.outputHeight)
|
||||
.build());
|
||||
new Presentation.Builder().setResolution(transformationRequest.outputHeight).build());
|
||||
}
|
||||
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor =
|
||||
new EncoderCompatibilityFrameProcessor();
|
||||
effectsListBuilder.add(() -> encoderCompatibilityFrameProcessor);
|
||||
EncoderCompatibilityTransformation encoderCompatibilityTransformation =
|
||||
new EncoderCompatibilityTransformation();
|
||||
effectsListBuilder.add(encoderCompatibilityTransformation);
|
||||
frameProcessorChain =
|
||||
FrameProcessorChain.create(
|
||||
context,
|
||||
@ -99,7 +95,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
||||
effectsListBuilder.build(),
|
||||
transformationRequest.enableHdrEditing);
|
||||
Size requestedEncoderSize = frameProcessorChain.getOutputSize();
|
||||
outputRotationDegrees = encoderCompatibilityFrameProcessor.getOutputRotationDegrees();
|
||||
outputRotationDegrees = encoderCompatibilityTransformation.getOutputRotationDegrees();
|
||||
|
||||
Format requestedEncoderFormat =
|
||||
new Format.Builder()
|
||||
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link EncoderCompatibilityFrameProcessor}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class EncoderCompatibilityFrameProcessorTest {
|
||||
@Test
|
||||
public void getOutputSize_noEditsLandscape_leavesOrientationUnchanged() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor =
|
||||
new EncoderCompatibilityFrameProcessor();
|
||||
|
||||
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_noEditsSquare_leavesOrientationUnchanged() {
|
||||
int inputWidth = 150;
|
||||
int inputHeight = 150;
|
||||
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor =
|
||||
new EncoderCompatibilityFrameProcessor();
|
||||
|
||||
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_noEditsPortrait_flipsOrientation() {
|
||||
int inputWidth = 150;
|
||||
int inputHeight = 200;
|
||||
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor =
|
||||
new EncoderCompatibilityFrameProcessor();
|
||||
|
||||
encoderCompatibilityFrameProcessor.configureOutputSizeAndRotation(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityFrameProcessor.getOutputRotationDegrees()).isEqualTo(90);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputRotationDegreesBeforeConfigure_throwsIllegalStateException() {
|
||||
EncoderCompatibilityFrameProcessor encoderCompatibilityFrameProcessor =
|
||||
new EncoderCompatibilityFrameProcessor();
|
||||
|
||||
// configureOutputSize not called before getOutputRotationDegrees.
|
||||
assertThrows(
|
||||
IllegalStateException.class, encoderCompatibilityFrameProcessor::getOutputRotationDegrees);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.transformer;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.util.Size;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link EncoderCompatibilityTransformation}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class EncoderCompatibilityTransformationTest {
|
||||
@Test
|
||||
public void configure_noEditsLandscape_leavesOrientationUnchanged() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
EncoderCompatibilityTransformation encoderCompatibilityTransformation =
|
||||
new EncoderCompatibilityTransformation();
|
||||
|
||||
Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(0);
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configure_noEditsSquare_leavesOrientationUnchanged() {
|
||||
int inputWidth = 150;
|
||||
int inputHeight = 150;
|
||||
EncoderCompatibilityTransformation encoderCompatibilityTransformation =
|
||||
new EncoderCompatibilityTransformation();
|
||||
|
||||
Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(0);
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configure_noEditsPortrait_flipsOrientation() {
|
||||
int inputWidth = 150;
|
||||
int inputHeight = 200;
|
||||
EncoderCompatibilityTransformation encoderCompatibilityTransformation =
|
||||
new EncoderCompatibilityTransformation();
|
||||
|
||||
Size outputSize = encoderCompatibilityTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(encoderCompatibilityTransformation.getOutputRotationDegrees()).isEqualTo(90);
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputHeight);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputWidth);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputRotationDegreesBeforeConfigure_throwsIllegalStateException() {
|
||||
EncoderCompatibilityTransformation encoderCompatibilityTransformation =
|
||||
new EncoderCompatibilityTransformation();
|
||||
|
||||
// configure not called before getOutputRotationDegrees.
|
||||
assertThrows(
|
||||
IllegalStateException.class, encoderCompatibilityTransformation::getOutputRotationDegrees);
|
||||
}
|
||||
}
|
@ -25,55 +25,49 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PresentationFrameProcessor}.
|
||||
* Unit tests for {@link Presentation}.
|
||||
*
|
||||
* <p>See {@code PresentationFrameProcessorPixelTest} for pixel tests testing {@link
|
||||
* PresentationFrameProcessor}.
|
||||
* <p>See {@code PresentationFrameProcessorPixelTest} for pixel tests testing {@link Presentation}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class PresentationFrameProcessorTest {
|
||||
public final class PresentationTest {
|
||||
@Test
|
||||
public void getOutputSize_noEdits_leavesFramesUnchanged() {
|
||||
public void configure_noEdits_leavesFramesUnchanged() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder().build();
|
||||
Presentation presentation = new Presentation.Builder().build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setResolution_changesDimensions() {
|
||||
public void configure_setResolution_changesDimensions() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
int requestedHeight = 300;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder().setResolution(requestedHeight).build();
|
||||
Presentation presentation = new Presentation.Builder().setResolution(requestedHeight).build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(requestedHeight * inputWidth / inputHeight);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setCrop_changesDimensions() {
|
||||
public void configure_setCrop_changesDimensions() {
|
||||
int inputWidth = 300;
|
||||
int inputHeight = 200;
|
||||
float left = -.5f;
|
||||
float right = .5f;
|
||||
float bottom = .5f;
|
||||
float top = 1f;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder().setCrop(left, right, bottom, top).build();
|
||||
Presentation presentation =
|
||||
new Presentation.Builder().setCrop(left, right, bottom, top).build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
|
||||
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
|
||||
@ -82,7 +76,7 @@ public final class PresentationFrameProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setCropAndSetResolution_changesDimensions() {
|
||||
public void configure_setCropAndSetResolution_changesDimensions() {
|
||||
int inputWidth = 300;
|
||||
int inputHeight = 200;
|
||||
float left = -.5f;
|
||||
@ -90,14 +84,13 @@ public final class PresentationFrameProcessorTest {
|
||||
float bottom = .5f;
|
||||
float top = 1f;
|
||||
int requestedHeight = 100;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
Presentation presentation =
|
||||
new Presentation.Builder()
|
||||
.setCrop(left, right, bottom, top)
|
||||
.setResolution(requestedHeight)
|
||||
.build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
|
||||
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
|
||||
@ -108,7 +101,7 @@ public final class PresentationFrameProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setResolutionAndCrop_changesDimensions() {
|
||||
public void configure_setResolutionAndCrop_changesDimensions() {
|
||||
int inputWidth = 300;
|
||||
int inputHeight = 200;
|
||||
float left = -.5f;
|
||||
@ -116,14 +109,13 @@ public final class PresentationFrameProcessorTest {
|
||||
float bottom = .5f;
|
||||
float top = 1f;
|
||||
int requestedHeight = 100;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
Presentation presentation =
|
||||
new Presentation.Builder()
|
||||
.setResolution(requestedHeight)
|
||||
.setCrop(left, right, bottom, top)
|
||||
.build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
|
||||
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
|
||||
@ -134,46 +126,44 @@ public final class PresentationFrameProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setAspectRatio_changesDimensions() {
|
||||
public void configure_setAspectRatio_changesDimensions() {
|
||||
int inputWidth = 300;
|
||||
int inputHeight = 200;
|
||||
float aspectRatio = 2f;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(aspectRatio, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT)
|
||||
Presentation presentation =
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT)
|
||||
.build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * inputHeight));
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setAspectRatioAndResolution_changesDimensions() {
|
||||
public void configure_setAspectRatioAndResolution_changesDimensions() {
|
||||
int inputWidth = 300;
|
||||
int inputHeight = 200;
|
||||
float aspectRatio = 2f;
|
||||
int requestedHeight = 100;
|
||||
PresentationFrameProcessor presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(aspectRatio, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT)
|
||||
Presentation presentation =
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT)
|
||||
.setResolution(requestedHeight)
|
||||
.build();
|
||||
|
||||
presentationFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = presentationFrameProcessor.getOutputSize();
|
||||
Size outputSize = presentation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * requestedHeight));
|
||||
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setAspectRatioAndCrop_throwsIllegalStateException() {
|
||||
PresentationFrameProcessor.Builder presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
.setAspectRatio(/* aspectRatio= */ 2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT);
|
||||
public void configure_setAspectRatioAndCrop_throwsIllegalStateException() {
|
||||
Presentation.Builder presentationFrameProcessor =
|
||||
new Presentation.Builder()
|
||||
.setAspectRatio(/* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT);
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
@ -183,15 +173,15 @@ public final class PresentationFrameProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_setCropAndAspectRatio_throwsIllegalStateException() {
|
||||
PresentationFrameProcessor.Builder presentationFrameProcessor =
|
||||
new PresentationFrameProcessor.Builder()
|
||||
public void configure_setCropAndAspectRatio_throwsIllegalStateException() {
|
||||
Presentation.Builder presentationFrameProcessor =
|
||||
new Presentation.Builder()
|
||||
.setCrop(/* left= */ -.5f, /* right= */ .5f, /* bottom= */ .5f, /* top= */ 1f);
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
() ->
|
||||
presentationFrameProcessor.setAspectRatio(
|
||||
/* aspectRatio= */ 2f, PresentationFrameProcessor.LAYOUT_SCALE_TO_FIT));
|
||||
/* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT));
|
||||
}
|
||||
}
|
@ -23,96 +23,90 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScaleToFitFrameProcessor}.
|
||||
* Unit tests for {@link ScaleToFitTransformation}.
|
||||
*
|
||||
* <p>See {@code AdvancedFrameProcessorPixelTest} for pixel tests testing {@link
|
||||
* AdvancedFrameProcessor} given a transformation matrix.
|
||||
* <p>See {@code MatrixTransformationFrameProcessorText} for pixel tests testing {@link
|
||||
* MatrixTransformationFrameProcessor} given a transformation matrix.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class ScaleToFitFrameProcessorTest {
|
||||
public final class ScaleToFitTransformationTest {
|
||||
|
||||
@Test
|
||||
public void getOutputSize_noEdits_leavesFramesUnchanged() {
|
||||
public void configure_noEdits_leavesFramesUnchanged() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().build();
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder().build();
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_scaleNarrow_decreasesWidth() {
|
||||
public void configure_scaleNarrow_decreasesWidth() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder()
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder()
|
||||
.setScale(/* scaleX= */ .5f, /* scaleY= */ 1f)
|
||||
.build();
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(Math.round(inputWidth * .5f));
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_scaleWide_increasesWidth() {
|
||||
public void configure_scaleWide_increasesWidth() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build();
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build();
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth * 2);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_scaleTall_increasesHeight() {
|
||||
public void configure_scaleTall_increasesHeight() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build();
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build();
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputHeight * 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_rotate90_swapsDimensions() {
|
||||
public void configure_rotate90_swapsDimensions() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(90).build();
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder().setRotationDegrees(90).build();
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(inputHeight);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(inputWidth);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOutputSize_rotate45_changesDimensions() {
|
||||
public void configure_rotate45_changesDimensions() {
|
||||
int inputWidth = 200;
|
||||
int inputHeight = 150;
|
||||
ScaleToFitFrameProcessor scaleToFitFrameProcessor =
|
||||
new ScaleToFitFrameProcessor.Builder().setRotationDegrees(45).build();
|
||||
ScaleToFitTransformation scaleToFitTransformation =
|
||||
new ScaleToFitTransformation.Builder().setRotationDegrees(45).build();
|
||||
long expectedOutputWidthHeight = 247;
|
||||
|
||||
scaleToFitFrameProcessor.configureOutputSizeAndTransformationMatrix(inputWidth, inputHeight);
|
||||
Size outputSize = scaleToFitFrameProcessor.getOutputSize();
|
||||
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
|
||||
|
||||
assertThat(outputSize.getWidth()).isEqualTo(expectedOutputWidthHeight);
|
||||
assertThat(outputSize.getHeight()).isEqualTo(expectedOutputWidthHeight);
|
Loading…
x
Reference in New Issue
Block a user