From e52a8232eb16f74f7b5e61d4846226b548a232c2 Mon Sep 17 00:00:00 2001 From: Bodhi Mulders Date: Mon, 11 Jan 2021 17:20:24 +0100 Subject: [PATCH] [camera] Add iOS and Android implementations for managing auto focus. (#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 --- packages/camera/camera/CHANGELOG.md | 5 + .../io/flutter/plugins/camera/Camera.java | 115 ++++++++++++++--- .../plugins/camera/CameraPermissions.java | 4 + .../flutter/plugins/camera/CameraRegions.java | 17 +++ .../flutter/plugins/camera/CameraUtils.java | 4 + .../io/flutter/plugins/camera/CameraZoom.java | 4 + .../flutter/plugins/camera/DartMessenger.java | 13 +- .../plugins/camera/MethodCallHandlerImpl.java | 36 ++++++ .../plugins/camera/PictureCaptureRequest.java | 4 + .../camera/media/MediaRecorderBuilder.java | 1 + .../plugins/camera/types/ExposureMode.java | 4 + .../plugins/camera/types/FlashMode.java | 4 + .../plugins/camera/types/FocusMode.java | 29 +++++ .../camera/types/ResolutionPreset.java | 4 + .../plugins/camera/CameraPermissionsTest.java | 4 + .../plugins/camera/CameraRegionsTest.java | 21 ++++ .../plugins/camera/CameraZoomTest.java | 4 + .../plugins/camera/DartMessengerTest.java | 9 +- .../camera/PictureCaptureRequestTest.java | 4 + .../media/MediaRecorderBuilderTest.java | 4 + .../camera/types/ExposureModeTest.java | 4 + .../plugins/camera/types/FlashModeTest.java | 4 + .../plugins/camera/types/FocusModeTest.java | 34 +++++ packages/camera/camera/example/lib/main.dart | 104 ++++++++++++++- .../camera/camera/ios/Classes/CameraPlugin.m | 118 ++++++++++++++++-- packages/camera/camera/lib/camera.dart | 1 + .../camera/lib/src/camera_controller.dart | 53 +++++++- packages/camera/camera/pubspec.yaml | 4 +- packages/camera/camera/test/camera_test.dart | 11 +- .../camera/camera/test/camera_value_test.dart | 5 +- 30 files changed, 591 insertions(+), 37 deletions(-) create mode 100644 packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java create mode 100644 packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 0ba77e5ee1d5..1a2b03d93a6a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/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. diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index fd7f4d67fa04..3fc702a2a879 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -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; @@ -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; @@ -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; @@ -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()) { @@ -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; } } @@ -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(); @@ -309,7 +318,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession = session; updateFpsRange(); - updateAutoFocus(); + updateFocus(focusMode); updateFlash(flashMode); updateExposure(exposureMode); @@ -510,7 +519,7 @@ private void runPictureAutoFocus() { assert (pictureCaptureRequest != null); pictureCaptureRequest.setState(PictureCaptureRequest.State.focusing); - lockAutoFocus(); + lockAutoFocus(pictureCaptureCallback); } private void runPicturePreCapture() { @@ -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); @@ -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) { @@ -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 = @@ -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 range = cameraManager @@ -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 @@ -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); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java index b4569d2fec07..3529e69a2b0b 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java @@ -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; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java index 2285f67ad25c..04412a56631f 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraRegions.java @@ -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.hardware.camera2.params.MeteringRectangle; @@ -5,6 +9,7 @@ public final class CameraRegions { private MeteringRectangle aeMeteringRectangle; + private MeteringRectangle afMeteringRectangle; private Size maxBoundaries; public CameraRegions(Size maxBoundaries) { @@ -17,6 +22,10 @@ public MeteringRectangle getAEMeteringRectangle() { return aeMeteringRectangle; } + public MeteringRectangle getAFMeteringRectangle() { + return afMeteringRectangle; + } + public Size getMaxBoundaries() { return this.maxBoundaries; } @@ -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); diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index 3b665d6b24f2..6f04dc80e102 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -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; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java index a179f12db224..5eed9f4734b7 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraZoom.java @@ -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; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java index 2fee13816b51..ec68ac0acda3 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java @@ -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.text.TextUtils; @@ -5,6 +9,7 @@ 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; @@ -25,11 +30,15 @@ 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() { @@ -37,7 +46,9 @@ void sendCameraInitializedEvent( put("previewWidth", previewWidth.doubleValue()); put("previewHeight", previewHeight.doubleValue()); put("exposureMode", exposureMode.toString()); + put("focusMode", focusMode.toString()); put("exposurePointSupported", exposurePointSupported); + put("focusPointSupported", focusPointSupported); } }); } diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 95c0b198e43d..36048dbb5176 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -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; @@ -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; @@ -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 { diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java index 1103b8583ad6..189f2f1490dc 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java @@ -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 androidx.annotation.Nullable; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index b2309c83a4a5..4c3fb3add230 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -1,6 +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.media; import android.media.CamcorderProfile; diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java index 8066f59d2b14..595206fa2216 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java @@ -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.types; // Mirrors exposure_mode.dart diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java index ee6fe489511f..c4f0998c418a 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java @@ -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.types; // Mirrors flash_mode.dart diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java new file mode 100644 index 000000000000..b0dba047f7eb --- /dev/null +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java @@ -0,0 +1,29 @@ +// 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.types; + +// Mirrors focus_mode.dart +public enum FocusMode { + auto("auto"), + locked("locked"); + + private final String strValue; + + FocusMode(String strValue) { + this.strValue = strValue; + } + + public static FocusMode getValueForString(String modeStr) { + for (FocusMode value : values()) { + if (value.strValue.equals(modeStr)) return value; + } + return null; + } + + @Override + public String toString() { + return strValue; + } +} diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java index ffbe2e62095d..1508dcefb293 100644 --- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java +++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java @@ -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.types; // Mirrors camera.dart diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java index b622c313258a..2b19b5dbb0d6 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java @@ -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 junit.framework.TestCase.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java index ca66918e2493..99745e56a857 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraRegionsTest.java @@ -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 org.junit.Assert.assertEquals; @@ -85,6 +89,7 @@ public void constructor_should_initialize() { CameraRegions cr = new CameraRegions(new Size(100, 50)); assertEquals(new Size(100, 50), cr.getMaxBoundaries()); assertNull(cr.getAEMeteringRectangle()); + assertNull(cr.getAFMeteringRectangle()); } @Test @@ -102,4 +107,20 @@ public void resetAutoExposureMeteringRectangle_should_reset_aeMeteringRectangle( cr.resetAutoExposureMeteringRectangle(); assertNull(cr.getAEMeteringRectangle()); } + + @Test + public void setAutoFocusMeteringRectangleFromPoint_should_set_afMeteringRectangle_for_point() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoFocusMeteringRectangleFromPoint(0, 0); + assertEquals(new MeteringRectangle(0, 0, 10, 5, 1), cr.getAFMeteringRectangle()); + } + + @Test + public void resetAutoFocusMeteringRectangle_should_reset_afMeteringRectangle() { + CameraRegions cr = new CameraRegions(new Size(100, 50)); + cr.setAutoFocusMeteringRectangleFromPoint(0, 0); + assertNotNull(cr.getAFMeteringRectangle()); + cr.resetAutoFocusMeteringRectangle(); + assertNull(cr.getAFMeteringRectangle()); + } } diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java index 93aaa5d926b4..8f05da71b5c5 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraZoomTest.java @@ -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 org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java index f91bf82c7063..64425b7b8283 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java @@ -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 junit.framework.TestCase.assertNull; @@ -8,6 +12,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugins.camera.types.ExposureMode; +import io.flutter.plugins.camera.types.FocusMode; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -59,7 +64,7 @@ public void sendCameraErrorEvent_includesErrorDescriptions() { @Test public void sendCameraInitializedEvent_includesPreviewSize() { - dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, true); + dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.auto, true, true); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); @@ -68,7 +73,9 @@ public void sendCameraInitializedEvent_includesPreviewSize() { assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); assertEquals("ExposureMode auto", call.argument("exposureMode"), "auto"); + assertEquals("FocusMode continuous", call.argument("focusMode"), "auto"); assertEquals("exposurePointSupported", call.argument("exposurePointSupported"), true); + assertEquals("focusPointSupported", call.argument("focusPointSupported"), true); } @Test diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java index 2356b306c6c4..3ede0b7abe3a 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java @@ -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 org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java index 622b49b660a2..823975803994 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java @@ -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.media; import static org.junit.Assert.assertNotNull; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java index 28d2343cedcd..63810f0b5684 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java @@ -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.types; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java index bba01836545a..1f5f0c6272ed 100644 --- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java @@ -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.types; import static org.junit.Assert.assertEquals; diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java new file mode 100644 index 000000000000..4aa6fadf776b --- /dev/null +++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java @@ -0,0 +1,34 @@ +// 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.types; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class FocusModeTest { + + @Test + public void getValueForString_returns_correct_values() { + assertEquals( + "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto); + assertEquals( + "Returns FocusMode.locked for 'locked'", + FocusMode.getValueForString("locked"), + FocusMode.locked); + } + + @Test + public void getValueForString_returns_null_for_nonexistant_value() { + assertEquals( + "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null); + } + + @Test + public void toString_returns_correct_value() { + assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto"); + assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked"); + } +} diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 6eaf66a256de..681a45172816 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -49,6 +49,8 @@ class _CameraExampleHomeState extends State Animation _flashModeControlRowAnimation; AnimationController _exposureModeControlRowAnimationController; Animation _exposureModeControlRowAnimation; + AnimationController _focusModeControlRowAnimationController; + Animation _focusModeControlRowAnimation; double _minAvailableZoom; double _maxAvailableZoom; double _currentScale = 1.0; @@ -77,6 +79,14 @@ class _CameraExampleHomeState extends State parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); + _focusModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _focusModeControlRowAnimation = CurvedAnimation( + parent: _focusModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); } @override @@ -249,6 +259,11 @@ class _CameraExampleHomeState extends State onPressed: controller != null ? onExposureModeButtonPressed : null, ), + IconButton( + icon: Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: controller != null ? onFocusModeButtonPressed : null, + ), IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, @@ -258,6 +273,7 @@ class _CameraExampleHomeState extends State ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), + _focusModeControlRowWidget(), ], ); } @@ -323,6 +339,7 @@ class _CameraExampleHomeState extends State ? Colors.orange : Colors.blue, ); + return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( @@ -387,6 +404,59 @@ class _CameraExampleHomeState extends State ); } + Widget _focusModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + primary: controller?.value?.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + primary: controller?.value?.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _focusModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + Center( + child: Text("Focus Mode"), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + children: [ + TextButton( + child: Text('AUTO'), + style: styleAuto, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.auto) + : null, + onLongPress: () { + if (controller != null) controller.setFocusPoint(null); + showInSnackBar('Resetting focus point'); + }, + ), + TextButton( + child: Text('LOCKED'), + style: styleLocked, + onPressed: controller != null + ? () => onSetFocusModeButtonPressed(FocusMode.locked) + : null, + ), + ], + ), + ], + ), + ), + ), + ); + } + /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { return Row( @@ -472,10 +542,12 @@ class _CameraExampleHomeState extends State } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { - controller.setExposurePoint(Offset( + final offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, - )); + ); + controller.setExposurePoint(offset); + controller.setFocusPoint(offset); } void onNewCameraSelected(CameraDescription cameraDescription) async { @@ -531,6 +603,7 @@ class _CameraExampleHomeState extends State } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); } } @@ -540,6 +613,17 @@ class _CameraExampleHomeState extends State } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onFocusModeButtonPressed() { + if (_focusModeControlRowAnimationController.value == 1) { + _focusModeControlRowAnimationController.reverse(); + } else { + _focusModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _exposureModeControlRowAnimationController.reverse(); } } @@ -564,6 +648,13 @@ class _CameraExampleHomeState extends State }); } + void onSetFocusModeButtonPressed(FocusMode mode) { + setFocusMode(mode).then((_) { + if (mounted) setState(() {}); + showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); + }); + } + void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) setState(() {}); @@ -683,6 +774,15 @@ class _CameraExampleHomeState extends State } } + Future setFocusMode(FocusMode mode) async { + try { + await controller.setFocusMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + Future _startVideoPlayer() async { final VideoPlayerController vController = VideoPlayerController.file(File(videoFile.path)); diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index d1ef0e5c923e..298b906ace7b 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -226,6 +226,44 @@ static ExposureMode getExposureModeForString(NSString *mode) { } } +// Mirrors FocusMode in camera.dart +typedef enum { + FocusModeAuto, + FocusModeLocked, +} FocusMode; + +static NSString *getStringForFocusMode(FocusMode mode) { + switch (mode) { + case FocusModeAuto: + return @"auto"; + case FocusModeLocked: + return @"locked"; + } + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown string for focus mode"] + }]; + @throw error; +} + +static FocusMode getFocusModeForString(NSString *mode) { + if ([mode isEqualToString:@"auto"]) { + return FocusModeAuto; + } else if ([mode isEqualToString:@"locked"]) { + return FocusModeLocked; + } else { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain + code:NSURLErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey : [NSString + stringWithFormat:@"Unknown focus mode %@", mode] + }]; + @throw error; + } +} + // Mirrors ResolutionPreset in camera.dart typedef enum { veryLow, @@ -294,6 +332,7 @@ @interface FLTCam : NSObject { )), exposureMode: await _initializeCompleter.future .then((event) => event.exposureMode), + focusMode: + await _initializeCompleter.future.then((event) => event.focusMode), exposurePointSupported: await _initializeCompleter.future .then((event) => event.exposurePointSupported), + focusPointSupported: await _initializeCompleter.future + .then((event) => event.focusPointSupported), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); @@ -699,6 +718,38 @@ class CameraController extends ValueNotifier { } } + /// Sets the focus mode for taking pictures. + Future setFocusMode(FocusMode mode) async { + try { + await CameraPlatform.instance.setFocusMode(_cameraId, mode); + value = value.copyWith(focusMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the focus point for automatically determining the focus value. + Future setFocusPoint(Offset point) async { + if (point != null && + (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError( + 'The values of point should be anywhere between (0,0) and (1,1).'); + } + try { + await CameraPlatform.instance.setFocusPoint( + _cameraId, + point == null + ? null + : Point( + point.dx, + point.dy, + ), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 7c275c2268cd..0b21497b5462 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -2,13 +2,13 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.6.5 +version: 0.6.6 homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera dependencies: flutter: sdk: flutter - camera_platform_interface: ^1.3.0 + camera_platform_interface: ^1.5.0 pedantic: ^1.8.0 dev_dependencies: diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 4f2109371392..1cea609d1741 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -28,8 +28,15 @@ get mockAvailableCameras => [ get mockInitializeCamera => 13; -get mockOnCameraInitializedEvent => - CameraInitializedEvent(13, 75, 75, ExposureMode.auto, true); +get mockOnCameraInitializedEvent => CameraInitializedEvent( + 13, + 75, + 75, + ExposureMode.auto, + true, + FocusMode.auto, + true, + ); get mockOnCameraClosingEvent => null; diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index d9193e212ea9..eb7927b9eb6c 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -106,11 +106,14 @@ void main() { isTakingPicture: false, isStreamingImages: false, flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + focusMode: FocusMode.auto, exposurePointSupported: true, + focusPointSupported: true, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: null, exposurePointSupported: true)'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true)'); }); }); }