Skip to content

Commit

Permalink
[camera] Add iOS and Android implementations for managing auto focus. (
Browse files Browse the repository at this point in the history
…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 bdeed1d.

* 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 <maurits@baseflow.com>
  • Loading branch information
2 people authored and adsonpleal committed Feb 26, 2021
1 parent 2525fdc commit e52a823
Show file tree
Hide file tree
Showing 30 changed files with 591 additions and 37 deletions.
5 changes: 5 additions & 0 deletions packages/camera/camera/CHANGELOG.md
@@ -1,6 +1,11 @@
## 0.6.6

* Adds auto focus support for Android and iOS implementations.

## 0.6.5

* Adds ImageFormat selection for ImageStream and Video(iOS only).

## 0.6.4+5

* Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets.
Expand Down
@@ -1,3 +1,7 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import static android.view.OrientationEventListener.ORIENTATION_UNKNOWN;
Expand Down Expand Up @@ -47,6 +51,7 @@
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
import io.flutter.plugins.camera.types.ExposureMode;
import io.flutter.plugins.camera.types.FlashMode;
import io.flutter.plugins.camera.types.FocusMode;
import io.flutter.plugins.camera.types.ResolutionPreset;
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
import java.io.File;
Expand Down Expand Up @@ -95,6 +100,7 @@ public class Camera {
private int currentOrientation = ORIENTATION_UNKNOWN;
private FlashMode flashMode;
private ExposureMode exposureMode;
private FocusMode focusMode;
private PictureCaptureRequest pictureCaptureRequest;
private CameraRegions cameraRegions;
private int exposureOffset;
Expand Down Expand Up @@ -128,6 +134,7 @@ public Camera(
this.applicationContext = activity.getApplicationContext();
this.flashMode = FlashMode.auto;
this.exposureMode = ExposureMode.auto;
this.focusMode = FocusMode.auto;
this.exposureOffset = 0;
orientationEventListener =
new OrientationEventListener(activity.getApplicationContext()) {
Expand Down Expand Up @@ -168,7 +175,7 @@ private void initFps(CameraCharacteristics cameraCharacteristics) {
int upper = range.getUpper();
Log.i("Camera", "[FPS Range Available] is:" + range);
if (upper >= 10) {
if (fpsRange == null || upper < fpsRange.getUpper()) {
if (fpsRange == null || upper > fpsRange.getUpper()) {
fpsRange = range;
}
}
Expand Down Expand Up @@ -221,7 +228,9 @@ public void onOpened(@NonNull CameraDevice device) {
previewSize.getWidth(),
previewSize.getHeight(),
exposureMode,
isExposurePointSupported());
focusMode,
isExposurePointSupported(),
isFocusPointSupported());
} catch (CameraAccessException e) {
dartMessenger.sendCameraErrorEvent(e.getMessage());
close();
Expand Down Expand Up @@ -309,7 +318,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
cameraCaptureSession = session;

updateFpsRange();
updateAutoFocus();
updateFocus(focusMode);
updateFlash(flashMode);
updateExposure(exposureMode);

Expand Down Expand Up @@ -510,7 +519,7 @@ private void runPictureAutoFocus() {
assert (pictureCaptureRequest != null);

pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing);
lockAutoFocus();
lockAutoFocus(pictureCaptureCallback);
}

private void runPicturePreCapture() {
Expand Down Expand Up @@ -570,7 +579,7 @@ public void onCaptureCompleted(
}
}

private void lockAutoFocus() {
private void lockAutoFocus(CaptureCallback callback) {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);

Expand All @@ -581,7 +590,7 @@ private void lockAutoFocus() {
private void unlockAutoFocus() {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
updateAutoFocus();
updateFocus(focusMode);
try {
cameraCaptureSession.capture(captureRequestBuilder.build(), null, null);
} catch (CameraAccessException ignored) {
Expand Down Expand Up @@ -764,25 +773,72 @@ public void setExposurePoint(@NonNull final Result result, Double x, Double y)
"setExposurePointFailed", "Device does not have exposure point capabilities", null);
return;
}
// Check if we are doing a reset or not
if (x == null || y == null) {
x = 0.5;
y = 0.5;
}
// Get the current region boundaries.
Size maxBoundaries = getRegionBoundaries();
if (maxBoundaries == null) {
// Check if the current region boundaries are known
if (cameraRegions.getMaxBoundaries() == null) {
result.error("setExposurePointFailed", "Could not determine max region boundaries", null);
return;
}
// Set the metering rectangle
cameraRegions.setAutoExposureMeteringRectangleFromPoint(x, y);
if (x == null || y == null) cameraRegions.resetAutoExposureMeteringRectangle();
else cameraRegions.setAutoExposureMeteringRectangleFromPoint(x, y);
// Apply it
updateExposure(exposureMode);
refreshPreviewCaptureSession(
() -> result.success(null), (code, message) -> result.error("CameraAccess", message, null));
}

public void setFocusMode(@NonNull final Result result, FocusMode mode)
throws CameraAccessException {
this.focusMode = mode;

updateFocus(mode);

switch (mode) {
case auto:
refreshPreviewCaptureSession(
null, (code, message) -> result.error("setFocusMode", message, null));
break;
case locked:
lockAutoFocus(
new CaptureCallback() {
@Override
public void onCaptureCompleted(
@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
unlockAutoFocus();
}
});
break;
}
result.success(null);
}

public void setFocusPoint(@NonNull final Result result, Double x, Double y)
throws CameraAccessException {
// Check if focus point functionality is available.
if (!isFocusPointSupported()) {
result.error("setFocusPointFailed", "Device does not have focus point capabilities", null);
return;
}

// Check if the current region boundaries are known
if (cameraRegions.getMaxBoundaries() == null) {
result.error("setFocusPointFailed", "Could not determine max region boundaries", null);
return;
}

// Set the metering rectangle
if (x == null || y == null) {
cameraRegions.resetAutoFocusMeteringRectangle();
} else {
cameraRegions.setAutoFocusMeteringRectangleFromPoint(x, y);
}

// Apply the new metering rectangle
setFocusMode(result, focusMode);
}

@TargetApi(VERSION_CODES.P)
private boolean supportsDistortionCorrection() throws CameraAccessException {
int[] availableDistortionCorrectionModes =
Expand Down Expand Up @@ -832,6 +888,14 @@ private boolean isExposurePointSupported() throws CameraAccessException {
return supportedRegions != null && supportedRegions > 0;
}

private boolean isFocusPointSupported() throws CameraAccessException {
Integer supportedRegions =
cameraManager
.getCameraCharacteristics(cameraDevice.getId())
.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
return supportedRegions != null && supportedRegions > 0;
}

public double getMinExposureOffset() throws CameraAccessException {
Range<Integer> range =
cameraManager
Expand Down Expand Up @@ -912,7 +976,7 @@ private void updateFpsRange() {
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
}

private void updateAutoFocus() {
private void updateFocus(FocusMode mode) {
if (useAutoFocus) {
int[] modes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
// Auto focus is not supported
Expand All @@ -923,8 +987,25 @@ private void updateAutoFocus() {
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
} else {
// Applying auto focus
switch (mode) {
case locked:
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
break;
case auto:
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE,
recordingVideo
? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
: CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
default:
break;
}
MeteringRectangle afRect = cameraRegions.getAFMeteringRectangle();
captureRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
CaptureRequest.CONTROL_AF_REGIONS,
afRect == null ? null : new MeteringRectangle[] {afRect});
}
} else {
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
Expand Down
@@ -1,3 +1,7 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.Manifest;
Expand Down
@@ -1,10 +1,15 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.hardware.camera2.params.MeteringRectangle;
import android.util.Size;

public final class CameraRegions {
private MeteringRectangle aeMeteringRectangle;
private MeteringRectangle afMeteringRectangle;
private Size maxBoundaries;

public CameraRegions(Size maxBoundaries) {
Expand All @@ -17,6 +22,10 @@ public MeteringRectangle getAEMeteringRectangle() {
return aeMeteringRectangle;
}

public MeteringRectangle getAFMeteringRectangle() {
return afMeteringRectangle;
}

public Size getMaxBoundaries() {
return this.maxBoundaries;
}
Expand All @@ -29,6 +38,14 @@ public void setAutoExposureMeteringRectangleFromPoint(double x, double y) {
this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
}

public void resetAutoFocusMeteringRectangle() {
this.afMeteringRectangle = null;
}

public void setAutoFocusMeteringRectangleFromPoint(double x, double y) {
this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
}

public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) {
assert (x >= 0 && x <= 1);
assert (y >= 0 && y <= 1);
Expand Down
@@ -1,3 +1,7 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.app.Activity;
Expand Down
@@ -1,3 +1,7 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.graphics.Rect;
Expand Down
@@ -1,10 +1,15 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.text.TextUtils;
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.camera.types.ExposureMode;
import io.flutter.plugins.camera.types.FocusMode;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -25,19 +30,25 @@ void sendCameraInitializedEvent(
Integer previewWidth,
Integer previewHeight,
ExposureMode exposureMode,
Boolean exposurePointSupported) {
FocusMode focusMode,
Boolean exposurePointSupported,
Boolean focusPointSupported) {
assert (previewWidth != null);
assert (previewHeight != null);
assert (exposureMode != null);
assert (focusMode != null);
assert (exposurePointSupported != null);
assert (focusPointSupported != null);
this.send(
EventType.INITIALIZED,
new HashMap<String, Object>() {
{
put("previewWidth", previewWidth.doubleValue());
put("previewHeight", previewHeight.doubleValue());
put("exposureMode", exposureMode.toString());
put("focusMode", focusMode.toString());
put("exposurePointSupported", exposurePointSupported);
put("focusPointSupported", focusPointSupported);
}
});
}
Expand Down
@@ -1,3 +1,7 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.app.Activity;
Expand All @@ -12,6 +16,7 @@
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
import io.flutter.plugins.camera.types.ExposureMode;
import io.flutter.plugins.camera.types.FlashMode;
import io.flutter.plugins.camera.types.FocusMode;
import io.flutter.view.TextureRegistry;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -206,6 +211,37 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
}
break;
}
case "setFocusMode":
{
String modeStr = call.argument("mode");
FocusMode mode = FocusMode.getValueForString(modeStr);
if (mode == null) {
result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null);
return;
}
try {
camera.setFocusMode(result, mode);
} catch (Exception e) {
handleException(e, result);
}
break;
}
case "setFocusPoint":
{
Boolean reset = call.argument("reset");
Double x = null;
Double y = null;
if (reset == null || !reset) {
x = call.argument("x");
y = call.argument("y");
}
try {
camera.setFocusPoint(result, x, y);
} catch (Exception e) {
handleException(e, result);
}
break;
}
case "startImageStream":
{
try {
Expand Down

0 comments on commit e52a823

Please sign in to comment.