diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/CropPixelTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/CropPixelTest.java new file mode 100644 index 0000000000..a7c7db9b0a --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/CropPixelTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2021 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 com.google.android.exoplayer2.transformer; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.android.exoplayer2.transformer.BitmapTestUtil.MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.Bitmap; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.util.Size; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.util.GlUtil; +import java.io.IOException; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Pixel test for texture processing via {@link Crop}. + * + *
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
+ * BitmapTestUtil#MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE} and/or inspect the saved output bitmaps
+ * as recommended in {@link FrameProcessorChainPixelTest}.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CropPixelTest {
+ 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 =
+ "media/bitmap/sample_mp4_first_frame/crop_smaller.png";
+ public static final String CROP_LARGER_PNG_ASSET_PATH =
+ "media/bitmap/sample_mp4_first_frame/crop_larger.png";
+
+ static {
+ GlUtil.glAssertionsEnabled = true;
+ }
+
+ private final Context context = getApplicationContext();
+ private final EGLDisplay eglDisplay = GlUtil.createEglDisplay();
+ private final EGLContext eglContext = GlUtil.createEglContext(eglDisplay);
+ private @MonotonicNonNull SingleFrameGlTextureProcessor cropTextureProcessor;
+ private @MonotonicNonNull EGLSurface placeholderEglSurface;
+ private int inputTexId;
+ private int outputTexId;
+ private int inputWidth;
+ private int inputHeight;
+
+ @Before
+ public void createTextures() throws IOException {
+ Bitmap inputBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
+ inputWidth = inputBitmap.getWidth();
+ inputHeight = inputBitmap.getHeight();
+ placeholderEglSurface = GlUtil.createPlaceholderEglSurface(eglDisplay);
+ GlUtil.focusEglSurface(eglDisplay, eglContext, placeholderEglSurface, inputWidth, inputHeight);
+ inputTexId = BitmapTestUtil.createGlTextureFromBitmap(inputBitmap);
+ }
+
+ @After
+ public void release() {
+ if (cropTextureProcessor != null) {
+ cropTextureProcessor.release();
+ }
+ GlUtil.destroyEglContext(eglDisplay, eglContext);
+ }
+
+ @Test
+ public void drawFrame_noEdits_producesExpectedOutput() throws Exception {
+ String testId = "drawFrame_noEdits";
+ cropTextureProcessor =
+ new Crop(/* left= */ -1, /* right= */ 1, /* bottom= */ -1, /* top= */ 1)
+ .toGlTextureProcessor(context);
+ Size outputSize = cropTextureProcessor.configure(inputWidth, inputHeight);
+ setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
+ Bitmap expectedBitmap = BitmapTestUtil.readBitmap(ORIGINAL_PNG_ASSET_PATH);
+
+ cropTextureProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
+ Bitmap actualBitmap =
+ BitmapTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer(
+ outputSize.getWidth(), outputSize.getHeight());
+
+ BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory(
+ testId, /* bitmapLabel= */ "actual", actualBitmap);
+ // TODO(b/207848601): switch to using proper tooling for testing against golden data.
+ float averagePixelAbsoluteDifference =
+ BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
+ expectedBitmap, actualBitmap, testId);
+ assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
+ }
+
+ @Test
+ public void drawFrame_cropSmaller_producesExpectedOutput() throws Exception {
+ String testId = "drawFrame_cropSmaller";
+ cropTextureProcessor =
+ new Crop(/* left= */ -.9f, /* right= */ .1f, /* bottom= */ -1f, /* top= */ .5f)
+ .toGlTextureProcessor(context);
+ Size outputSize = cropTextureProcessor.configure(inputWidth, inputHeight);
+ setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
+ Bitmap expectedBitmap = BitmapTestUtil.readBitmap(CROP_SMALLER_PNG_ASSET_PATH);
+
+ cropTextureProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
+ Bitmap actualBitmap =
+ BitmapTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer(
+ outputSize.getWidth(), outputSize.getHeight());
+
+ BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory(
+ testId, /* bitmapLabel= */ "actual", actualBitmap);
+ // TODO(b/207848601): switch to using proper tooling for testing against golden data.
+ float averagePixelAbsoluteDifference =
+ BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
+ expectedBitmap, actualBitmap, testId);
+ assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
+ }
+
+ @Test
+ public void drawFrame_cropLarger_producesExpectedOutput() throws Exception {
+ String testId = "drawFrame_cropLarger";
+ cropTextureProcessor =
+ new Crop(/* left= */ -2f, /* right= */ 2f, /* bottom= */ -1f, /* top= */ 2f)
+ .toGlTextureProcessor(context);
+ Size outputSize = cropTextureProcessor.configure(inputWidth, inputHeight);
+ setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
+ Bitmap expectedBitmap = BitmapTestUtil.readBitmap(CROP_LARGER_PNG_ASSET_PATH);
+
+ cropTextureProcessor.drawFrame(inputTexId, /* presentationTimeUs= */ 0);
+ Bitmap actualBitmap =
+ BitmapTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer(
+ outputSize.getWidth(), outputSize.getHeight());
+
+ BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory(
+ testId, /* bitmapLabel= */ "actual", actualBitmap);
+ // TODO(b/207848601): switch to using proper tooling for testing against golden data.
+ float averagePixelAbsoluteDifference =
+ BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
+ expectedBitmap, actualBitmap, testId);
+ assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
+ }
+
+ private void setupOutputTexture(int outputWidth, int outputHeight) {
+ outputTexId = GlUtil.createTexture(outputWidth, outputHeight);
+ int frameBuffer = GlUtil.createFboForTexture(outputTexId);
+ GlUtil.focusFramebuffer(
+ eglDisplay,
+ eglContext,
+ checkNotNull(placeholderEglSurface),
+ frameBuffer,
+ outputWidth,
+ outputHeight);
+ }
+}
diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/FrameProcessorChainPixelTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/FrameProcessorChainPixelTest.java
index d6b95a0cb0..e55ab256c6 100644
--- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/FrameProcessorChainPixelTest.java
+++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/FrameProcessorChainPixelTest.java
@@ -67,6 +67,8 @@ public final class FrameProcessorChainPixelTest {
"media/bitmap/sample_mp4_first_frame/translate_then_rotate.png";
public static final String REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/request_output_height.png";
+ public static final String CROP_THEN_ASPECT_RATIO_PNG_ASSET_PATH =
+ "media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png";
public static final String ROTATE45_SCALE_TO_FIT_PNG_ASSET_PATH =
"media/bitmap/sample_mp4_first_frame/rotate_45_scale_to_fit.png";
@@ -216,6 +218,28 @@ public final class FrameProcessorChainPixelTest {
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
}
+ @Test
+ public void processData_withCropAndPresentation_producesExpectedOutput() throws Exception {
+ String testId = "processData_withCropAndPresentation";
+ setUpAndPrepareFirstFrame(
+ DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO,
+ new Crop(/* left= */ -.5f, /* right= */ .5f, /* bottom= */ -.5f, /* top= */ .5f),
+ new Presentation.Builder()
+ .setAspectRatio(/* aspectRatio= */ .5f, Presentation.LAYOUT_SCALE_TO_FIT)
+ .build());
+ Bitmap expectedBitmap = BitmapTestUtil.readBitmap(CROP_THEN_ASPECT_RATIO_PNG_ASSET_PATH);
+
+ Bitmap actualBitmap = processFirstFrameAndEnd();
+
+ BitmapTestUtil.maybeSaveTestBitmapToCacheDirectory(
+ testId, /* bitmapLabel= */ "actual", actualBitmap);
+ // TODO(b/207848601): switch to using proper tooling for testing against golden data.
+ float averagePixelAbsoluteDifference =
+ BitmapTestUtil.getAveragePixelAbsoluteDifferenceArgb8888(
+ expectedBitmap, actualBitmap, testId);
+ assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
+ }
+
@Test
public void processData_withScaleToFitTransformation_rotate45_producesExpectedOutput()
throws Exception {
@@ -242,10 +266,8 @@ public final class FrameProcessorChainPixelTest {
throws Exception {
String testId =
"processData_withManyComposedMatrixTransformations_producesSameOutputAsCombinedTransformation";
- Presentation centerCrop =
- new Presentation.Builder()
- .setCrop(/* left= */ -0.5f, /* right= */ 0.5f, /* bottom= */ -0.5f, /* top= */ 0.5f)
- .build();
+ Crop centerCrop =
+ new Crop(/* left= */ -0.5f, /* right= */ 0.5f, /* bottom= */ -0.5f, /* top= */ 0.5f);
ImmutableList.Builder The background color of the output frame will be black, with alpha = 0 if applicable.
+ */
+public final class Crop implements MatrixTransformation {
+
+ static {
+ GlUtil.glAssertionsEnabled = true;
+ }
+
+ private final float left;
+ private final float right;
+ private final float bottom;
+ private final float top;
+
+ private @MonotonicNonNull Matrix transformationMatrix;
+
+ /**
+ * Crops a smaller (or larger) frame, per normalized device coordinates (NDC), where the input
+ * frame corresponds to the square ranging from -1 to 1 on the x and y axes.
+ *
+ * {@code left} and {@code bottom} default to -1, and {@code right} and {@code top} default to
+ * 1, which corresponds to not applying any crop. To crop to a smaller subset of the input frame,
+ * use values between -1 and 1. To crop to a larger frame, use values below -1 and above 1.
+ *
+ * @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 bottom The bottom edge of the output frame, in NDC. Must be less than {@code top}.
+ * @param top The top edge of the output frame, in NDC. Must be greater than {@code bottom}.
+ */
+ public Crop(float left, float right, float bottom, float top) {
+ checkArgument(
+ right > left, "right value " + right + " should be greater than left value " + left);
+ checkArgument(
+ top > bottom, "top value " + top + " should be greater than bottom value " + bottom);
+ this.left = left;
+ this.right = right;
+ this.bottom = bottom;
+ this.top = top;
+
+ transformationMatrix = new Matrix();
+ }
+
+ @Override
+ 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 (left == -1f && right == 1f && bottom == -1f && top == 1f) {
+ // No crop needed.
+ return new Size(inputWidth, inputHeight);
+ }
+
+ float scaleX = (right - left) / GlUtil.LENGTH_NDC;
+ float scaleY = (top - bottom) / GlUtil.LENGTH_NDC;
+ float centerX = (left + right) / 2;
+ float centerY = (bottom + top) / 2;
+
+ transformationMatrix.postTranslate(-centerX, -centerY);
+ transformationMatrix.postScale(1f / scaleX, 1f / scaleY);
+
+ int outputWidth = Math.round(inputWidth * scaleX);
+ int outputHeight = Math.round(inputHeight * scaleY);
+ return new Size(outputWidth, outputHeight);
+ }
+
+ @Override
+ public Matrix getMatrix(long presentationTimeUs) {
+ return checkStateNotNull(transformationMatrix, "configure must be called first");
+ }
+}
diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Presentation.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Presentation.java
index 67f5a565a1..c9d6c4efbd 100644
--- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Presentation.java
+++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Presentation.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
-import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -33,11 +32,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
- * 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).
+ * Controls how a frame is presented with options to set the output resolution 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).
*
- * Cropping or aspect ratio is applied before setting resolution.
+ * Aspect ratio is applied before setting resolution.
*
* The background color of the output frame will be black, with alpha = 0 if applicable.
*/
@@ -104,21 +103,13 @@ public final class Presentation implements MatrixTransformation {
public static final class Builder {
// Optional fields.
- private int heightPixels;
- private float cropLeft;
- private float cropRight;
- private float cropBottom;
- private float cropTop;
+ private int outputHeight;
private float aspectRatio;
private @Layout int layout;
/** Creates a builder with default values. */
public Builder() {
- heightPixels = C.LENGTH_UNSET;
- cropLeft = -1f;
- cropRight = 1f;
- cropBottom = -1f;
- cropTop = 1f;
+ outputHeight = C.LENGTH_UNSET;
aspectRatio = C.LENGTH_UNSET;
}
@@ -136,44 +127,7 @@ public final class Presentation implements MatrixTransformation {
* @return This builder.
*/
public Builder setResolution(int height) {
- this.heightPixels = height;
- return this;
- }
-
- /**
- * Crops a smaller (or larger frame), per normalized device coordinates (NDC), where the input
- * frame corresponds to the square ranging from -1 to 1 on the x and y axes.
- *
- * {@code left} and {@code bottom} default to -1, and {@code right} and {@code top} default
- * to 1, which corresponds to not applying any crop. To crop to a smaller subset of the input
- * frame, use values between -1 and 1. To crop to a larger frame, use values below -1 and above
- * 1.
- *
- * Width and height values set may be rescaled by {@link #setResolution(int)}, which is
- * applied after cropping changes.
- *
- * Only one of {@code setCrop} or {@link #setAspectRatio(float, int)} can be called for one
- * {@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}.
- * @param bottom The bottom edge of the output frame, in NDC. Must be less than {@code top}.
- * @param top The top edge of the output frame, in NDC. Must be greater than {@code bottom}.
- * @return This builder.
- */
- public Builder setCrop(float left, float right, float bottom, float top) {
- checkArgument(
- right > left, "right value " + right + " should be greater than left value " + left);
- checkArgument(
- top > bottom, "top value " + top + " should be greater than bottom value " + bottom);
- checkState(
- aspectRatio == C.LENGTH_UNSET,
- "setAspectRatio and setCrop cannot be called in the same instance");
- cropLeft = left;
- cropRight = right;
- cropBottom = bottom;
- cropTop = top;
-
+ this.outputHeight = height;
return this;
}
@@ -187,9 +141,6 @@ public final class Presentation implements MatrixTransformation {
* Width and height values set may be rescaled by {@link #setResolution(int)}, which is
* applied after aspect ratio changes.
*
- * Only one of {@link #setCrop(float, float, float, float)} or {@code setAspectRatio} can be
- * called for one {@link Presentation}.
- *
* @param aspectRatio The aspect ratio (width/height ratio) of the output frame. Must be
* positive.
* @return This builder.
@@ -201,17 +152,13 @@ public final class Presentation implements MatrixTransformation {
|| layout == LAYOUT_SCALE_TO_FIT_WITH_CROP
|| layout == LAYOUT_STRETCH_TO_FIT,
"invalid layout " + layout);
- checkState(
- cropLeft == -1f && cropRight == 1f && cropBottom == -1f && cropTop == 1f,
- "setAspectRatio and setCrop cannot be called in the same instance");
this.aspectRatio = aspectRatio;
this.layout = layout;
return this;
}
public Presentation build() {
- return new Presentation(
- heightPixels, cropLeft, cropRight, cropBottom, cropTop, aspectRatio, layout);
+ return new Presentation(outputHeight, aspectRatio, layout);
}
}
@@ -220,10 +167,6 @@ public final class Presentation implements MatrixTransformation {
}
private final int requestedHeightPixels;
- private final float cropLeft;
- private final float cropRight;
- private final float cropBottom;
- private final float cropTop;
private final float requestedAspectRatio;
private final @Layout int layout;
@@ -232,19 +175,8 @@ public final class Presentation implements MatrixTransformation {
private @MonotonicNonNull Matrix transformationMatrix;
/** Creates a new instance. */
- private Presentation(
- int requestedHeightPixels,
- float cropLeft,
- float cropRight,
- float cropBottom,
- float cropTop,
- float requestedAspectRatio,
- @Layout int layout) {
+ private Presentation(int requestedHeightPixels, float requestedAspectRatio, @Layout int layout) {
this.requestedHeightPixels = requestedHeightPixels;
- this.cropLeft = cropLeft;
- this.cropRight = cropRight;
- this.cropBottom = cropBottom;
- this.cropTop = cropTop;
this.requestedAspectRatio = requestedAspectRatio;
this.layout = layout;
@@ -262,12 +194,7 @@ public final class Presentation implements MatrixTransformation {
outputWidth = inputWidth;
outputHeight = inputHeight;
- if (cropLeft != -1f || cropRight != 1f || cropBottom != -1f || cropTop != 1f) {
- checkState(
- requestedAspectRatio == C.LENGTH_UNSET,
- "aspect ratio and crop cannot both be set in the same instance");
- applyCrop();
- } else if (requestedAspectRatio != C.LENGTH_UNSET) {
+ if (requestedAspectRatio != C.LENGTH_UNSET) {
applyAspectRatio();
}
@@ -284,20 +211,6 @@ public final class Presentation implements MatrixTransformation {
return checkStateNotNull(transformationMatrix, "configure must be called first");
}
- @RequiresNonNull("transformationMatrix")
- private void applyCrop() {
- float scaleX = (cropRight - cropLeft) / GlUtil.LENGTH_NDC;
- float scaleY = (cropTop - cropBottom) / GlUtil.LENGTH_NDC;
- float centerX = (cropLeft + cropRight) / 2;
- float centerY = (cropBottom + cropTop) / 2;
-
- transformationMatrix.postTranslate(-centerX, -centerY);
- transformationMatrix.postScale(1f / scaleX, 1f / scaleY);
-
- outputWidth = outputWidth * scaleX;
- outputHeight = outputHeight * scaleY;
- }
-
@RequiresNonNull("transformationMatrix")
private void applyAspectRatio() {
float inputAspectRatio = outputWidth / outputHeight;
diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/CropTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/CropTest.java
new file mode 100644
index 0000000000..172d9e81d6
--- /dev/null
+++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/CropTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.google.android.exoplayer2.transformer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Size;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.util.GlUtil;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link Crop}.
+ *
+ * See {@code CropPixelTest} for pixel tests testing {@link Crop}.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CropTest {
+ @Test
+ public void configure_noEdits_leavesFramesUnchanged() {
+ int inputWidth = 200;
+ int inputHeight = 150;
+ Crop crop = new Crop(/* left= */ -1, /* right= */ 1, /* bottom= */ -1, /* top= */ 1);
+
+ Size outputSize = crop.configure(inputWidth, inputHeight);
+
+ assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
+ assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
+ }
+
+ @Test
+ public void configure_setCrop_changesDimensions() {
+ int inputWidth = 300;
+ int inputHeight = 200;
+ float left = -.5f;
+ float right = .5f;
+ float bottom = .5f;
+ float top = 1f;
+ Crop crop = new Crop(left, right, bottom, top);
+
+ Size outputSize = crop.configure(inputWidth, inputHeight);
+
+ int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
+ int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
+ assertThat(outputSize.getWidth()).isEqualTo(expectedPostCropWidth);
+ assertThat(outputSize.getHeight()).isEqualTo(expectedPostCropHeight);
+ }
+}
diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/PresentationTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/PresentationTest.java
index 5638ce3aa8..aa9ef2f303 100644
--- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/PresentationTest.java
+++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/PresentationTest.java
@@ -16,11 +16,9 @@
package com.google.android.exoplayer2.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 com.google.android.exoplayer2.util.GlUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,75 +54,6 @@ public final class PresentationTest {
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
}
- @Test
- public void configure_setCrop_changesDimensions() {
- int inputWidth = 300;
- int inputHeight = 200;
- float left = -.5f;
- float right = .5f;
- float bottom = .5f;
- float top = 1f;
- Presentation presentation =
- new Presentation.Builder().setCrop(left, right, bottom, top).build();
-
- 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);
- assertThat(outputSize.getWidth()).isEqualTo(expectedPostCropWidth);
- assertThat(outputSize.getHeight()).isEqualTo(expectedPostCropHeight);
- }
-
- @Test
- public void configure_setCropAndSetResolution_changesDimensions() {
- int inputWidth = 300;
- int inputHeight = 200;
- float left = -.5f;
- float right = .5f;
- float bottom = .5f;
- float top = 1f;
- int requestedHeight = 100;
- Presentation presentation =
- new Presentation.Builder()
- .setCrop(left, right, bottom, top)
- .setResolution(requestedHeight)
- .build();
-
- 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);
- assertThat(outputSize.getWidth())
- .isEqualTo(
- Math.round((float) requestedHeight * expectedPostCropWidth / expectedPostCropHeight));
- assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
- }
-
- @Test
- public void configure_setResolutionAndCrop_changesDimensions() {
- int inputWidth = 300;
- int inputHeight = 200;
- float left = -.5f;
- float right = .5f;
- float bottom = .5f;
- float top = 1f;
- int requestedHeight = 100;
- Presentation presentation =
- new Presentation.Builder()
- .setResolution(requestedHeight)
- .setCrop(left, right, bottom, top)
- .build();
-
- 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);
- assertThat(outputSize.getWidth())
- .isEqualTo(
- Math.round((float) requestedHeight * expectedPostCropWidth / expectedPostCropHeight));
- assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
- }
-
@Test
public void configure_setAspectRatio_changesDimensions() {
int inputWidth = 300;
@@ -158,30 +87,4 @@ public final class PresentationTest {
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * requestedHeight));
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
}
-
- @Test
- public void configure_setAspectRatioAndCrop_throwsIllegalStateException() {
- Presentation.Builder presentationBuilder =
- new Presentation.Builder()
- .setAspectRatio(/* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT);
-
- assertThrows(
- IllegalStateException.class,
- () ->
- presentationBuilder.setCrop(
- /* left= */ -.5f, /* right= */ .5f, /* bottom= */ .5f, /* top= */ 1f));
- }
-
- @Test
- public void configure_setCropAndAspectRatio_throwsIllegalStateException() {
- Presentation.Builder presentationBuilder =
- new Presentation.Builder()
- .setCrop(/* left= */ -.5f, /* right= */ .5f, /* bottom= */ .5f, /* top= */ 1f);
-
- assertThrows(
- IllegalStateException.class,
- () ->
- presentationBuilder.setAspectRatio(
- /* aspectRatio= */ 2f, Presentation.LAYOUT_SCALE_TO_FIT));
- }
}
diff --git a/testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png b/testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png
new file mode 100644
index 0000000000..6813342d46
Binary files /dev/null and b/testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png differ