Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 77f4a36

Browse files
authored
[camera] android-rework part 6: Android exposure- and focus point features (#4039)
1 parent 9dd26e0 commit 77f4a36

File tree

10 files changed

+1077
-613
lines changed

10 files changed

+1077
-613
lines changed

packages/camera/camera/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ android {
4949
dependencies {
5050
compileOnly 'androidx.annotation:annotation:1.1.0'
5151
testImplementation 'junit:junit:4.12'
52-
testImplementation 'org.mockito:mockito-inline:3.5.13'
52+
testImplementation 'org.mockito:mockito-inline:3.11.1'
5353
testImplementation 'androidx.test:core:1.3.0'
5454
testImplementation 'org.robolectric:robolectric:4.3'
5555
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera;
6+
7+
import android.annotation.TargetApi;
8+
import android.hardware.camera2.CaptureRequest;
9+
import android.hardware.camera2.params.MeteringRectangle;
10+
import android.os.Build;
11+
import android.util.Size;
12+
import androidx.annotation.NonNull;
13+
import androidx.annotation.VisibleForTesting;
14+
import java.util.Arrays;
15+
16+
/**
17+
* Utility class offering functions to calculate values regarding the camera boundaries.
18+
*
19+
* <p>The functions are used to calculate focus and exposure settings.
20+
*/
21+
public final class CameraRegionUtils {
22+
23+
/**
24+
* Obtains the boundaries for the currently active camera, that can be used for calculating
25+
* MeteringRectangle instances required for setting focus or exposure settings.
26+
*
27+
* @param cameraProperties - Collection of the characteristics for the current camera device.
28+
* @param requestBuilder - The request builder for the current capture request.
29+
* @return The boundaries for the current camera device.
30+
*/
31+
public static Size getCameraBoundaries(
32+
@NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) {
33+
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
34+
&& supportsDistortionCorrection(cameraProperties)) {
35+
// Get the current distortion correction mode.
36+
Integer distortionCorrectionMode =
37+
requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE);
38+
39+
// Return the correct boundaries depending on the mode.
40+
android.graphics.Rect rect;
41+
if (distortionCorrectionMode == null
42+
|| distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) {
43+
rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize();
44+
} else {
45+
rect = cameraProperties.getSensorInfoActiveArraySize();
46+
}
47+
48+
return SizeFactory.create(rect.width(), rect.height());
49+
} else {
50+
// No distortion correction support.
51+
return cameraProperties.getSensorInfoPixelArraySize();
52+
}
53+
}
54+
55+
/**
56+
* Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the center
57+
* point.
58+
*
59+
* <p>Since the Camera API (due to cross-platform constraints) only accepts a point when
60+
* configuring a specific focus or exposure area and Android requires a rectangle to configure
61+
* these settings there is a need to convert the point into a rectangle. This method will create
62+
* the required rectangle with an arbitrarily size that is a 10th of the current viewport and the
63+
* coordinates as the center point.
64+
*
65+
* @param boundaries - The camera boundaries to calculate the metering rectangle for.
66+
* @param x x - 1 >= coordinate >= 0.
67+
* @param y y - 1 >= coordinate >= 0.
68+
* @return The dimensions of the metering rectangle based on the supplied coordinates and
69+
* boundaries.
70+
*/
71+
public static MeteringRectangle convertPointToMeteringRectangle(
72+
@NonNull Size boundaries, double x, double y) {
73+
assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0);
74+
assert (x >= 0 && x <= 1);
75+
assert (y >= 0 && y <= 1);
76+
77+
// Interpolate the target coordinate.
78+
int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1)));
79+
int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1)));
80+
// Determine the dimensions of the metering rectangle (10th of the viewport).
81+
int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d);
82+
int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d);
83+
// Adjust target coordinate to represent top-left corner of metering rectangle.
84+
targetX -= targetWidth / 2;
85+
targetY -= targetHeight / 2;
86+
// Adjust target coordinate as to not fall out of bounds.
87+
if (targetX < 0) {
88+
targetX = 0;
89+
}
90+
if (targetY < 0) {
91+
targetY = 0;
92+
}
93+
int maxTargetX = boundaries.getWidth() - 1 - targetWidth;
94+
int maxTargetY = boundaries.getHeight() - 1 - targetHeight;
95+
if (targetX > maxTargetX) {
96+
targetX = maxTargetX;
97+
}
98+
if (targetY > maxTargetY) {
99+
targetY = maxTargetY;
100+
}
101+
102+
// Build the metering rectangle.
103+
return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1);
104+
}
105+
106+
@TargetApi(Build.VERSION_CODES.P)
107+
private static boolean supportsDistortionCorrection(CameraProperties cameraProperties) {
108+
int[] availableDistortionCorrectionModes =
109+
cameraProperties.getDistortionCorrectionAvailableModes();
110+
if (availableDistortionCorrectionModes == null) {
111+
availableDistortionCorrectionModes = new int[0];
112+
}
113+
long nonOffModesSupported =
114+
Arrays.stream(availableDistortionCorrectionModes)
115+
.filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF)
116+
.count();
117+
return nonOffModesSupported > 0;
118+
}
119+
120+
/** Factory class that assists in creating a {@link MeteringRectangle} instance. */
121+
static class MeteringRectangleFactory {
122+
/**
123+
* Creates a new instance of the {@link MeteringRectangle} class.
124+
*
125+
* <p>This method is visible for testing purposes only and should never be used outside this *
126+
* class.
127+
*
128+
* @param x coordinate >= 0.
129+
* @param y coordinate >= 0.
130+
* @param width width >= 0.
131+
* @param height height >= 0.
132+
* @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and
133+
* {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively
134+
* @return new instance of the {@link MeteringRectangle} class.
135+
* @throws IllegalArgumentException if any of the parameters were negative.
136+
*/
137+
@VisibleForTesting
138+
public static MeteringRectangle create(
139+
int x, int y, int width, int height, int meteringWeight) {
140+
return new MeteringRectangle(x, y, width, height, meteringWeight);
141+
}
142+
}
143+
144+
/** Factory class that assists in creating a {@link Size} instance. */
145+
static class SizeFactory {
146+
/**
147+
* Creates a new instance of the {@link Size} class.
148+
*
149+
* <p>This method is visible for testing purposes only and should never be used outside this *
150+
* class.
151+
*
152+
* @param width width >= 0.
153+
* @param height height >= 0.
154+
* @return new instance of the {@link Size} class.
155+
*/
156+
@VisibleForTesting
157+
public static Size create(int width, int height) {
158+
return new Size(width, height);
159+
}
160+
}
161+
}

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,37 @@
66

