DisplayOrientedMeteringPointFactory.java
/*
* Copyright 2019 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;
import android.graphics.PointF;
import android.view.Display;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
/**
* A {@link MeteringPointFactory} that can convert a {@link View} (x, y) into a
* {@link MeteringPoint} which can then be used to construct a {@link FocusMeteringAction} to
* start a focus and metering action.
*
* <p>For apps showing full camera preview in a View without any scaling, cropping or
* rotating applied, they can simply use view width and height to create the
* {@link DisplayOrientedMeteringPointFactory} and then pass {@link View} (x, y) to create a
* {@link MeteringPoint}. This factory will convert the (x, y) into the sensor (x, y) based on
* display rotation and lensFacing.
*
* <p>If camera preview is scaled, cropped or rotated in the {@link View}, it is applications'
* duty to transform the coordinates properly so that the width and height of this
* factory represents the full Preview FOV and also the (x,y) passed to create
* {@link MeteringPoint} needs to be adjusted by apps to the coordinates left-top (0,0) -
* right-bottom (width, height). For example, if the preview is scaled to 2X from the center and
* is cropped in a {@link View}. Assuming that the dimension of View is (240, 320), then the
* width/height of this {@link DisplayOrientedMeteringPointFactory} should be (480, 640). And
* the (x, y) from the {@link View} should be converted to (x + (480-240)/2, y + (640 - 320)/2)
* first.
*
* @see MeteringPoint
*/
public final class DisplayOrientedMeteringPointFactory extends MeteringPointFactory {
/** The logical width of FoV in current display orientation */
private final float mWidth;
/** The logical height of FoV in current display orientation */
private final float mHeight;
/** Lens facing is required for correctly adjusted for front camera */
private final CameraSelector mCameraSelector;
/** {@link Display} used for detecting display orientation */
@NonNull
private final Display mDisplay;
@NonNull
private final CameraInfoInternal mCameraInfo;
/**
* Creates a {@link DisplayOrientedMeteringPointFactory} for converting View (x, y) into a
* {@link MeteringPoint} based on the current display's rotation and {@link CameraSelector}.
*
* <p>The width/height of this factory forms a coordinate left-top (0, 0) - right-bottom
* (width, height) which represents the full camera preview FOV in the display's
* orientation. For apps showing full camera preview in a {@link View}, it is as simple as
* passing View's width/height and passing View (x, y) directly to create a
* {@link MeteringPoint}. Otherwise the (x, y) passed to
* {@link MeteringPointFactory#createPoint(float, float)} should be adjusted to this
* coordinate system first.
*
* @param display {@link Display} to get the orientation from. This should be the
* current display where camera preview is showing.
* @param cameraSelector current cameraSelector to choose camera.
* @param width the width of the coordinate which are mapped to the full camera preview
* FOV in given display's orientation.
* @param height the height of the coordinate which are mapped to the full camera
* preview
* FOV in given display's orientation.
*/
public DisplayOrientedMeteringPointFactory(@NonNull Display display,
@NonNull CameraSelector cameraSelector, float width, float height) {
mWidth = width;
mHeight = height;
mCameraSelector = cameraSelector;
mDisplay = display;
try {
CameraInternal camera = CameraX.getCameraWithCameraSelector(mCameraSelector);
mCameraInfo = camera.getCameraInfoInternal();
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Unable to get camera id for the CameraSelector.", e);
}
}
@Nullable
private Integer getLensFacing() {
return mCameraInfo.getLensFacing();
}
/**
* {@inheritDoc}
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
@Override
protected PointF convertPoint(float x, float y) {
float width = mWidth;
float height = mHeight;
final Integer lensFacing = getLensFacing();
boolean compensateForMirroring =
(lensFacing != null && lensFacing == CameraSelector.LENS_FACING_FRONT);
int relativeCameraOrientation = getRelativeCameraOrientation(compensateForMirroring);
float outputX = x;
float outputY = y;
float outputWidth = width;
float outputHeight = height;
if (relativeCameraOrientation == 90 || relativeCameraOrientation == 270) {
// We're horizontal. Swap width/height. Swap x/y.
outputX = y;
outputY = x;
outputWidth = height;
outputHeight = width;
}
switch (relativeCameraOrientation) {
// Map to correct coordinates according to relativeCameraOrientation
case 90:
outputY = outputHeight - outputY;
break;
case 180:
outputX = outputWidth - outputX;
outputY = outputHeight - outputY;
break;
case 270:
outputX = outputWidth - outputX;
break;
default:
break;
}
// Swap x if it's a mirrored preview
if (compensateForMirroring) {
outputX = outputWidth - outputX;
}
// Normalized it to [0, 1]
outputX = outputX / outputWidth;
outputY = outputY / outputHeight;
return new PointF(outputX, outputY);
}
private int getRelativeCameraOrientation(boolean compensateForMirroring) {
int rotationDegrees;
try {
int displayRotation = mDisplay.getRotation();
rotationDegrees = mCameraInfo.getSensorRotationDegrees(displayRotation);
if (compensateForMirroring) {
rotationDegrees = (360 - rotationDegrees) % 360;
}
} catch (Exception e) {
rotationDegrees = 0;
}
return rotationDegrees;
}
}