Skip to content

Commit 71a8317

Browse files
[camera] Add iOS and Android implementations for managing auto focus. (flutter#3370)
* 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 * Added platform interface methods for managing auto focus. * Formatted code * Export focus mode * Add Android and iOS implementations (WIP) * Update platform interface for changes to autofocus methods * WIP * Revert "Update platform interface for changes to autofocus methods" This reverts commit bdeed1d213a9f106d0bd80b8905c0ae3af29886e. * Finish android implementation * Fix iOS implementation * iOS fix for setting the exposure point * Removed unnecessary check * Updated changelog and pubspec.yaml * Updated changelog and pubspec.yaml * Update platform interface dependency * Implement PR feedback * Restore test * Revert test change * Update camera pubspec * Update platform interface to prevent breaking changes with current master * Update test to match platform interface updates * Code format * Fixed compilation error * Fix formatting * Add missing license headers to java source files. * Update platform interface dependency * Change fps range determination * Fix analysis warnings Co-authored-by: Maurits van Beusekom <[email protected]>
1 parent da1b463 commit 71a8317

30 files changed

+591
-37
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
## 0.6.6
2+
3+
* Adds auto focus support for Android and iOS implementations.
4+
15
## 0.6.5
26

37
* Adds ImageFormat selection for ImageStream and Video(iOS only).
8+
49
## 0.6.4+5
510

611
* Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets.

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

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN;
@@ -47,6 +51,7 @@
4751
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
4852
import io.flutter.plugins.camera.types.ExposureMode;
4953
import io.flutter.plugins.camera.types.FlashMode;
54+
import io.flutter.plugins.camera.types.FocusMode;
5055
import io.flutter.plugins.camera.types.ResolutionPreset;
5156
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
5257
import java.io.File;
@@ -95,6 +100,7 @@ public class Camera {
95100
private int currentOrientation = ORIENTATION_UNKNOWN;
96101
private FlashMode flashMode;
97102
private ExposureMode exposureMode;
103+
private FocusMode focusMode;
98104
private PictureCaptureRequest pictureCaptureRequest;
99105
private CameraRegions cameraRegions;
100106
private int exposureOffset;
@@ -128,6 +134,7 @@ public Camera(
128134
this.applicationContext = activity.getApplicationContext();
129135
this.flashMode = FlashMode.auto;
130136
this.exposureMode = ExposureMode.auto;
137+
this.focusMode = FocusMode.auto;
131138
this.exposureOffset = 0;
132139
orientationEventListener =
133140
new OrientationEventListener(activity.getApplicationContext()) {
@@ -168,7 +175,7 @@ private void initFps(CameraCharacteristics cameraCharacteristics) {
168175
int upper = range.getUpper();
169176
Log.i("Camera", "[FPS Range Available] is:" + range);
170177
if (upper >= 10) {
171-
if (fpsRange == null || upper < fpsRange.getUpper()) {
178+
if (fpsRange == null || upper > fpsRange.getUpper()) {
172179
fpsRange = range;
173180
}
174181
}
@@ -221,7 +228,9 @@ public void onOpened(@NonNull CameraDevice device) {
221228
previewSize.getWidth(),
222229
previewSize.getHeight(),
223230
exposureMode,
224-
isExposurePointSupported());
231+
focusMode,
232+
isExposurePointSupported(),
233+
isFocusPointSupported());
225234
} catch (CameraAccessException e) {
226235
dartMessenger.sendCameraErrorEvent(e.getMessage());
227236
close();
@@ -309,7 +318,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
309318
cameraCaptureSession = session;
310319

311320
updateFpsRange();
312-
updateAutoFocus();
321+
updateFocus(focusMode);
313322
updateFlash(flashMode);
314323
updateExposure(exposureMode);
315324

@@ -510,7 +519,7 @@ private void runPictureAutoFocus() {
510519
assert (pictureCaptureRequest != null);
511520

512521
pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
513-
lockAutoFocus();
522+
lockAutoFocus(pictureCaptureCallback);
514523
}
515524

516525
private void runPicturePreCapture() {
@@ -570,7 +579,7 @@ public void onCaptureCompleted(
570579
}
571580
}
572581

573-
private void lockAutoFocus() {
582+
private void lockAutoFocus(CaptureCallback callback) {
574583
captureRequestBuilder.set(
575584
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
576585

@@ -581,7 +590,7 @@ private void lockAutoFocus() {
581590
private void unlockAutoFocus() {
582591
captureRequestBuilder.set(
583592
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
584-
updateAutoFocus();
593+
updateFocus(focusMode);
585594
try {
586595
cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
587596
} catch (CameraAccessException ignored) {
@@ -764,25 +773,72 @@ public void setExposurePoint(@NonNull final Result result, Double x, Double y)
764773
"setExposurePointFailed", "Device does not have exposure point capabilities", null);
765774
return;
766775
}
767-
// Check if we are doing a reset or not
768-
if (x == null || y == null) {
769-
x = 0.5;
770-
y = 0.5;
771-
}
772-
// Get the current region boundaries.
773-
Size maxBoundaries = getRegionBoundaries();
774-
if (maxBoundaries == null) {
776+
// Check if the current region boundaries are known
777+
if (cameraRegions.getMaxBoundaries() == null) {
775778
result.error("setExposurePointFailed", "Could not determine max region boundaries", null);
776779
return;
777780
}
778781
// Set the metering rectangle
779-
cameraRegions.setAutoExposureMeteringRectangleFromPoint(x, y);
782+
if (x == null || y == null) cameraRegions.resetAutoExposureMeteringRectangle();
783+
else cameraRegions.setAutoExposureMeteringRectangleFromPoint(x, y);
780784
// Apply it
781785
updateExposure(exposureMode);
782786
refreshPreviewCaptureSession(
783787
() -> result.success(null), (code, message) -> result.error("CameraAccess", message, null));
784788
}
785789

790+
public void setFocusMode(@NonNull final Result result, FocusMode mode)
791+
throws CameraAccessException {
792+
this.focusMode = mode;
793+
794+
updateFocus(mode);
795+
796+
switch (mode) {
797+
case auto:
798+
refreshPreviewCaptureSession(
799+
null, (code, message) -> result.error("setFocusMode", message, null));
800+
break;
801+
case locked:
802+
lockAutoFocus(
803+
new CaptureCallback() {
804+
@Override
805+
public void onCaptureCompleted(
806+
@NonNull CameraCaptureSession session,
807+
@NonNull CaptureRequest request,
808+
@NonNull TotalCaptureResult result) {
809+
unlockAutoFocus();
810+
}
811+
});
812+
break;
813+
}
814+
result.success(null);
815+
}
816+
817+
public void setFocusPoint(@NonNull final Result result, Double x, Double y)
818+
throws CameraAccessException {
819+
// Check if focus point functionality is available.
820+
if (!isFocusPointSupported()) {
821+
result.error("setFocusPointFailed", "Device does not have focus point capabilities", null);
822+
return;
823+
}
824+
825+
// Check if the current region boundaries are known
826+
if (cameraRegions.getMaxBoundaries() == null) {
827+
result.error("setFocusPointFailed", "Could not determine max region boundaries", null);
828+
return;
829+
}
830+
831+
// Set the metering rectangle
832+
if (x == null || y == null) {
833+
cameraRegions.resetAutoFocusMeteringRectangle();
834+
} else {
835+
cameraRegions.setAutoFocusMeteringRectangleFromPoint(x, y);
836+
}
837+
838+
// Apply the new metering rectangle
839+
setFocusMode(result, focusMode);
840+
}
841+
786842
@TargetApi(VERSION_CODES.P)
787843
private boolean supportsDistortionCorrection() throws CameraAccessException {
788844
int[] availableDistortionCorrectionModes =
@@ -832,6 +888,14 @@ private boolean isExposurePointSupported() throws CameraAccessException {
832888
return supportedRegions != null && supportedRegions > 0;
833889
}
834890

891+
private boolean isFocusPointSupported() throws CameraAccessException {
892+
Integer supportedRegions =
893+
cameraManager
894+
.getCameraCharacteristics(cameraDevice.getId())
895+
.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
896+
return supportedRegions != null && supportedRegions > 0;
897+
}
898+
835899
public double getMinExposureOffset() throws CameraAccessException {
836900
Range<Integer> range =
837901
cameraManager
@@ -912,7 +976,7 @@ private void updateFpsRange() {
912976
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
913977
}
914978

915-
private void updateAutoFocus() {
979+
private void updateFocus(FocusMode mode) {
916980
if (useAutoFocus) {
917981
int[] modes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
918982
// Auto focus is not supported
@@ -923,8 +987,25 @@ private void updateAutoFocus() {
923987
captureRequestBuilder.set(
924988
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
925989
} else {
990+
// Applying auto focus
991+
switch (mode) {
992+
case locked:
993+
captureRequestBuilder.set(
994+
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
995+
break;
996+
case auto:
997+
captureRequestBuilder.set(
998+
CaptureRequest.CONTROL_AF_MODE,
999+
recordingVideo
1000+
? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
1001+
: CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
1002+
default:
1003+
break;
1004+
}
1005+
MeteringRectangle afRect = cameraRegions.getAFMeteringRectangle();
9261006
captureRequestBuilder.set(
927-
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
1007+
CaptureRequest.CONTROL_AF_REGIONS,
1008+
afRect == null ? null : new MeteringRectangle[] {afRect});
9281009
}
9291010
} else {
9301011
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.Manifest;

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.hardware.camera2.params.MeteringRectangle;
48
import android.util.Size;
59

610
public final class CameraRegions {
711
private MeteringRectangle aeMeteringRectangle;
12+
private MeteringRectangle afMeteringRectangle;
813
private Size maxBoundaries;
914

1015
public CameraRegions(Size maxBoundaries) {
@@ -17,6 +22,10 @@ public MeteringRectangle getAEMeteringRectangle() {
1722
return aeMeteringRectangle;
1823
}
1924

25+
public MeteringRectangle getAFMeteringRectangle() {
26+
return afMeteringRectangle;
27+
}
28+
2029
public Size getMaxBoundaries() {
2130
return this.maxBoundaries;
2231
}
@@ -29,6 +38,14 @@ public void setAutoExposureMeteringRectangleFromPoint(double x, double y) {
2938
this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
3039
}
3140

41+
public void resetAutoFocusMeteringRectangle() {
42+
this.afMeteringRectangle = null;
43+
}
44+
45+
public void setAutoFocusMeteringRectangleFromPoint(double x, double y) {
46+
this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
47+
}
48+
3249
public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) {
3350
assert (x >= 0 && x <= 1);
3451
assert (y >= 0 && y <= 1);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.app.Activity;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.graphics.Rect;

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.text.TextUtils;
48
import androidx.annotation.Nullable;
59
import io.flutter.plugin.common.BinaryMessenger;
610
import io.flutter.plugin.common.MethodChannel;
711
import io.flutter.plugins.camera.types.ExposureMode;
12+
import io.flutter.plugins.camera.types.FocusMode;
813
import java.util.HashMap;
914
import java.util.Map;
1015

@@ -25,19 +30,25 @@ void sendCameraInitializedEvent(
2530
Integer previewWidth,
2631
Integer previewHeight,
2732
ExposureMode exposureMode,
28-
Boolean exposurePointSupported) {
33+
FocusMode focusMode,
34+
Boolean exposurePointSupported,
35+
Boolean focusPointSupported) {
2936
assert (previewWidth != null);
3037
assert (previewHeight != null);
3138
assert (exposureMode != null);
39+
assert (focusMode != null);
3240
assert (exposurePointSupported != null);
41+
assert (focusPointSupported != null);
3342
this.send(
3443
EventType.INITIALIZED,
3544
new HashMap<String, Object>() {
3645
{
3746
put("previewWidth", previewWidth.doubleValue());
3847
put("previewHeight", previewHeight.doubleValue());
3948
put("exposureMode", exposureMode.toString());
49+
put("focusMode", focusMode.toString());
4050
put("exposurePointSupported", exposurePointSupported);
51+
put("focusPointSupported", focusPointSupported);
4152
}
4253
});
4354
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2019 The Chromium 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+
15
package io.flutter.plugins.camera;
26

37
import android.app.Activity;
@@ -12,6 +16,7 @@
1216
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
1317
import io.flutter.plugins.camera.types.ExposureMode;
1418
import io.flutter.plugins.camera.types.FlashMode;
19+
import io.flutter.plugins.camera.types.FocusMode;
1520
import io.flutter.view.TextureRegistry;
1621
import java.util.HashMap;
1722
import java.util.Map;
@@ -206,6 +211,37 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
206211
}
207212
break;
208213
}
214+
case "setFocusMode":
215+
{
216+
String modeStr = call.argument("mode");
217+
FocusMode mode = FocusMode.getValueForString(modeStr);
218+
if (mode == null) {
219+
result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null);
220+
return;
221+
}
222+
try {
223+
camera.setFocusMode(result, mode);
224+
} catch (Exception e) {
225+
handleException(e, result);
226+
}
227+
break;
228+
}
229+
case "setFocusPoint":
230+
{
231+
Boolean reset = call.argument("reset");
232+
Double x = null;
233+
Double y = null;
234+
if (reset == null || !reset) {
235+
x = call.argument("x");
236+
y = call.argument("y");
237+
}
238+
try {
239+
camera.setFocusPoint(result, x, y);
240+
} catch (Exception e) {
241+
handleException(e, result);
242+
}
243+
break;
244+
}
209245
case "startImageStream":
210246
{
211247
try {

0 commit comments

Comments
 (0)