PreviewViewMeteringPointFactory.java
/*
* 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.view;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.Size;
import android.view.Display;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.view.PreviewView.ScaleType;
/**
* Implement the MeteringPointFactory for PreviewView by DisplayOrientedMeteringPointFactory.
*
* <p>The width / height in DisplayOrientedMeteringPointFactory defines area (0, 0) - (width,
* height) which represents the full preview surface. The (x, y) passed to createPoint()
* must be in this coordinate system.
*
* However, in PreviewView, the preview could be cropped or letterbox/pillarbox depending on
* the scaleType. Thus we need to adjust two things for DisplayOrientedMeteringPointFactory.
* (1) Calculate the new width/height of factory based on the FIT/FULL scaleType to make it
* represent the full preview
* (2) Add offset to the (x, y) in convertPoint based on the BEGIN/CENTER/END scaleType.
*
*/
class PreviewViewMeteringPointFactory extends MeteringPointFactory {
private DisplayOrientedMeteringPointFactory mDisplayOrientedMeteringPointFactory;
private final float mViewWidth;
private final float mViewHeight;
private float mFactoryWidth;
private float mFactoryHeight;
private final ScaleType mScaleType;
private final boolean mIsValid;
// TODO(b/150916047): Use Previewview scaleType transform to implement instead.
PreviewViewMeteringPointFactory(@NonNull Display display,
@NonNull CameraSelector cameraSelector, @Nullable Size resolution,
@NonNull ScaleType scaleType, int viewWidth, int viewHeight) {
mViewWidth = viewWidth;
mViewHeight = viewHeight;
mScaleType = scaleType;
// invalid condition
if (resolution == null || mViewWidth <= 0 || mViewHeight <= 0) {
mIsValid = false;
return;
}
mIsValid = true;
boolean needReverse = false;
if (isNaturalPortrait(display)) {
if (display.getRotation() == Surface.ROTATION_0
|| display.getRotation() == Surface.ROTATION_180) {
needReverse = true;
}
} else {
if (display.getRotation() == Surface.ROTATION_90
|| display.getRotation() == Surface.ROTATION_270) {
needReverse = true;
}
}
int bufferRotatedWidth;
int bufferRotatedHeight;
if (needReverse) {
bufferRotatedWidth = resolution.getHeight();
bufferRotatedHeight = resolution.getWidth();
} else {
bufferRotatedWidth = resolution.getWidth();
bufferRotatedHeight = resolution.getHeight();
}
final float scale;
if (mScaleType == ScaleType.FILL_CENTER
|| mScaleType == ScaleType.FILL_START
|| mScaleType == ScaleType.FILL_END) {
scale = Math.max((float) viewWidth / bufferRotatedWidth,
(float) viewHeight / bufferRotatedHeight);
} else if (mScaleType == ScaleType.FIT_START
|| mScaleType == ScaleType.FIT_CENTER
|| mScaleType == ScaleType.FIT_END) {
scale = Math.min((float) viewWidth / bufferRotatedWidth,
(float) viewHeight / bufferRotatedHeight);
} else {
throw new IllegalArgumentException("Unknown scale type " + scaleType);
}
mFactoryWidth = bufferRotatedWidth * scale;
mFactoryHeight = bufferRotatedHeight * scale;
mDisplayOrientedMeteringPointFactory =
new DisplayOrientedMeteringPointFactory(display, cameraSelector, mFactoryWidth,
mFactoryHeight);
}
@NonNull
@Override
protected PointF convertPoint(float x, float y) {
if (!mIsValid) {
// Returns a invalid point whose value is out of range [0..1]
return new PointF(2.0f, 2.0f);
}
float offsetX = 0f;
float offsetY = 0f;
if (mScaleType == ScaleType.FILL_START
|| mScaleType == ScaleType.FIT_START) {
offsetX = 0;
offsetY = 0;
} else if (mScaleType == ScaleType.FILL_CENTER
|| mScaleType == ScaleType.FIT_CENTER) {
offsetX = (mFactoryWidth - mViewWidth) / 2;
offsetY = (mFactoryHeight - mViewHeight) / 2;
} else if (mScaleType == ScaleType.FILL_END
|| mScaleType == ScaleType.FIT_END) {
offsetX = (mFactoryWidth - mViewWidth);
offsetY = (mFactoryHeight - mViewHeight);
}
float adjustedX = x + offsetX;
float adjustedY = y + offsetY;
// DisplayOrientedMeteringPointFactory#convertPoint is not public, thus we cannot use
// it to convert the point. A alternative approach is using createPoint() to create a
// MeteringPoint and get X, Y from it.
MeteringPoint pt = mDisplayOrientedMeteringPointFactory.createPoint(adjustedX,
adjustedY);
return new PointF(pt.getX(), pt.getY());
}
private boolean isNaturalPortrait(Display display) {
final Point deviceSize = new Point();
display.getRealSize(deviceSize);
int rotation = display.getRotation();
final int width = deviceSize.x;
final int height = deviceSize.y;
return ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
&& width < height) || (
(rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270)
&& width >= height);
}
}