summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2025-02-25 00:20:46 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-25 00:20:46 -0800
commita619e61625136f3958f6dfd6656e293d76ff6267 (patch)
tree9910549995cac82b69c308437371460a61ee97df
parent20e43c51390da74880afb01ce175baba7b19f2e9 (diff)
parent2f71bdb46b1080943764cafd2fd140be632251ac (diff)
Merge "Centerly crop QR preview and better handling on camera orientation." into main
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java215
1 files changed, 144 insertions, 71 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
index ae17acb5104b..8bb41ccf9600 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCamera.java
@@ -16,8 +16,8 @@
package com.android.settingslib.qrcode;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -75,12 +75,29 @@ public class QrCamera extends Handler {
@VisibleForTesting
Camera mCamera;
+ Camera.CameraInfo mCameraInfo;
+
+ /**
+ * The size of the preview image as requested to camera, e.g. 1920x1080.
+ */
private Size mPreviewSize;
+
+ /**
+ * Whether the preview image would be displayed in "portrait" (width less
+ * than height) orientation in current display orientation.
+ *
+ * Note that we don't distinguish between a rotation of 90 degrees or 270
+ * degrees here, since we center crop all the preview.
+ *
+ * TODO: Handle external camera / multiple display, this likely requires
+ * migrating to newer Camera2 API.
+ */
+ private boolean mPreviewInPortrait;
+
private WeakReference<Context> mContext;
private ScannerCallback mScannerCallback;
private MultiFormatReader mReader;
private DecodingTask mDecodeTask;
- private int mCameraOrientation;
@VisibleForTesting
Camera.Parameters mParameters;
@@ -152,8 +169,14 @@ public class QrCamera extends Handler {
* @param previewSize Is the preview size set by camera
* @param cameraOrientation Is the orientation of current Camera
* @return The rectangle would like to crop from the camera preview shot.
+ * @deprecated This is no longer used, and the frame position is
+ * automatically calculated from the preview size and the
+ * background View size.
*/
- Rect getFramePosition(Size previewSize, int cameraOrientation);
+ @Deprecated
+ default @NonNull Rect getFramePosition(@NonNull Size previewSize, int cameraOrientation) {
+ throw new AssertionError("getFramePosition shouldn't be used");
+ }
/**
* Sets the transform to associate with preview area.
@@ -172,6 +195,41 @@ public class QrCamera extends Handler {
boolean isValid(String qrCode);
}
+ private boolean setPreviewDisplayOrientation() {
+ if (mContext.get() == null) {
+ return false;
+ }
+
+ final WindowManager winManager =
+ (WindowManager) mContext.get().getSystemService(Context.WINDOW_SERVICE);
+ final int rotation = winManager.getDefaultDisplay().getRotation();
+ int degrees = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break;
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break;
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;
+ }
+ int rotateDegrees = 0;
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ rotateDegrees = (mCameraInfo.orientation + degrees) % 360;
+ rotateDegrees = (360 - rotateDegrees) % 360; // compensate the mirror
+ } else {
+ rotateDegrees = (mCameraInfo.orientation - degrees + 360) % 360;
+ }
+ mCamera.setDisplayOrientation(rotateDegrees);
+ mPreviewInPortrait = (rotateDegrees == 90 || rotateDegrees == 270);
+ return true;
+ }
+
@VisibleForTesting
void setCameraParameter() {
mParameters = mCamera.getParameters();
@@ -195,37 +253,39 @@ public class QrCamera extends Handler {
mCamera.setParameters(mParameters);
}
- private boolean startPreview() {
- if (mContext.get() == null) {
- return false;
- }
+ /**
+ * Set transform matrix to crop and center the preview picture.
+ */
+ private void setTransformationMatrix() {
+ final Size previewDisplaySize = rotateIfPortrait(mPreviewSize);
+ final Size viewSize = mScannerCallback.getViewSize();
+ final Rect cropRegion = calculateCenteredCrop(previewDisplaySize, viewSize);
- final WindowManager winManager =
- (WindowManager) mContext.get().getSystemService(Context.WINDOW_SERVICE);
- final int rotation = winManager.getDefaultDisplay().getRotation();
- int degrees = 0;
- switch (rotation) {
- case Surface.ROTATION_0:
- degrees = 0;
- break;
- case Surface.ROTATION_90:
- degrees = 90;
- break;
- case Surface.ROTATION_180:
- degrees = 180;
- break;
- case Surface.ROTATION_270:
- degrees = 270;
- break;
- }
- final int rotateDegrees = (mCameraOrientation - degrees + 360) % 360;
- mCamera.setDisplayOrientation(rotateDegrees);
+ // Note that strictly speaking, since the preview is mirrored in front
+ // camera case, we should also mirror the crop region here. But since
+ // we're cropping at the center, mirroring would result in the same
+ // crop region other than small off-by-one error from floating point
+ // calculation and wouldn't be noticeable.
+
+ // Calculate transformation matrix.
+ float scaleX = previewDisplaySize.getWidth() / (float) cropRegion.width();
+ float scaleY = previewDisplaySize.getHeight() / (float) cropRegion.height();
+ float translateX = -cropRegion.left / (float) cropRegion.width() * viewSize.getWidth();
+ float translateY = -cropRegion.top / (float) cropRegion.height() * viewSize.getHeight();
+
+ // Set the transform matrix.
+ final Matrix matrix = new Matrix();
+ matrix.setScale(scaleX, scaleY);
+ matrix.postTranslate(translateX, translateY);
+ mScannerCallback.setTransform(matrix);
+ }
+
+ private void startPreview() {
mCamera.startPreview();
if (Camera.Parameters.FOCUS_MODE_AUTO.equals(mParameters.getFocusMode())) {
mCamera.autoFocus(/* Camera.AutoFocusCallback */ null);
sendMessageDelayed(obtainMessage(MSG_AUTO_FOCUS), AUTOFOCUS_INTERVAL_MS);
}
- return true;
}
private class DecodingTask extends AsyncTask<Void, Void, String> {
@@ -300,7 +360,7 @@ public class QrCamera extends Handler {
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
releaseCamera();
mCamera = Camera.open(i);
- mCameraOrientation = cameraInfo.orientation;
+ mCameraInfo = cameraInfo;
break;
}
}
@@ -309,7 +369,7 @@ public class QrCamera extends Handler {
Camera.getCameraInfo(0, cameraInfo);
releaseCamera();
mCamera = Camera.open(0);
- mCameraOrientation = cameraInfo.orientation;
+ mCameraInfo = cameraInfo;
}
} catch (RuntimeException e) {
Log.e(TAG, "Fail to open camera: " + e);
@@ -323,11 +383,12 @@ public class QrCamera extends Handler {
throw new IOException("Cannot find available camera");
}
mCamera.setPreviewTexture(surface);
+ if (!setPreviewDisplayOrientation()) {
+ throw new IOException("Lost context");
+ }
setCameraParameter();
setTransformationMatrix();
- if (!startPreview()) {
- throw new IOException("Lost contex");
- }
+ startPreview();
} catch (IOException ioe) {
Log.e(TAG, "Fail to startPreview camera: " + ioe);
mCamera = null;
@@ -345,32 +406,30 @@ public class QrCamera extends Handler {
}
}
- /** Set transform matrix to crop and center the preview picture */
- private void setTransformationMatrix() {
- final boolean isPortrait = mContext.get().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT;
-
- final int previewWidth = isPortrait ? mPreviewSize.getWidth() : mPreviewSize.getHeight();
- final int previewHeight = isPortrait ? mPreviewSize.getHeight() : mPreviewSize.getWidth();
- final float ratioPreview = (float) getRatio(previewWidth, previewHeight);
-
- // Calculate transformation matrix.
- float scaleX = 1.0f;
- float scaleY = 1.0f;
- if (previewWidth > previewHeight) {
- scaleY = scaleX / ratioPreview;
+ /**
+ * Calculates the crop region in `previewSize` to have the same aspect
+ * ratio as `viewSize` and center aligned.
+ */
+ private Rect calculateCenteredCrop(Size previewSize, Size viewSize) {
+ final double previewRatio = getRatio(previewSize);
+ final double viewRatio = getRatio(viewSize);
+ int width;
+ int height;
+ if (previewRatio > viewRatio) {
+ width = previewSize.getWidth();
+ height = (int) Math.round(width * viewRatio);
} else {
- scaleX = scaleY / ratioPreview;
+ height = previewSize.getHeight();
+ width = (int) Math.round(height / viewRatio);
}
-
- // Set the transform matrix.
- final Matrix matrix = new Matrix();
- matrix.setScale(scaleX, scaleY);
- mScannerCallback.setTransform(matrix);
+ final int left = (previewSize.getWidth() - width) / 2;
+ final int top = (previewSize.getHeight() - height) / 2;
+ return new Rect(left, top, left + width, top + height);
}
private QrYuvLuminanceSource getFrameImage(byte[] imageData) {
- final Rect frame = mScannerCallback.getFramePosition(mPreviewSize, mCameraOrientation);
+ final Size viewSize = mScannerCallback.getViewSize();
+ final Rect frame = calculateCenteredCrop(mPreviewSize, rotateIfPortrait(viewSize));
final QrYuvLuminanceSource image = new QrYuvLuminanceSource(imageData,
mPreviewSize.getWidth(), mPreviewSize.getHeight());
return (QrYuvLuminanceSource)
@@ -398,17 +457,18 @@ public class QrCamera extends Handler {
*/
private Size getBestPreviewSize(Camera.Parameters parameters) {
final double minRatioDiffPercent = 0.1;
- final Size windowSize = mScannerCallback.getViewSize();
- final double winRatio = getRatio(windowSize.getWidth(), windowSize.getHeight());
+ final Size viewSize = rotateIfPortrait(mScannerCallback.getViewSize());
+ final double viewRatio = getRatio(viewSize);
double bestChoiceRatio = 0;
Size bestChoice = new Size(0, 0);
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
- double ratio = getRatio(size.width, size.height);
+ final Size newSize = toAndroidSize(size);
+ final double ratio = getRatio(newSize);
if (size.height * size.width > bestChoice.getWidth() * bestChoice.getHeight()
- && (Math.abs(bestChoiceRatio - winRatio) / winRatio > minRatioDiffPercent
- || Math.abs(ratio - winRatio) / winRatio <= minRatioDiffPercent)) {
- bestChoice = new Size(size.width, size.height);
- bestChoiceRatio = getRatio(size.width, size.height);
+ && (Math.abs(bestChoiceRatio - viewRatio) / viewRatio > minRatioDiffPercent
+ || Math.abs(ratio - viewRatio) / viewRatio <= minRatioDiffPercent)) {
+ bestChoice = newSize;
+ bestChoiceRatio = ratio;
}
}
return bestChoice;
@@ -419,25 +479,26 @@ public class QrCamera extends Handler {
* picture size and aspect ratio to choose the best one.
*/
private Size getBestPictureSize(Camera.Parameters parameters) {
- final Camera.Size previewSize = parameters.getPreviewSize();
- final double previewRatio = getRatio(previewSize.width, previewSize.height);
+ final Size previewSize = mPreviewSize;
+ final double previewRatio = getRatio(previewSize);
List<Size> bestChoices = new ArrayList<>();
final List<Size> similarChoices = new ArrayList<>();
// Filter by ratio
- for (Camera.Size size : parameters.getSupportedPictureSizes()) {
- double ratio = getRatio(size.width, size.height);
+ for (Camera.Size picSize : parameters.getSupportedPictureSizes()) {
+ final Size size = toAndroidSize(picSize);
+ final double ratio = getRatio(size);
if (ratio == previewRatio) {
- bestChoices.add(new Size(size.width, size.height));
+ bestChoices.add(size);
} else if (Math.abs(ratio - previewRatio) < MAX_RATIO_DIFF) {
- similarChoices.add(new Size(size.width, size.height));
+ similarChoices.add(size);
}
}
if (bestChoices.size() == 0 && similarChoices.size() == 0) {
Log.d(TAG, "No proper picture size, return default picture size");
Camera.Size defaultPictureSize = parameters.getPictureSize();
- return new Size(defaultPictureSize.width, defaultPictureSize.height);
+ return toAndroidSize(defaultPictureSize);
}
if (bestChoices.size() == 0) {
@@ -447,7 +508,7 @@ public class QrCamera extends Handler {
// Get the best by area
int bestAreaDifference = Integer.MAX_VALUE;
Size bestChoice = null;
- final int previewArea = previewSize.width * previewSize.height;
+ final int previewArea = previewSize.getWidth() * previewSize.getHeight();
for (Size size : bestChoices) {
int areaDifference = Math.abs(size.getWidth() * size.getHeight() - previewArea);
if (areaDifference < bestAreaDifference) {
@@ -458,8 +519,20 @@ public class QrCamera extends Handler {
return bestChoice;
}
- private double getRatio(double x, double y) {
- return (x < y) ? x / y : y / x;
+ private Size rotateIfPortrait(Size size) {
+ if (mPreviewInPortrait) {
+ return new Size(size.getHeight(), size.getWidth());
+ } else {
+ return size;
+ }
+ }
+
+ private double getRatio(Size size) {
+ return size.getHeight() / (double) size.getWidth();
+ }
+
+ private Size toAndroidSize(Camera.Size size) {
+ return new Size(size.width, size.height);
}
@VisibleForTesting