Skip to content

Commit 0c79b3b

Browse files
BeMacizedMinyewoo
authored andcommitted
[camera] Add iOS and Android implementations for managing auto exposure. (flutter#3346)
* Added platform interface methods for setting auto exposure. * Added platform interface methods for setting auto exposure. * Remove workspace files * Added auto exposure implementations for Android and iOS * iOS fix for setting the exposure point * Removed unnecessary check * Update platform interface dependency * Implement PR feedback * Restore test * Small improvements for exposure point resetting
1 parent 74fd685 commit 0c79b3b

File tree

9 files changed

+464
-58
lines changed

9 files changed

+464
-58
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.4
2+
3+
* Adds auto exposure support for Android and iOS implementations.
4+
15
## 0.6.3+4
26

37
* Revert previous dependency update: Changed dependency on camera_platform_interface to >=1.04 <1.1.0.

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public void onOrientationChanged(int i) {
144144
characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE),
145145
characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
146146
}
147+
private CameraRegions cameraRegions;
147148

148149
private void prepareMediaRecorder(String outputFilePath) throws IOException {
149150
if (mediaRecorder != null) {
@@ -175,6 +176,7 @@ public void open() throws CameraAccessException {
175176
public void onOpened(@NonNull CameraDevice device) {
176177
cameraDevice = device;
177178
try {
179+
cameraRegions = new CameraRegions(getRegionBoundaries());
178180
startPreview();
179181
dartMessenger.sendCameraInitializedEvent(
180182
previewSize.getWidth(),
@@ -639,7 +641,7 @@ public void setFlashMode(@NonNull final Result result, FlashMode mode)
639641
// If switching directly from torch to auto or on, make sure we turn off the torch.
640642
if (flashMode == FlashMode.torch && mode != FlashMode.torch && mode != FlashMode.off) {
641643
this.flashMode = FlashMode.off;
642-
initPreviewCaptureBuilder();
644+
initPreviewCaptureBuilder();
643645
this.cameraCaptureSession.setRepeatingRequest(
644646
captureRequestBuilder.build(),
645647
new CaptureCallback() {
@@ -675,8 +677,8 @@ public void onCaptureFailed(
675677
null);
676678
} else {
677679
updateFlash(mode);
678-
result.success(null);
679-
}
680+
result.success(null);
681+
}
680682
}
681683

682684
public void setWbMode(@NonNull final Result result, WbMode mode)
@@ -906,9 +908,11 @@ private void initPreviewCaptureBuilder() {
906908
break;
907909
}
908910

911+
909912
// Applying auto exposure
910913
if (aeMeteringRectangle != null) {
911-
captureRequestBuilder.set(
914+
captureRequestBuilder.set(
915+
912916
CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {aeMeteringRectangle});
913917
}
914918
switch (exposureMode) {
@@ -972,7 +976,7 @@ private void initPreviewCaptureBuilder() {
972976
captureRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
973977
//captureRequestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, false);
974978
break;
975-
}
979+
}
976980
}
977981

978982
public void startPreview() throws CameraAccessException {
@@ -999,6 +1003,47 @@ public void onCancel(Object o) {
9991003
});
10001004
}
10011005

1006+
@TargetApi(VERSION_CODES.P)
1007+
private boolean supportsDistortionCorrection() throws CameraAccessException {
1008+
int[] availableDistortionCorrectionModes =
1009+
cameraManager
1010+
.getCameraCharacteristics(cameraDevice.getId())
1011+
.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES);
1012+
if (availableDistortionCorrectionModes == null) availableDistortionCorrectionModes = new int[0];
1013+
long nonOffModesSupported =
1014+
Arrays.stream(availableDistortionCorrectionModes)
1015+
.filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF)
1016+
.count();
1017+
return nonOffModesSupported > 0;
1018+
}
1019+
1020+
private Size getRegionBoundaries() throws CameraAccessException {
1021+
// No distortion correction support
1022+
if (android.os.Build.VERSION.SDK_INT < VERSION_CODES.P || !supportsDistortionCorrection()) {
1023+
return cameraManager
1024+
.getCameraCharacteristics(cameraDevice.getId())
1025+
.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
1026+
}
1027+
// Get the current distortion correction mode
1028+
Integer distortionCorrectionMode =
1029+
captureRequestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE);
1030+
// Return the correct boundaries depending on the mode
1031+
android.graphics.Rect rect;
1032+
if (distortionCorrectionMode == null
1033+
|| distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) {
1034+
rect =
1035+
cameraManager
1036+
.getCameraCharacteristics(cameraDevice.getId())
1037+
.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
1038+
} else {
1039+
rect =
1040+
cameraManager
1041+
.getCameraCharacteristics(cameraDevice.getId())
1042+
.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
1043+
}
1044+
return rect == null ? null : new Size(rect.width(), rect.height());
1045+
}
1046+
10021047
private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) {
10031048
imageStreamReader.setOnImageAvailableListener(
10041049
reader -> {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.flutter.plugins.camera;
2+
3+
import android.hardware.camera2.params.MeteringRectangle;
4+
import android.util.Size;
5+
6+
public final class CameraRegions {
7+
private MeteringRectangle aeMeteringRectangle;
8+
private Size maxBoundaries;
9+
10+
public CameraRegions(Size maxBoundaries) {
11+
assert (maxBoundaries == null || maxBoundaries.getWidth() > 0);
12+
assert (maxBoundaries == null || maxBoundaries.getHeight() > 0);
13+
this.maxBoundaries = maxBoundaries;
14+
}
15+
16+
public MeteringRectangle getAEMeteringRectangle() {
17+
return aeMeteringRectangle;
18+
}
19+
20+
public Size getMaxBoundaries() {
21+
return this.maxBoundaries;
22+
}
23+
24+
public void resetAutoExposureMeteringRectangle() {
25+
this.aeMeteringRectangle = null;
26+
}
27+
28+
public void setAutoExposureMeteringRectangleFromPoint(double x, double y) {
29+
this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
30+
}
31+
32+
public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) {
33+
assert (x >= 0 && x <= 1);
34+
assert (y >= 0 && y <= 1);
35+
if (maxBoundaries == null)
36+
throw new IllegalStateException(
37+
"Functionality for managing metering rectangles is unavailable as this CameraRegions instance was initialized with null boundaries.");
38+
39+
// Interpolate the target coordinate
40+
int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1)));
41+
int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1)));
42+
// Determine the dimensions of the metering triangle (10th of the viewport)
43+
int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d);
44+
int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d);
45+
// Adjust target coordinate to represent top-left corner of metering rectangle
46+
targetX -= targetWidth / 2;
47+
targetY -= targetHeight / 2;
48+
// Adjust target coordinate as to not fall out of bounds
49+
if (targetX < 0) targetX = 0;
50+
if (targetY < 0) targetY = 0;
51+
int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth;
52+
int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight;
53+
if (targetX > maxTargetX) targetX = maxTargetX;
54+
if (targetY > maxTargetY) targetY = maxTargetY;
55+
56+
// Build the metering rectangle
57+
return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1);
58+
}
59+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package io.flutter.plugins.camera;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
6+
7+
import android.hardware.camera2.params.MeteringRectangle;
8+
import android.util.Size;
9+
import org.junit.Before;
10+
import org.junit.Test;
11+
import org.junit.runner.RunWith;
12+
import org.robolectric.RobolectricTestRunner;
13+
14+
@RunWith(RobolectricTestRunner.class)
15+
public class CameraRegionsTest {
16+
17+
CameraRegions cameraRegions;
18+
19+
@Before
20+
public void setUp() {
21+
this.cameraRegions = new CameraRegions(new Size(100, 50));
22+
}
23+
24+
@Test(expected = AssertionError.class)
25+
public void getMeteringRectangleForPoint_should_throw_for_x_upper_bound() {
26+
cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 1.5, 0);
27+
}
28+
29+
@Test(expected = AssertionError.class)
30+
public void getMeteringRectangleForPoint_should_throw_for_x_lower_bound() {
31+
cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), -0.5, 0);
32+
}
33+
34+
@Test(expected = AssertionError.class)
35+
public void getMeteringRectangleForPoint_should_throw_for_y_upper_bound() {
36+
cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, 1.5);
37+
}
38+
39+
@Test(expected = AssertionError.class)
40+
public void getMeteringRectangleForPoint_should_throw_for_y_lower_bound() {
41+
cameraRegions.getMeteringRectangleForPoint(new Size(10, 10), 0, -0.5);
42+
}
43+
44+
@Test(expected = IllegalStateException.class)
45+
public void getMeteringRectangleForPoint_should_throw_for_null_boundaries() {
46+
cameraRegions.getMeteringRectangleForPoint(null, 0, -0);
47+
}
48+
49+
@Test
50+
public void getMeteringRectangleForPoint_should_return_valid_MeteringRectangle() {
51+
MeteringRectangle r;
52+
// Center
53+
r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.5, 0.5);
54+
assertEquals(new MeteringRectangle(45, 23, 10, 5, 1), r);
55+
56+
// Top left
57+
r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 0.0);
58+
assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), r);
59+
60+
// Bottom right
61+
r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 1.0);
62+
assertEquals(new MeteringRectangle(89, 44, 10, 5, 1), r);
63+
64+
// Top left
65+
r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 0.0, 1.0);
66+
assertEquals(new MeteringRectangle(0, 44, 10, 5, 1), r);
67+
68+
// Top right
69+
r = cameraRegions.getMeteringRectangleForPoint(cameraRegions.getMaxBoundaries(), 1.0, 0.0);
70+
assertEquals(new MeteringRectangle(89, 0, 10, 5, 1), r);
71+
}
72+
73+
@Test(expected = AssertionError.class)
74+
public void constructor_should_throw_for_0_width_boundary() {
75+
new CameraRegions(new Size(0, 50));
76+
}
77+
78+
@Test(expected = AssertionError.class)
79+
public void constructor_should_throw_for_0_height_boundary() {
80+
new CameraRegions(new Size(100, 0));
81+
}
82+
83+
@Test
84+
public void constructor_should_initialize() {
85+
CameraRegions cr = new CameraRegions(new Size(100, 50));
86+
assertEquals(new Size(100, 50), cr.getMaxBoundaries());
87+
assertNull(cr.getAEMeteringRectangle());
88+
}
89+
90+
@Test
91+
public void setAutoExposureMeteringRectangleFromPoint_should_set_aeMeteringRectangle_for_point() {
92+
CameraRegions cr = new CameraRegions(new Size(100, 50));
93+
cr.setAutoExposureMeteringRectangleFromPoint(0, 0);
94+
assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAEMeteringRectangle());
95+
}
96+
97+
@Test
98+
public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle() {
99+
CameraRegions cr = new CameraRegions(new Size(100, 50));
100+
cr.setAutoExposureMeteringRectangleFromPoint(0, 0);
101+
assertNotNull(cr.getAEMeteringRectangle());
102+
cr.resetAutoExposureMeteringRectangle();
103+
assertNull(cr.getAEMeteringRectangle());
104+
}
105+
}

0 commit comments

Comments
 (0)