mirror of
https://github.com/androidx/media.git
synced 2025-05-09 00:20:45 +08:00
Transformer GL: Split Presentation and Crop.
This removes the prior restriction of needing to remember not to crop and set aspect ratio in the same Presentation.Builder, and makes each class a bit more targeted. This is partially made feasible by the past work to merge consecutive MatrixTransformations into a single MatrixTransformationFrameProcessor, which ensures that there's no loss in quality between successive MatrixTransformations. PiperOrigin-RevId: 453660582
This commit is contained in:
parent
b3b57bc93d
commit
b33dc5e57b
@ -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}.
|
||||||
|
*
|
||||||
|
* <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
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,8 @@ public final class FrameProcessorChainPixelTest {
|
|||||||
"media/bitmap/sample_mp4_first_frame/translate_then_rotate.png";
|
"media/bitmap/sample_mp4_first_frame/translate_then_rotate.png";
|
||||||
public static final String REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH =
|
public static final String REQUEST_OUTPUT_HEIGHT_PNG_ASSET_PATH =
|
||||||
"media/bitmap/sample_mp4_first_frame/request_output_height.png";
|
"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 =
|
public static final String ROTATE45_SCALE_TO_FIT_PNG_ASSET_PATH =
|
||||||
"media/bitmap/sample_mp4_first_frame/rotate_45_scale_to_fit.png";
|
"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);
|
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
|
@Test
|
||||||
public void processData_withScaleToFitTransformation_rotate45_producesExpectedOutput()
|
public void processData_withScaleToFitTransformation_rotate45_producesExpectedOutput()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@ -242,10 +266,8 @@ public final class FrameProcessorChainPixelTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
String testId =
|
String testId =
|
||||||
"processData_withManyComposedMatrixTransformations_producesSameOutputAsCombinedTransformation";
|
"processData_withManyComposedMatrixTransformations_producesSameOutputAsCombinedTransformation";
|
||||||
Presentation centerCrop =
|
Crop centerCrop =
|
||||||
new Presentation.Builder()
|
new Crop(/* left= */ -0.5f, /* right= */ 0.5f, /* bottom= */ -0.5f, /* top= */ 0.5f);
|
||||||
.setCrop(/* left= */ -0.5f, /* right= */ 0.5f, /* bottom= */ -0.5f, /* top= */ 0.5f)
|
|
||||||
.build();
|
|
||||||
ImmutableList.Builder<GlEffect> full10StepRotationAndCenterCrop = new ImmutableList.Builder<>();
|
ImmutableList.Builder<GlEffect> full10StepRotationAndCenterCrop = new ImmutableList.Builder<>();
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
full10StepRotationAndCenterCrop.add(new Rotation(/* degrees= */ 36));
|
full10StepRotationAndCenterCrop.add(new Rotation(/* degrees= */ 36));
|
||||||
|
@ -47,10 +47,6 @@ import org.junit.runner.RunWith;
|
|||||||
public final class PresentationPixelTest {
|
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 =
|
|
||||||
"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";
|
|
||||||
public static final String ASPECT_RATIO_SCALE_TO_FIT_NARROW_PNG_ASSET_PATH =
|
public static final String ASPECT_RATIO_SCALE_TO_FIT_NARROW_PNG_ASSET_PATH =
|
||||||
"media/bitmap/sample_mp4_first_frame/aspect_ratio_scale_to_fit_narrow.png";
|
"media/bitmap/sample_mp4_first_frame/aspect_ratio_scale_to_fit_narrow.png";
|
||||||
public static final String ASPECT_RATIO_SCALE_TO_FIT_WIDE_PNG_ASSET_PATH =
|
public static final String ASPECT_RATIO_SCALE_TO_FIT_WIDE_PNG_ASSET_PATH =
|
||||||
@ -118,58 +114,6 @@ public final class PresentationPixelTest {
|
|||||||
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
assertThat(averagePixelAbsoluteDifference).isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void drawFrame_cropSmaller_producesExpectedOutput() throws Exception {
|
|
||||||
String testId = "drawFrame_cropSmaller";
|
|
||||||
presentationTextureProcessor =
|
|
||||||
new Presentation.Builder()
|
|
||||||
.setCrop(/* left= */ -.9f, /* right= */ .1f, /* bottom= */ -1f, /* top= */ .5f)
|
|
||||||
.build()
|
|
||||||
.toGlTextureProcessor(context);
|
|
||||||
Size outputSize = presentationTextureProcessor.configure(inputWidth, inputHeight);
|
|
||||||
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
|
||||||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(CROP_SMALLER_PNG_ASSET_PATH);
|
|
||||||
|
|
||||||
presentationTextureProcessor.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_cropSmaller";
|
|
||||||
presentationTextureProcessor =
|
|
||||||
new Presentation.Builder()
|
|
||||||
.setCrop(/* left= */ -2f, /* right= */ 2f, /* bottom= */ -1f, /* top= */ 2f)
|
|
||||||
.build()
|
|
||||||
.toGlTextureProcessor(context);
|
|
||||||
Size outputSize = presentationTextureProcessor.configure(inputWidth, inputHeight);
|
|
||||||
setupOutputTexture(outputSize.getWidth(), outputSize.getHeight());
|
|
||||||
Bitmap expectedBitmap = BitmapTestUtil.readBitmap(CROP_LARGER_PNG_ASSET_PATH);
|
|
||||||
|
|
||||||
presentationTextureProcessor.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
|
@Test
|
||||||
public void drawFrame_changeAspectRatio_scaleToFit_narrow_producesExpectedOutput()
|
public void drawFrame_changeAspectRatio_scaleToFit_narrow_producesExpectedOutput()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.android.exoplayer2.util.Assertions.checkArgument;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.util.Size;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a crop to apply in the vertex shader.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>{@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");
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2.transformer;
|
package com.google.android.exoplayer2.transformer;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
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 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;
|
||||||
@ -33,11 +32,11 @@ 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 with options to set the output resolution, crop the input, and
|
* Controls how a frame is presented with options to set the output resolution and choose how to map
|
||||||
* choose how to map the input pixels onto the output frame geometry (for example, by stretching the
|
* the input pixels onto the output frame geometry (for example, by stretching the input frame to
|
||||||
* input frame to match the specified output frame, or fitting the input frame using letterboxing).
|
* match the specified output frame, or fitting the input frame using letterboxing).
|
||||||
*
|
*
|
||||||
* <p>Cropping or aspect ratio is applied before setting resolution.
|
* <p>Aspect ratio is applied before setting resolution.
|
||||||
*
|
*
|
||||||
* <p>The background color of the output frame will be black, with alpha = 0 if applicable.
|
* <p>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 {
|
public static final class Builder {
|
||||||
|
|
||||||
// Optional fields.
|
// Optional fields.
|
||||||
private int heightPixels;
|
private int outputHeight;
|
||||||
private float cropLeft;
|
|
||||||
private float cropRight;
|
|
||||||
private float cropBottom;
|
|
||||||
private float cropTop;
|
|
||||||
private float aspectRatio;
|
private float aspectRatio;
|
||||||
private @Layout int layout;
|
private @Layout int layout;
|
||||||
|
|
||||||
/** Creates a builder with default values. */
|
/** Creates a builder with default values. */
|
||||||
public Builder() {
|
public Builder() {
|
||||||
heightPixels = C.LENGTH_UNSET;
|
outputHeight = C.LENGTH_UNSET;
|
||||||
cropLeft = -1f;
|
|
||||||
cropRight = 1f;
|
|
||||||
cropBottom = -1f;
|
|
||||||
cropTop = 1f;
|
|
||||||
aspectRatio = C.LENGTH_UNSET;
|
aspectRatio = C.LENGTH_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,44 +127,7 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder setResolution(int height) {
|
public Builder setResolution(int height) {
|
||||||
this.heightPixels = height;
|
this.outputHeight = 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.
|
|
||||||
*
|
|
||||||
* <p>{@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.
|
|
||||||
*
|
|
||||||
* <p>Width and height values set may be rescaled by {@link #setResolution(int)}, which is
|
|
||||||
* applied after cropping changes.
|
|
||||||
*
|
|
||||||
* <p>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;
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,9 +141,6 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
* <p>Width and height values set may be rescaled by {@link #setResolution(int)}, which is
|
* <p>Width and height values set may be rescaled by {@link #setResolution(int)}, which is
|
||||||
* 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
|
|
||||||
* 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.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
@ -201,17 +152,13 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
|| layout == LAYOUT_SCALE_TO_FIT_WITH_CROP
|
|| layout == LAYOUT_SCALE_TO_FIT_WITH_CROP
|
||||||
|| layout == LAYOUT_STRETCH_TO_FIT,
|
|| layout == LAYOUT_STRETCH_TO_FIT,
|
||||||
"invalid layout " + layout);
|
"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.aspectRatio = aspectRatio;
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Presentation build() {
|
public Presentation build() {
|
||||||
return new Presentation(
|
return new Presentation(outputHeight, aspectRatio, layout);
|
||||||
heightPixels, cropLeft, cropRight, cropBottom, cropTop, aspectRatio, layout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,10 +167,6 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final int requestedHeightPixels;
|
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 float requestedAspectRatio;
|
||||||
private final @Layout int layout;
|
private final @Layout int layout;
|
||||||
|
|
||||||
@ -232,19 +175,8 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
private @MonotonicNonNull Matrix transformationMatrix;
|
private @MonotonicNonNull Matrix transformationMatrix;
|
||||||
|
|
||||||
/** Creates a new instance. */
|
/** Creates a new instance. */
|
||||||
private Presentation(
|
private Presentation(int requestedHeightPixels, float requestedAspectRatio, @Layout int layout) {
|
||||||
int requestedHeightPixels,
|
|
||||||
float cropLeft,
|
|
||||||
float cropRight,
|
|
||||||
float cropBottom,
|
|
||||||
float cropTop,
|
|
||||||
float requestedAspectRatio,
|
|
||||||
@Layout int layout) {
|
|
||||||
this.requestedHeightPixels = requestedHeightPixels;
|
this.requestedHeightPixels = requestedHeightPixels;
|
||||||
this.cropLeft = cropLeft;
|
|
||||||
this.cropRight = cropRight;
|
|
||||||
this.cropBottom = cropBottom;
|
|
||||||
this.cropTop = cropTop;
|
|
||||||
this.requestedAspectRatio = requestedAspectRatio;
|
this.requestedAspectRatio = requestedAspectRatio;
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
|
|
||||||
@ -262,12 +194,7 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
outputWidth = inputWidth;
|
outputWidth = inputWidth;
|
||||||
outputHeight = inputHeight;
|
outputHeight = inputHeight;
|
||||||
|
|
||||||
if (cropLeft != -1f || cropRight != 1f || cropBottom != -1f || cropTop != 1f) {
|
if (requestedAspectRatio != C.LENGTH_UNSET) {
|
||||||
checkState(
|
|
||||||
requestedAspectRatio == C.LENGTH_UNSET,
|
|
||||||
"aspect ratio and crop cannot both be set in the same instance");
|
|
||||||
applyCrop();
|
|
||||||
} else if (requestedAspectRatio != C.LENGTH_UNSET) {
|
|
||||||
applyAspectRatio();
|
applyAspectRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,20 +211,6 @@ public final class Presentation implements MatrixTransformation {
|
|||||||
return checkStateNotNull(transformationMatrix, "configure must be called first");
|
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")
|
@RequiresNonNull("transformationMatrix")
|
||||||
private void applyAspectRatio() {
|
private void applyAspectRatio() {
|
||||||
float inputAspectRatio = outputWidth / outputHeight;
|
float inputAspectRatio = outputWidth / outputHeight;
|
||||||
|
@ -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}.
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,9 @@
|
|||||||
package com.google.android.exoplayer2.transformer;
|
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 android.util.Size;
|
import android.util.Size;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.util.GlUtil;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -56,75 +54,6 @@ public final class PresentationTest {
|
|||||||
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
|
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
|
@Test
|
||||||
public void configure_setAspectRatio_changesDimensions() {
|
public void configure_setAspectRatio_changesDimensions() {
|
||||||
int inputWidth = 300;
|
int inputWidth = 300;
|
||||||
@ -158,30 +87,4 @@ public final class PresentationTest {
|
|||||||
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
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
BIN
testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png
vendored
Normal file
BIN
testdata/src/test/assets/media/bitmap/sample_mp4_first_frame/crop_then_aspect_ratio.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
Loading…
x
Reference in New Issue
Block a user