77
import android.hardware.camera2.CaptureRequest;
88
import android.hardware.camera2.params.MeteringRectangle;
9-
import android.util.Log;
9+
import android.util.Size;
1010
import androidx.annotation.NonNull;
1111
import io.flutter.plugins.camera.CameraProperties;
12+
import io.flutter.plugins.camera.CameraRegionUtils;
1213
import io.flutter.plugins.camera.features.CameraFeature;
1314
import io.flutter.plugins.camera.features.Point;
14-
import io.flutter.plugins.camera.types.CameraRegions;
1515

1616
/** Exposure point controls where in the frame exposure metering will come from. */
1717
public class ExposurePointFeature extends CameraFeature<Point> {
1818

19-
private final CameraRegions cameraRegions;
20-
private Point currentSetting = new Point(0.0, 0.0);
19+
private Size cameraBoundaries;
20+
private Point exposurePoint;
21+
private MeteringRectangle exposureRectangle;
2122

2223
/**
2324
* Creates a new instance of the {@link ExposurePointFeature}.
2425
*
2526
* @param cameraProperties Collection of the characteristics for the current camera device.
26-
* @param cameraRegions Utility class to assist in calculating exposure boundaries.
2727
*/
28-
public ExposurePointFeature(CameraProperties cameraProperties, CameraRegions cameraRegions) {
28+
public ExposurePointFeature(CameraProperties cameraProperties) {
2929
super(cameraProperties);
30-
this.cameraRegions = cameraRegions;
30+
}
31+
32+
/**
33+
* Sets the camera boundaries that are required for the exposure point feature to function.
34+
*
35+
* @param cameraBoundaries - The camera boundaries to set.
36+
*/
37+
public void setCameraBoundaries(@NonNull Size cameraBoundaries) {
38+
this.cameraBoundaries = cameraBoundaries;
39+
this.buildExposureRectangle();
3140
}
3241

3342
@Override
@@ -37,18 +46,13 @@ public String getDebugName() {
3746

3847
@Override
3948
public Point getValue() {
40-
return currentSetting;
49+
return exposurePoint;
4150
}
4251

4352
@Override
44-
public void setValue(@NonNull Point value) {
45-
this.currentSetting = value;
46-
47-
if (value.x == null || value.y == null) {
48-
cameraRegions.resetAutoExposureMeteringRectangle();
49-
} else {
50-
cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y);
51-
}
53+
public void setValue(Point value) {
54+
this.exposurePoint = (value == null || value.x == null || value.y == null) ? null : value;
55+
this.buildExposureRectangle();
5256
}
5357

5458
// Whether or not this camera can set the exposure point.
@@ -63,16 +67,22 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
6367
if (!checkIsSupported()) {
6468
return;
6569
}
66-
67-
MeteringRectangle aeRect = null;
68-
try {
69-
aeRect = cameraRegions.getAEMeteringRectangle();
70-
} catch (Exception e) {
71-
Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e);
72-
}
73-
7470
requestBuilder.set(
7571
CaptureRequest.CONTROL_AE_REGIONS,
76-
aeRect == null ? null : new MeteringRectangle[] {aeRect});
72+
exposureRectangle == null ? null : new MeteringRectangle[] {exposureRectangle});
73+
}
74+
75+
private void buildExposureRectangle() {
76+
if (this.cameraBoundaries == null) {
77+
throw new AssertionError(
78+
"The cameraBoundaries should be set (using `ExposurePointFeature.setCameraBoundaries(Size)`) before updating the exposure point.");
79+
}
80+
if (this.exposurePoint == null) {
81+
this.exposureRectangle = null;
82+
} else {
83+
this.exposureRectangle =
84+
CameraRegionUtils.convertPointToMeteringRectangle(
85+
this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y);
86+
}
7787
}
7888
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.focuspoint;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import android.hardware.camera2.params.MeteringRectangle;
9+
import android.util.Size;
10+
import androidx.annotation.NonNull;
11+
import io.flutter.plugins.camera.CameraProperties;
12+
import io.flutter.plugins.camera.CameraRegionUtils;
13+
import io.flutter.plugins.camera.features.CameraFeature;
14+
import io.flutter.plugins.camera.features.Point;
15+
16+
/** Focus point controls where in the frame focus will come from. */
17+
public class FocusPointFeature extends CameraFeature<Point> {
18+
19+
private Size cameraBoundaries;
20+
private Point focusPoint;
21+
private MeteringRectangle focusRectangle;
22+
23+
/**
24+
* Creates a new instance of the {@link FocusPointFeature}.
25+
*
26+
* @param cameraProperties Collection of the characteristics for the current camera device.
27+
*/
28+
public FocusPointFeature(CameraProperties cameraProperties) {
29+
super(cameraProperties);
30+
}
31+
32+
/**
33+
* Sets the camera boundaries that are required for the focus point feature to function.
34+
*
35+
* @param cameraBoundaries - The camera boundaries to set.
36+
*/
37+
public void setCameraBoundaries(@NonNull Size cameraBoundaries) {
38+
this.cameraBoundaries = cameraBoundaries;
39+
this.buildFocusRectangle();
40+
}
41+
42+
@Override
43+
public String getDebugName() {
44+
return "FocusPointFeature";
45+
}
46+
47+
@Override
48+
public Point getValue() {
49+
return focusPoint;
50+
}
51+
52+
@Override
53+
public void setValue(Point value) {
54+
this.focusPoint = value == null || value.x == null || value.y == null ? null : value;
55+
this.buildFocusRectangle();
56+
}
57+
58+
// Whether or not this camera can set the focus point.
59+
@Override
60+
public boolean checkIsSupported() {
61+
Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoFocus();
62+
return supportedRegions != null && supportedRegions > 0;
63+
}
64+
65+
@Override
66+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
67+
if (!checkIsSupported()) {
68+
return;
69+
}
70+
requestBuilder.set(
71+
CaptureRequest.CONTROL_AF_REGIONS,
72+
focusRectangle == null ? null : new MeteringRectangle[] {focusRectangle});
73+
}
74+
75+
private void buildFocusRectangle() {
76+
if (this.cameraBoundaries == null) {
77+
throw new AssertionError(
78+
"The cameraBoundaries should be set (using `FocusPointFeature.setCameraBoundaries(Size)`) before updating the focus point.");
79+
}
80+
if (this.focusPoint == null) {
81+
this.focusRectangle = null;
82+
} else {
83+
this.focusRectangle =
84+
CameraRegionUtils.convertPointToMeteringRectangle(
85+
this.cameraBoundaries, this.focusPoint.x, this.focusPoint.y);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)