/*
* Copyright 2020 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.camera.core.internal;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Rational;
import android.util.Size;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.camera.core.UseCase;
import androidx.camera.core.ViewPort;
import androidx.camera.core.internal.utils.ImageUtil;
import java.util.HashMap;
import java.util.Map;
/**
* Utility methods for calculating viewports.
*/
public class ViewPorts {
private ViewPorts() {
}
/**
* Calculate a set of ViewPorts based on the combination of the camera, viewport, and use cases.
*
* @param fullSensorRect The full size of the viewport.
* @param viewPortAspectRatio The aspect ratio of the viewport.
* @param outputRotationDegrees The output rotation of the viewport
* @param scaleType The scaletype to calculate
* @param useCaseSizes The resolutions of the UseCases
* @return The set of Viewports that should be set for each UseCase
*/
@NonNull
public static Map<UseCase, Rect> calculateViewPortRects(
@NonNull Rect fullSensorRect,
@NonNull Rational viewPortAspectRatio,
@IntRange(from = 0, to = 359) int outputRotationDegrees,
@ViewPort.ScaleType int scaleType,
@NonNull Map<UseCase, Size> useCaseSizes) {
// Transform aspect ratio to sensor orientation. The the rest of the method is in sensor
// orientation.
Rational rotatedViewPortAspectRatio = ImageUtil.getRotatedAspectRatio(
outputRotationDegrees, viewPortAspectRatio);
RectF fullSensorRectF = new RectF(fullSensorRect);
// Calculate the transformation for each UseCase.
Map<UseCase, Matrix> useCaseToSensorTransformations = new HashMap<>();
RectF sensorIntersectionRect = new RectF(fullSensorRect);
for (Map.Entry<UseCase, Size> entry : useCaseSizes.entrySet()) {
// Calculate the transformation from UseCase to sensor.
Matrix useCaseToSensorTransformation = new Matrix();
RectF srcRect = new RectF(0, 0, entry.getValue().getWidth(),
entry.getValue().getHeight());
useCaseToSensorTransformation.setRectToRect(srcRect, fullSensorRectF,
Matrix.ScaleToFit.CENTER);
useCaseToSensorTransformations.put(entry.getKey(), useCaseToSensorTransformation);
// Calculate the UseCase intersection in sensor coordinates.
RectF useCaseSensorRect = new RectF();
useCaseToSensorTransformation.mapRect(useCaseSensorRect, srcRect);
sensorIntersectionRect.intersect(useCaseSensorRect);
}
// Get the shared sensor rect by the given aspect ratio.
sensorIntersectionRect = getScaledRect(
sensorIntersectionRect, rotatedViewPortAspectRatio, scaleType);
// Map the max shared sensor rect to UseCase coordinates.
Map<UseCase, Rect> useCaseOutputRects = new HashMap<>();
RectF useCaseOutputRect = new RectF();
Matrix sensorToUseCaseTransformation = new Matrix();
for (Map.Entry<UseCase, Matrix> entry : useCaseToSensorTransformations.entrySet()) {
// Transform the sensor crop rect to UseCase coordinates.
entry.getValue().invert(sensorToUseCaseTransformation);
sensorToUseCaseTransformation.mapRect(useCaseOutputRect, sensorIntersectionRect);
Rect outputCropRect = new Rect();
useCaseOutputRect.round(outputCropRect);
useCaseOutputRects.put(entry.getKey(), outputCropRect);
}
return useCaseOutputRects;
}
/**
* Returns the scaled surface rect that fits/fills the given view port aspect ratio.
*
* <p> Scale type represents the transformation from surface to view port. For FIT types,
* this method returns the smallest rectangle that is larger the surface; For FILL types,
* returns the largest rectangle that is smaller than the view port. The returned rectangle
* is also required to 1) have the view port's aspect ratio and 2) be in the surface
* coordinates.
*/
@SuppressLint("SwitchIntDef")
@NonNull
public static RectF getScaledRect(
@NonNull RectF surfaceRect,
@NonNull Rational viewPortAspectRatio,
@ViewPort.ScaleType int scaleType) {
Matrix viewPortToSurfaceTransformation = new Matrix();
RectF viewPortRect = new RectF(0, 0, viewPortAspectRatio.getNumerator(),
viewPortAspectRatio.getDenominator());
if (scaleType == ViewPort.FIT_CENTER || scaleType == ViewPort.FIT_END
|| scaleType == ViewPort.FIT_START) {
Matrix surfaceToViewPortTransformation = new Matrix();
switch (scaleType) {
// To workaround the limitation that Matrix doesn't not support FILL types
// natively, use inverted backward FIT mapping to achieve forward FILL mapping.
case ViewPort.FIT_CENTER:
surfaceToViewPortTransformation.setRectToRect(
surfaceRect, viewPortRect, Matrix.ScaleToFit.CENTER);
break;
case ViewPort.FIT_START:
surfaceToViewPortTransformation.setRectToRect(
surfaceRect, viewPortRect, Matrix.ScaleToFit.START);
break;
case ViewPort.FIT_END:
surfaceToViewPortTransformation.setRectToRect(
surfaceRect, viewPortRect, Matrix.ScaleToFit.END);
break;
}
surfaceToViewPortTransformation.invert(viewPortToSurfaceTransformation);
} else if (scaleType == ViewPort.FILL_CENTER || scaleType == ViewPort.FILL_END
|| scaleType == ViewPort.FILL_START) {
switch (scaleType) {
case ViewPort.FILL_CENTER:
viewPortToSurfaceTransformation.setRectToRect(
viewPortRect, surfaceRect, Matrix.ScaleToFit.CENTER);
break;
case ViewPort.FILL_START:
viewPortToSurfaceTransformation.setRectToRect(
viewPortRect, surfaceRect, Matrix.ScaleToFit.START);
break;
case ViewPort.FILL_END:
viewPortToSurfaceTransformation.setRectToRect(
viewPortRect, surfaceRect, Matrix.ScaleToFit.END);
break;
}
} else {
throw new IllegalStateException("Unexpected scale type: " + scaleType);
}
RectF viewPortRectInSurfaceCoordinates = new RectF();
viewPortToSurfaceTransformation.mapRect(viewPortRectInSurfaceCoordinates, viewPortRect);
return viewPortRectInSurfaceCoordinates;
}
}