From 65adb98cb54f588f1e54b7aad4ab64fceb0c7c57 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Mon, 10 Feb 2020 15:33:14 +1100 Subject: [PATCH 01/11] Added support for setting exposure compensation in the camera plugin --- .../io/flutter/plugins/camera/Camera.java | 61 ++++++++++++++++++- .../plugins/camera/MethodCallHandlerImpl.java | 19 +++++- packages/camera/ios/Classes/CameraPlugin.m | 42 ++++++++++++- packages/camera/lib/camera.dart | 51 ++++++++++++++++ 4 files changed, 170 insertions(+), 3 deletions(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 0fcda278d836..3b4f7b57be53 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -22,6 +22,7 @@ import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; +import android.util.Range; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; @@ -40,6 +41,8 @@ import java.util.Map; public class Camera { + private static final int DEFAULT_MIN_COMPENSATION = -10; + private static final int DEFAULT_MAX_COMPENSATION = 10; private final SurfaceTextureEntry flutterTexture; private final CameraManager cameraManager; private final OrientationEventListener orientationEventListener; @@ -50,6 +53,7 @@ public class Camera { private final Size previewSize; private final boolean enableAudio; + private int exposureCompensation; private CameraDevice cameraDevice; private CameraCaptureSession cameraCaptureSession; private ImageReader pictureImageReader; @@ -77,7 +81,8 @@ public Camera( final DartMessenger dartMessenger, final String cameraName, final String resolutionPreset, - final boolean enableAudio) + final boolean enableAudio, + final int exposureCompensation) throws CameraAccessException { if (activity == null) { throw new IllegalStateException("No activity available!"); @@ -85,6 +90,7 @@ public Camera( this.cameraName = cameraName; this.enableAudio = enableAudio; + this.exposureCompensation = exposureCompensation; this.flutterTexture = flutterTexture; this.dartMessenger = dartMessenger; this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); @@ -250,6 +256,8 @@ public void takePicture(String filePath, @NonNull final Result result) { captureBuilder.addTarget(pictureImageReader.getSurface()); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); + applyExposureCompensationRequest(captureBuilder, exposureCompensation); + cameraCaptureSession.capture( captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @@ -320,6 +328,9 @@ public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession = session; captureRequestBuilder.set( CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + + applyExposureCompensationRequest(captureRequestBuilder, exposureCompensation); + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); if (onSuccessCallback != null) { onSuccessCallback.run(); @@ -475,6 +486,54 @@ private void setImageStreamImageAvailableListener(final EventChannel.EventSink i null); } + public void applyExposureCompensation(@NonNull final Result result, int value) { + try { + applyExposureCompensationRequest(captureRequestBuilder, value); + + exposureCompensation = value; + cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); + + result.success(null); + } catch (Exception e) { + result.error("cameraExposureCompensationFailed", e.getMessage(), null); + } + } + + public double getMaxExposureTargetBias() { + Range exposureCompensationRange = getExposureCompensationRange(); + return exposureCompensationRange.getUpper().doubleValue(); + } + + public double getMinExposureTargetBias() { + Range exposureCompensationRange = getExposureCompensationRange(); + return exposureCompensationRange.getLower().doubleValue(); + } + + private Range getExposureCompensationRange() { + Range range = Range.create(DEFAULT_MIN_COMPENSATION, DEFAULT_MAX_COMPENSATION); + + try { + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); + range = characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); + } catch (CameraAccessException e) { + //In case of error we will send default range + e.printStackTrace(); + } + + return range; + } + + private void applyExposureCompensationRequest(CaptureRequest.Builder builderRequest, int value) { + Range exposureCompensationRange = getExposureCompensationRange(); + if (value > exposureCompensationRange.getUpper()) { + builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getUpper()); + } else if (value < exposureCompensationRange.getLower()) { + builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getLower()); + } else { + builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, value); + } + } + private void closeCaptureSession() { if (cameraCaptureSession != null) { cameraCaptureSession.close(); diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index cb58d19a9a02..728371b3bdde 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -123,6 +123,21 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) } break; } + case "applyExposureCompensation": + { + camera.applyExposureCompensation(result, call.argument("exposureCompensation")); + break; + } + case "getMaxExposureTargetBias": + { + result.success(camera.getMaxExposureTargetBias()); + break; + } + case "getMinExposureTargetBias": + { + result.success(camera.getMinExposureTargetBias()); + break; + } case "dispose": { if (camera != null) { @@ -145,6 +160,7 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce String cameraName = call.argument("cameraName"); String resolutionPreset = call.argument("resolutionPreset"); boolean enableAudio = call.argument("enableAudio"); + int exposureCompensation = call.argument("exposureCompensation"); TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id()); @@ -155,7 +171,8 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce dartMessenger, cameraName, resolutionPreset, - enableAudio); + enableAudio, + exposureCompensation); camera.open(result); } diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 42cdb6d5fdf9..56d1df9179a7 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -162,6 +162,7 @@ @interface FLTCam : NSObject *)messenger; - (void)stopImageStream; - (void)captureToFile:(NSString *)filename result:(FlutterResult)result; +- (void)applyExposureCompensation:(NSNumber*)exposureValue; @end @implementation FLTCam { @@ -216,6 +219,7 @@ @implementation FLTCam { - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio + exposureCompensation:(NSNumber *)exposureCompensation dispatchQueue:(dispatch_queue_t)dispatchQueue error:(NSError **)error { self = [super init]; @@ -261,6 +265,8 @@ - (instancetype)initWithCameraName:(NSString *)cameraName [_motionManager startAccelerometerUpdates]; [self setCaptureSessionPreset:_resolutionPreset]; + + [self applyExposureCompensation:exposureCompensation]; return self; } @@ -759,6 +765,31 @@ - (void)setUpCaptureSessionForAudio { } } } + +- (float)getMinExposureTargetBias { + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + return device.minExposureTargetBias; +} + +- (float)getMaxExposureTargetBias { + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + return device.maxExposureTargetBias; +} + +- (void)applyExposureCompensation:(NSNumber*)exposureValue { + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + [device lockForConfiguration:nil]; + + float minExposureTargetBias = [self getMinExposureTargetBias]; + float maxExposureTargetBias = [self getMaxExposureTargetBias]; + + int exposureTargetBias = MIN(exposureValue.intValue, (int)maxExposureTargetBias); + exposureTargetBias = MAX(exposureTargetBias, (int)minExposureTargetBias); + [device setExposureTargetBias:(float)exposureTargetBias completionHandler:^(CMTime syncTime) {}]; + + [device unlockForConfiguration]; +} + @end @interface CameraPlugin () @@ -832,10 +863,12 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re NSString *cameraName = call.arguments[@"cameraName"]; NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSNumber *exposureCompensation = call.arguments[@"exposureCompensation"]; NSError *error; FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName resolutionPreset:resolutionPreset enableAudio:[enableAudio boolValue] + exposureCompensation:exposureCompensation dispatchQueue:_dispatchQueue error:&error]; if (error) { @@ -871,7 +904,14 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else if ([@"stopImageStream" isEqualToString:call.method]) { [_camera stopImageStream]; result(nil); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + } else if ([@"applyExposureCompensation" isEqualToString:call.method]) { + [_camera applyExposureCompensation:call.arguments[@"exposureCompensation"]]; + result(nil); + } else if ([@"getMinExposureTargetBias" isEqualToString:call.method]) { + result(@([_camera getMinExposureTargetBias])); + } else if ([@"getMaxExposureTargetBias" isEqualToString:call.method]) { + result(@([_camera getMaxExposureTargetBias])); + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecording]; result(nil); } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index ce9fd9430dde..4e5313824674 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -245,6 +245,7 @@ class CameraController extends ValueNotifier { this.description, this.resolutionPreset, { this.enableAudio = true, + this.exposureCompensation = 0 }) : super(const CameraValue.uninitialized()); final CameraDescription description; @@ -253,6 +254,8 @@ class CameraController extends ValueNotifier { /// Whether to include audio when recording a video. final bool enableAudio; + final int exposureCompensation; + int _textureId; bool _isDisposed = false; StreamSubscription _eventSubscription; @@ -275,6 +278,7 @@ class CameraController extends ValueNotifier { 'cameraName': description.name, 'resolutionPreset': serializeResolutionPreset(resolutionPreset), 'enableAudio': enableAudio, + 'exposureCompensation': exposureCompensation }, ); _textureId = reply['textureId']; @@ -569,6 +573,53 @@ class CameraController extends ValueNotifier { } } + // Set the Exposure Compensation value + Future applyExposureCompensation({int exposureValue = 0}) async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'applyExposureCompensation was called on uninitialized CameraController', + ); + } + + try { + await _channel.invokeMethod( + 'applyExposureCompensation', {'exposureCompensation': exposureValue}); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + Future getMinExposureTargetBias() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'getMinExposureTargetBias was called on uninitialized CameraController', + ); + } + + try { + return await _channel.invokeMethod('getMinExposureTargetBias'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + Future getMaxExposureTargetBias() async { + if (!value.isInitialized || _isDisposed) { + throw CameraException( + 'Uninitialized CameraController.', + 'getMaxExposureTargetBias was called on uninitialized CameraController', + ); + } + + try { + return await _channel.invokeMethod('getMaxExposureTargetBias'); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Releases the resources of this camera. @override Future dispose() async { From 7a8568504d15a073b925c248037a03dbcc05b670 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Mon, 10 Feb 2020 18:00:59 +1100 Subject: [PATCH 02/11] Added exposure compensation slider to example project of the camera plugin --- packages/camera/example/lib/main.dart | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index ce8d37457123..27b071044212 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -37,12 +37,19 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { + static const double initialExposureCompensation = 0.0; + static const double initialMinExposureTargetBias = -10.0; + static const double initialMaxExposureTargetBias = 10.0; CameraController controller; String imagePath; String videoPath; VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; + double exposureCompensation = initialExposureCompensation; + double minExposureTargetBias = initialMinExposureTargetBias; + double maxExposureTargetBias = initialMaxExposureTargetBias; + bool isMinMaxExposureTargetBiasSet = false; @override void initState() { @@ -103,6 +110,7 @@ class _CameraExampleHomeState extends State ), _captureControlRowWidget(), _toggleAudioWidget(), + Center(child: _changeExposureCompensationWidget()), Padding( padding: const EdgeInsets.all(5.0), child: Row( @@ -269,6 +277,31 @@ class _CameraExampleHomeState extends State return Row(children: toggles); } + Widget _changeExposureCompensationWidget() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Slider( + value: exposureCompensation, + min: minExposureTargetBias, + max: maxExposureTargetBias, + onChanged: (value) { + exposureCompensation = value; + + controller.applyExposureCompensation(exposureValue: exposureCompensation.toInt()).then((value) async { + //We should set the device camera's min and max exposure target bias if we have not set it yet. + if (!isMinMaxExposureTargetBiasSet) { + minExposureTargetBias = await controller.getMinExposureTargetBias(); + maxExposureTargetBias = await controller.getMaxExposureTargetBias(); + isMinMaxExposureTargetBiasSet = true; + if (mounted) setState(() {}); + } + }); + if (mounted) setState(() {}); + }, + ), + ); + } + String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { @@ -283,6 +316,7 @@ class _CameraExampleHomeState extends State cameraDescription, ResolutionPreset.medium, enableAudio: enableAudio, + exposureCompensation: exposureCompensation.toInt() ); // If the controller is updated then update the UI. From 26b3c4ffde075ff281eba8a30a65f3051ab78155 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Wed, 12 Feb 2020 16:07:45 +1100 Subject: [PATCH 03/11] Format codes of changes related to the supporting exposure compensation in the camera plugin. --- .../io/flutter/plugins/camera/Camera.java | 6 +++-- .../plugins/camera/MethodCallHandlerImpl.java | 24 ++++++++--------- packages/camera/example/lib/main.dart | 26 ++++++++++--------- packages/camera/lib/camera.dart | 13 ++++------ 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 3b4f7b57be53..3cb21d217d2a 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -526,9 +526,11 @@ private Range getExposureCompensationRange() { private void applyExposureCompensationRequest(CaptureRequest.Builder builderRequest, int value) { Range exposureCompensationRange = getExposureCompensationRange(); if (value > exposureCompensationRange.getUpper()) { - builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getUpper()); + builderRequest.set( + CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getUpper()); } else if (value < exposureCompensationRange.getLower()) { - builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getLower()); + builderRequest.set( + CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationRange.getLower()); } else { builderRequest.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, value); } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 728371b3bdde..a591a3a23080 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -124,20 +124,20 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) break; } case "applyExposureCompensation": - { - camera.applyExposureCompensation(result, call.argument("exposureCompensation")); - break; - } + { + camera.applyExposureCompensation(result, call.argument("exposureCompensation")); + break; + } case "getMaxExposureTargetBias": - { - result.success(camera.getMaxExposureTargetBias()); - break; - } + { + result.success(camera.getMaxExposureTargetBias()); + break; + } case "getMinExposureTargetBias": - { - result.success(camera.getMinExposureTargetBias()); - break; - } + { + result.success(camera.getMinExposureTargetBias()); + break; + } case "dispose": { if (camera != null) { diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 27b071044212..a4967ec34c45 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -37,9 +37,9 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { - static const double initialExposureCompensation = 0.0; - static const double initialMinExposureTargetBias = -10.0; - static const double initialMaxExposureTargetBias = 10.0; + static const double initialExposureCompensation = 0.0; + static const double initialMinExposureTargetBias = -10.0; + static const double initialMaxExposureTargetBias = 10.0; CameraController controller; String imagePath; String videoPath; @@ -287,11 +287,16 @@ class _CameraExampleHomeState extends State onChanged: (value) { exposureCompensation = value; - controller.applyExposureCompensation(exposureValue: exposureCompensation.toInt()).then((value) async { + controller + .applyExposureCompensation( + exposureValue: exposureCompensation.toInt()) + .then((value) async { //We should set the device camera's min and max exposure target bias if we have not set it yet. if (!isMinMaxExposureTargetBiasSet) { - minExposureTargetBias = await controller.getMinExposureTargetBias(); - maxExposureTargetBias = await controller.getMaxExposureTargetBias(); + minExposureTargetBias = + await controller.getMinExposureTargetBias(); + maxExposureTargetBias = + await controller.getMaxExposureTargetBias(); isMinMaxExposureTargetBiasSet = true; if (mounted) setState(() {}); } @@ -312,12 +317,9 @@ class _CameraExampleHomeState extends State if (controller != null) { await controller.dispose(); } - controller = CameraController( - cameraDescription, - ResolutionPreset.medium, - enableAudio: enableAudio, - exposureCompensation: exposureCompensation.toInt() - ); + controller = CameraController(cameraDescription, ResolutionPreset.medium, + enableAudio: enableAudio, + exposureCompensation: exposureCompensation.toInt()); // If the controller is updated then update the UI. controller.addListener(() { diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index 4e5313824674..8fbea617b583 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -241,12 +241,9 @@ class CameraValue { /// /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { - CameraController( - this.description, - this.resolutionPreset, { - this.enableAudio = true, - this.exposureCompensation = 0 - }) : super(const CameraValue.uninitialized()); + CameraController(this.description, this.resolutionPreset, + {this.enableAudio = true, this.exposureCompensation = 0}) + : super(const CameraValue.uninitialized()); final CameraDescription description; final ResolutionPreset resolutionPreset; @@ -583,8 +580,8 @@ class CameraController extends ValueNotifier { } try { - await _channel.invokeMethod( - 'applyExposureCompensation', {'exposureCompensation': exposureValue}); + await _channel.invokeMethod('applyExposureCompensation', + {'exposureCompensation': exposureValue}); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } From bbe257b398623d5ef820f8b84852453550211238 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Wed, 12 Feb 2020 16:37:48 +1100 Subject: [PATCH 04/11] Added more comments to the supporting exposure compensation value in the camera plugin --- packages/camera/example/lib/main.dart | 5 +++-- packages/camera/lib/camera.dart | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index a4967ec34c45..49af11bd9a87 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -277,6 +277,7 @@ class _CameraExampleHomeState extends State return Row(children: toggles); } + // Display an exposure compensation slider to change the value of it. Widget _changeExposureCompensationWidget() { return Padding( padding: const EdgeInsets.all(8.0), @@ -298,10 +299,10 @@ class _CameraExampleHomeState extends State maxExposureTargetBias = await controller.getMaxExposureTargetBias(); isMinMaxExposureTargetBiasSet = true; - if (mounted) setState(() {}); + if (mounted) setState(() { /* We should set state to refresh the ui and see the changed min and max values for the slider */ }); } }); - if (mounted) setState(() {}); + if (mounted) setState(() { /* We should set state to refresh the ui and see the changed compensation effect in the camera view */ }); }, ), ); diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index 8fbea617b583..a2fe7f316171 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -251,6 +251,7 @@ class CameraController extends ValueNotifier { /// Whether to include audio when recording a video. final bool enableAudio; + /// The exposure compensation value which is 0 by default. final int exposureCompensation; int _textureId; @@ -570,7 +571,9 @@ class CameraController extends ValueNotifier { } } - // Set the Exposure Compensation value + /// Set the exposure compensation value + /// + /// Throws a [CameraException] if setting exposure compensation fails. Future applyExposureCompensation({int exposureValue = 0}) async { if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -587,6 +590,10 @@ class CameraController extends ValueNotifier { } } + /// Get the minimum exposure target bias value + /// + /// Throws a [CameraException] if getting minimum exposure + /// target bias fails. Future getMinExposureTargetBias() async { if (!value.isInitialized || _isDisposed) { throw CameraException( @@ -602,6 +609,10 @@ class CameraController extends ValueNotifier { } } + /// Get the maximum exposure target bias value + /// + /// Throws a [CameraException] if getting maximum exposure + /// target bias fails. Future getMaxExposureTargetBias() async { if (!value.isInitialized || _isDisposed) { throw CameraException( From e791ce3006e6b85d4102ade8b31d5661067b304d Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Wed, 12 Feb 2020 16:44:24 +1100 Subject: [PATCH 05/11] Added a change based on the analysis report. --- packages/camera/example/lib/main.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 49af11bd9a87..080617605406 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -299,10 +299,18 @@ class _CameraExampleHomeState extends State maxExposureTargetBias = await controller.getMaxExposureTargetBias(); isMinMaxExposureTargetBiasSet = true; - if (mounted) setState(() { /* We should set state to refresh the ui and see the changed min and max values for the slider */ }); + if (mounted) { + setState(() { + /* We should set state to refresh the ui and see the changed min and max values for the slider */ + }); + } } }); - if (mounted) setState(() { /* We should set state to refresh the ui and see the changed compensation effect in the camera view */ }); + if (mounted) { + setState(() { + /* We should set state to refresh the ui and see the changed compensation effect in the camera view */ + }); + } }, ), ); From bd6e479b8d6b0641959a98cfc5d8c5ebe2840f55 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Wed, 12 Feb 2020 17:30:20 +1100 Subject: [PATCH 06/11] Modify pubspec and CHANGELOG (Exposure compensation). --- packages/camera/CHANGELOG.md | 4 ++++ packages/camera/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 79eefa649dbf..b176291024b7 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.8 + +* Add feature to set exposure compensation value (brightness). + ## 0.5.7+3 * Fix an Android crash when permissions are requested multiple times. diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 2e8d1cba8a93..0bcb815af79d 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ 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.5.7+3 +version: 0.5.8 homepage: https://github.com/flutter/plugins/tree/master/packages/camera From 2cad9164ebad87222379f81f14abc802c49db96e Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Thu, 13 Feb 2020 16:02:05 +1100 Subject: [PATCH 07/11] Added support for setting exposure compensation to the front camera in addition to the back camera. --- .../io/flutter/plugins/camera/Camera.java | 14 ++--- .../plugins/camera/MethodCallHandlerImpl.java | 12 ++--- packages/camera/example/lib/main.dart | 38 +++++++------- packages/camera/ios/Classes/CameraPlugin.m | 51 +++++++++---------- packages/camera/lib/camera.dart | 22 ++++---- 5 files changed, 62 insertions(+), 75 deletions(-) diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 3cb21d217d2a..648f342dafa0 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -53,7 +53,6 @@ public class Camera { private final Size previewSize; private final boolean enableAudio; - private int exposureCompensation; private CameraDevice cameraDevice; private CameraCaptureSession cameraCaptureSession; private ImageReader pictureImageReader; @@ -81,8 +80,7 @@ public Camera( final DartMessenger dartMessenger, final String cameraName, final String resolutionPreset, - final boolean enableAudio, - final int exposureCompensation) + final boolean enableAudio) throws CameraAccessException { if (activity == null) { throw new IllegalStateException("No activity available!"); @@ -90,7 +88,6 @@ public Camera( this.cameraName = cameraName; this.enableAudio = enableAudio; - this.exposureCompensation = exposureCompensation; this.flutterTexture = flutterTexture; this.dartMessenger = dartMessenger; this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); @@ -256,8 +253,6 @@ public void takePicture(String filePath, @NonNull final Result result) { captureBuilder.addTarget(pictureImageReader.getSurface()); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation()); - applyExposureCompensationRequest(captureBuilder, exposureCompensation); - cameraCaptureSession.capture( captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { @@ -329,8 +324,6 @@ public void onConfigured(@NonNull CameraCaptureSession session) { captureRequestBuilder.set( CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); - applyExposureCompensationRequest(captureRequestBuilder, exposureCompensation); - cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); if (onSuccessCallback != null) { onSuccessCallback.run(); @@ -490,7 +483,6 @@ public void applyExposureCompensation(@NonNull final Result result, int value) { try { applyExposureCompensationRequest(captureRequestBuilder, value); - exposureCompensation = value; cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null); result.success(null); @@ -499,12 +491,12 @@ public void applyExposureCompensation(@NonNull final Result result, int value) { } } - public double getMaxExposureTargetBias() { + public double getMaxExposureCompensation() { Range exposureCompensationRange = getExposureCompensationRange(); return exposureCompensationRange.getUpper().doubleValue(); } - public double getMinExposureTargetBias() { + public double getMinExposureCompensation() { Range exposureCompensationRange = getExposureCompensationRange(); return exposureCompensationRange.getLower().doubleValue(); } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index a591a3a23080..4d7fce4aaf35 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -128,14 +128,14 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) camera.applyExposureCompensation(result, call.argument("exposureCompensation")); break; } - case "getMaxExposureTargetBias": + case "getMaxExposureCompensation": { - result.success(camera.getMaxExposureTargetBias()); + result.success(camera.getMaxExposureCompensation()); break; } - case "getMinExposureTargetBias": + case "getMinExposureCompensation": { - result.success(camera.getMinExposureTargetBias()); + result.success(camera.getMinExposureCompensation()); break; } case "dispose": @@ -160,7 +160,6 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce String cameraName = call.argument("cameraName"); String resolutionPreset = call.argument("resolutionPreset"); boolean enableAudio = call.argument("enableAudio"); - int exposureCompensation = call.argument("exposureCompensation"); TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id()); @@ -171,8 +170,7 @@ private void instantiateCamera(MethodCall call, Result result) throws CameraAcce dartMessenger, cameraName, resolutionPreset, - enableAudio, - exposureCompensation); + enableAudio); camera.open(result); } diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 080617605406..9f0d71cffb3b 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -38,8 +38,8 @@ void logError(String code, String message) => class _CameraExampleHomeState extends State with WidgetsBindingObserver { static const double initialExposureCompensation = 0.0; - static const double initialMinExposureTargetBias = -10.0; - static const double initialMaxExposureTargetBias = 10.0; + static const double initialMinExposureCompensation = -10.0; + static const double initialMaxExposureCompensation = 10.0; CameraController controller; String imagePath; String videoPath; @@ -47,9 +47,9 @@ class _CameraExampleHomeState extends State VoidCallback videoPlayerListener; bool enableAudio = true; double exposureCompensation = initialExposureCompensation; - double minExposureTargetBias = initialMinExposureTargetBias; - double maxExposureTargetBias = initialMaxExposureTargetBias; - bool isMinMaxExposureTargetBiasSet = false; + double minExposureCompensation = initialMinExposureCompensation; + double maxExposureCompensation = initialMaxExposureCompensation; + bool isMinMaxExposureCompensationSet = false; @override void initState() { @@ -283,8 +283,8 @@ class _CameraExampleHomeState extends State padding: const EdgeInsets.all(8.0), child: Slider( value: exposureCompensation, - min: minExposureTargetBias, - max: maxExposureTargetBias, + min: minExposureCompensation, + max: maxExposureCompensation, onChanged: (value) { exposureCompensation = value; @@ -292,13 +292,13 @@ class _CameraExampleHomeState extends State .applyExposureCompensation( exposureValue: exposureCompensation.toInt()) .then((value) async { - //We should set the device camera's min and max exposure target bias if we have not set it yet. - if (!isMinMaxExposureTargetBiasSet) { - minExposureTargetBias = - await controller.getMinExposureTargetBias(); - maxExposureTargetBias = - await controller.getMaxExposureTargetBias(); - isMinMaxExposureTargetBiasSet = true; + //We should get and set the device camera's min and max exposure target bias once if we have not set it yet. + if (!isMinMaxExposureCompensationSet) { + minExposureCompensation = + await controller.getMinExposureCompensation(); + maxExposureCompensation = + await controller.getMaxExposureCompensation(); + isMinMaxExposureCompensationSet = true; if (mounted) { setState(() { /* We should set state to refresh the ui and see the changed min and max values for the slider */ @@ -327,12 +327,16 @@ class _CameraExampleHomeState extends State await controller.dispose(); } controller = CameraController(cameraDescription, ResolutionPreset.medium, - enableAudio: enableAudio, - exposureCompensation: exposureCompensation.toInt()); + enableAudio: enableAudio); // If the controller is updated then update the UI. controller.addListener(() { - if (mounted) setState(() {}); + if (mounted) { + setState(() { + //Reset the exposure compensation when the user switches between cameras. + exposureCompensation = 0; + }); + } if (controller.value.hasError) { showInSnackBar('Camera error ${controller.value.errorDescription}'); } diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 56d1df9179a7..70023c076c04 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -196,7 +196,6 @@ @interface FLTCam : NSObject *)messenger; - (void)stopImageStream; - (void)captureToFile:(NSString *)filename result:(FlutterResult)result; -- (void)applyExposureCompensation:(NSNumber*)exposureValue; @end @implementation FLTCam { @@ -219,7 +217,6 @@ @implementation FLTCam { - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio - exposureCompensation:(NSNumber *)exposureCompensation dispatchQueue:(dispatch_queue_t)dispatchQueue error:(NSError **)error { self = [super init]; @@ -266,7 +263,6 @@ - (instancetype)initWithCameraName:(NSString *)cameraName [self setCaptureSessionPreset:_resolutionPreset]; - [self applyExposureCompensation:exposureCompensation]; return self; } @@ -766,28 +762,32 @@ - (void)setUpCaptureSessionForAudio { } } -- (float)getMinExposureTargetBias { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - return device.minExposureTargetBias; +- (float)getMinExposureCompensation { + return _captureDevice.minExposureTargetBias; } -- (float)getMaxExposureTargetBias { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - return device.maxExposureTargetBias; +- (float)getMaxExposureCompensation { + return _captureDevice.maxExposureTargetBias; } -- (void)applyExposureCompensation:(NSNumber*)exposureValue { - AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; - [device lockForConfiguration:nil]; +- (void)applyExposureCompensation:(NSNumber*)exposureValue result:(FlutterResult)result { - float minExposureTargetBias = [self getMinExposureTargetBias]; - float maxExposureTargetBias = [self getMaxExposureTargetBias]; + NSError *error = nil; + [_captureDevice lockForConfiguration:&error]; + if (error) { + result(getFlutterError(error)); + } else { + float minExposureCompensation = [self getMinExposureCompensation]; + float maxExposureCompensation = [self getMaxExposureCompensation]; - int exposureTargetBias = MIN(exposureValue.intValue, (int)maxExposureTargetBias); - exposureTargetBias = MAX(exposureTargetBias, (int)minExposureTargetBias); - [device setExposureTargetBias:(float)exposureTargetBias completionHandler:^(CMTime syncTime) {}]; + int exposureCompensation = MIN(exposureValue.intValue, (int)maxExposureCompensation); + exposureCompensation = MAX(exposureCompensation, (int)minExposureCompensation); + [_captureDevice setExposureTargetBias:(float)exposureCompensation completionHandler:^(CMTime syncTime) {}]; - [device unlockForConfiguration]; + [_captureDevice unlockForConfiguration]; + + result(nil); + } } @end @@ -863,12 +863,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re NSString *cameraName = call.arguments[@"cameraName"]; NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSNumber *exposureCompensation = call.arguments[@"exposureCompensation"]; NSError *error; FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName resolutionPreset:resolutionPreset enableAudio:[enableAudio boolValue] - exposureCompensation:exposureCompensation dispatchQueue:_dispatchQueue error:&error]; if (error) { @@ -905,12 +903,11 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera stopImageStream]; result(nil); } else if ([@"applyExposureCompensation" isEqualToString:call.method]) { - [_camera applyExposureCompensation:call.arguments[@"exposureCompensation"]]; - result(nil); - } else if ([@"getMinExposureTargetBias" isEqualToString:call.method]) { - result(@([_camera getMinExposureTargetBias])); - } else if ([@"getMaxExposureTargetBias" isEqualToString:call.method]) { - result(@([_camera getMaxExposureTargetBias])); + [_camera applyExposureCompensation:call.arguments[@"exposureCompensation"] result:result]; + } else if ([@"getMinExposureCompensation" isEqualToString:call.method]) { + result(@([_camera getMinExposureCompensation])); + } else if ([@"getMaxExposureCompensation" isEqualToString:call.method]) { + result(@([_camera getMaxExposureCompensation])); } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecording]; result(nil); diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index a2fe7f316171..4883c9fa5fb6 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -242,7 +242,7 @@ class CameraValue { /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { CameraController(this.description, this.resolutionPreset, - {this.enableAudio = true, this.exposureCompensation = 0}) + {this.enableAudio = true}) : super(const CameraValue.uninitialized()); final CameraDescription description; @@ -251,9 +251,6 @@ class CameraController extends ValueNotifier { /// Whether to include audio when recording a video. final bool enableAudio; - /// The exposure compensation value which is 0 by default. - final int exposureCompensation; - int _textureId; bool _isDisposed = false; StreamSubscription _eventSubscription; @@ -276,7 +273,6 @@ class CameraController extends ValueNotifier { 'cameraName': description.name, 'resolutionPreset': serializeResolutionPreset(resolutionPreset), 'enableAudio': enableAudio, - 'exposureCompensation': exposureCompensation }, ); _textureId = reply['textureId']; @@ -590,39 +586,39 @@ class CameraController extends ValueNotifier { } } - /// Get the minimum exposure target bias value + /// Get the minimum exposure compensation value /// /// Throws a [CameraException] if getting minimum exposure /// target bias fails. - Future getMinExposureTargetBias() async { + Future getMinExposureCompensation() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController.', - 'getMinExposureTargetBias was called on uninitialized CameraController', + 'getMinExposureCompensation was called on uninitialized CameraController', ); } try { - return await _channel.invokeMethod('getMinExposureTargetBias'); + return await _channel.invokeMethod('getMinExposureCompensation'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } - /// Get the maximum exposure target bias value + /// Get the maximum exposure compensation value /// /// Throws a [CameraException] if getting maximum exposure /// target bias fails. - Future getMaxExposureTargetBias() async { + Future getMaxExposureCompensation() async { if (!value.isInitialized || _isDisposed) { throw CameraException( 'Uninitialized CameraController.', - 'getMaxExposureTargetBias was called on uninitialized CameraController', + 'getMaxExposureCompensation was called on uninitialized CameraController', ); } try { - return await _channel.invokeMethod('getMaxExposureTargetBias'); + return await _channel.invokeMethod('getMaxExposureCompensation'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } From dc39932cc0f051b27b9b2a7a9e5cdbfc4568d701 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Thu, 13 Feb 2020 17:21:58 +1100 Subject: [PATCH 08/11] Fixed format issues in the camera plugin. --- packages/camera/example/lib/main.dart | 7 ++++-- packages/camera/ios/Classes/CameraPlugin.m | 28 ++++++++++++---------- packages/camera/lib/camera.dart | 8 ++++--- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 9f0d71cffb3b..5a69177a7647 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -326,8 +326,11 @@ class _CameraExampleHomeState extends State if (controller != null) { await controller.dispose(); } - controller = CameraController(cameraDescription, ResolutionPreset.medium, - enableAudio: enableAudio); + controller = CameraController( + cameraDescription, + ResolutionPreset.medium, + enableAudio: enableAudio, + ); // If the controller is updated then update the UI. controller.addListener(() { diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 70023c076c04..15ef039883ab 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -763,30 +763,32 @@ - (void)setUpCaptureSessionForAudio { } - (float)getMinExposureCompensation { - return _captureDevice.minExposureTargetBias; + return _captureDevice.minExposureTargetBias; } - (float)getMaxExposureCompensation { - return _captureDevice.maxExposureTargetBias; + return _captureDevice.maxExposureTargetBias; } -- (void)applyExposureCompensation:(NSNumber*)exposureValue result:(FlutterResult)result { +- (void)applyExposureCompensation:(NSNumber *)exposureValue result:(FlutterResult)result { NSError *error = nil; [_captureDevice lockForConfiguration:&error]; if (error) { - result(getFlutterError(error)); + result(getFlutterError(error)); } else { - float minExposureCompensation = [self getMinExposureCompensation]; - float maxExposureCompensation = [self getMaxExposureCompensation]; - - int exposureCompensation = MIN(exposureValue.intValue, (int)maxExposureCompensation); - exposureCompensation = MAX(exposureCompensation, (int)minExposureCompensation); - [_captureDevice setExposureTargetBias:(float)exposureCompensation completionHandler:^(CMTime syncTime) {}]; + float minExposureCompensation = [self getMinExposureCompensation]; + float maxExposureCompensation = [self getMaxExposureCompensation]; + + int exposureCompensation = MIN(exposureValue.intValue, (int)maxExposureCompensation); + exposureCompensation = MAX(exposureCompensation, (int)minExposureCompensation); + [_captureDevice setExposureTargetBias:(float)exposureCompensation + completionHandler:^(CMTime syncTime) { + }]; - [_captureDevice unlockForConfiguration]; + [_captureDevice unlockForConfiguration]; - result(nil); + result(nil); } } @@ -908,7 +910,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(@([_camera getMinExposureCompensation])); } else if ([@"getMaxExposureCompensation" isEqualToString:call.method]) { result(@([_camera getMaxExposureCompensation])); - } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { + } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecording]; result(nil); } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index 4883c9fa5fb6..a3231c50e372 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -241,9 +241,11 @@ class CameraValue { /// /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { - CameraController(this.description, this.resolutionPreset, - {this.enableAudio = true}) - : super(const CameraValue.uninitialized()); + CameraController( + this.description, + this.resolutionPreset, + {this.enableAudio = true, + }) : super(const CameraValue.uninitialized()); final CameraDescription description; final ResolutionPreset resolutionPreset; From 1a1dc40806451a202d5f9d294c0e0ff05838edf7 Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Fri, 14 Feb 2020 09:37:59 +1100 Subject: [PATCH 09/11] Fixed format issues in the camera plugin. --- packages/camera/ios/Classes/CameraPlugin.m | 9 ++++----- packages/camera/lib/camera.dart | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 15ef039883ab..e9b40002d1a0 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -262,7 +262,7 @@ - (instancetype)initWithCameraName:(NSString *)cameraName [_motionManager startAccelerometerUpdates]; [self setCaptureSessionPreset:_resolutionPreset]; - + return self; } @@ -771,7 +771,6 @@ - (float)getMaxExposureCompensation { } - (void)applyExposureCompensation:(NSNumber *)exposureValue result:(FlutterResult)result { - NSError *error = nil; [_captureDevice lockForConfiguration:&error]; if (error) { @@ -783,11 +782,11 @@ - (void)applyExposureCompensation:(NSNumber *)exposureValue result:(FlutterResul int exposureCompensation = MIN(exposureValue.intValue, (int)maxExposureCompensation); exposureCompensation = MAX(exposureCompensation, (int)minExposureCompensation); [_captureDevice setExposureTargetBias:(float)exposureCompensation - completionHandler:^(CMTime syncTime) { + completionHandler:^(CMTime syncTime){ }]; - + [_captureDevice unlockForConfiguration]; - + result(nil); } } diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index a3231c50e372..a55ea7d25f3c 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -243,8 +243,8 @@ class CameraValue { class CameraController extends ValueNotifier { CameraController( this.description, - this.resolutionPreset, - {this.enableAudio = true, + this.resolutionPreset, { + this.enableAudio = true, }) : super(const CameraValue.uninitialized()); final CameraDescription description; From 6a9ba9cdd8fecfcab625fe26585ebbfa3b914f0b Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Fri, 14 Feb 2020 12:30:17 +1100 Subject: [PATCH 10/11] Add tests to the camera plugin related to the setting exposure compensation. --- .../example/test_driver/camera_e2e.dart | 65 +++++++++++++++++++ packages/camera/test/camera_test.dart | 8 +-- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/packages/camera/example/test_driver/camera_e2e.dart b/packages/camera/example/test_driver/camera_e2e.dart index a1cc8ad9ca02..288f6da82ea7 100644 --- a/packages/camera/example/test_driver/camera_e2e.dart +++ b/packages/camera/example/test_driver/camera_e2e.dart @@ -235,4 +235,69 @@ void main() { }, skip: !Platform.isAndroid, ); + + // This tests that the minimum exposure compensation is always a negative value. + // Returns whether the minimum value is negative or not. + Future testGettingMinimumExposureCompensation( + CameraController controller) async { + print( + 'Getting minimum exposure compensation of camera ${controller.description.name}'); + + // Get minimum exposure compensation + double minimumExposureCompensation = await controller.getMinExposureCompensation(); + + // Verify minimum exposure compensation is negative + expect(minimumExposureCompensation, isNegative); + return minimumExposureCompensation < 0; + } + + testWidgets('Get minimum exposure compensation', + (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + final CameraController controller = + CameraController(cameraDescription, ResolutionPreset.medium); + await controller.initialize(); + final bool isSuccess = + await testGettingMinimumExposureCompensation(controller); + assert(isSuccess); + await controller.dispose(); + } + }, skip: !Platform.isAndroid); + + + // This tests that the maximum exposure compensation is always a positive value. + // Returns whether the maximum value is positive or not. + Future testGettingMaximumExposureCompensation( + CameraController controller) async { + print( + 'Getting maximum exposure compensation of camera ${controller.description.name}'); + + // Get maximum exposure compensation + double maximumExposureCompensation = await controller.getMaxExposureCompensation(); + + // Verify maximum exposure compensation is positive + expect(maximumExposureCompensation, isPositive); + return maximumExposureCompensation > 0; + } + + testWidgets('Get maximum exposure compensation', + (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + final CameraController controller = + CameraController(cameraDescription, ResolutionPreset.medium); + await controller.initialize(); + final bool isSuccess = + await testGettingMaximumExposureCompensation(controller); + assert(isSuccess); + await controller.dispose(); + } + }, skip: !Platform.isAndroid); } diff --git a/packages/camera/test/camera_test.dart b/packages/camera/test/camera_test.dart index fbb955689e48..6d3f6f38d449 100644 --- a/packages/camera/test/camera_test.dart +++ b/packages/camera/test/camera_test.dart @@ -42,7 +42,7 @@ void main() { final MockCameraConfigurator configurator = MockCameraConfigurator(); final CameraController controller1 = - CameraController.customConfigurator( + CameraController.customConfigurator( description: description, configurator: configurator, ); @@ -50,7 +50,7 @@ void main() { controller1.initialize(); final CameraController controller2 = - CameraController.customConfigurator( + CameraController.customConfigurator( description: description, configurator: configurator, ); @@ -58,12 +58,12 @@ void main() { controller2.initialize(); expect( - () => controller1.start(), + () => controller1.start(), throwsA(isInstanceOf()), ); expect( - () => controller1.stop(), + () => controller1.stop(), throwsA(isInstanceOf()), ); From 2a15a4a6e782a2c1629588a3d7a3c621158cfc7d Mon Sep 17 00:00:00 2001 From: Nima Azimi Date: Wed, 19 Feb 2020 10:16:36 +1100 Subject: [PATCH 11/11] Fixed format issues in the camera plugin. --- .../example/test_driver/camera_e2e.dart | 61 +++++++++---------- packages/camera/test/camera_test.dart | 8 +-- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/packages/camera/example/test_driver/camera_e2e.dart b/packages/camera/example/test_driver/camera_e2e.dart index 288f6da82ea7..53fdba8e5785 100644 --- a/packages/camera/example/test_driver/camera_e2e.dart +++ b/packages/camera/example/test_driver/camera_e2e.dart @@ -244,30 +244,29 @@ void main() { 'Getting minimum exposure compensation of camera ${controller.description.name}'); // Get minimum exposure compensation - double minimumExposureCompensation = await controller.getMinExposureCompensation(); + double minimumExposureCompensation = + await controller.getMinExposureCompensation(); // Verify minimum exposure compensation is negative expect(minimumExposureCompensation, isNegative); return minimumExposureCompensation < 0; } - testWidgets('Get minimum exposure compensation', - (WidgetTester tester) async { - final List cameras = await availableCameras(); - if (cameras.isEmpty) { - return; - } - for (CameraDescription cameraDescription in cameras) { - final CameraController controller = + testWidgets('Get minimum exposure compensation', (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + final CameraController controller = CameraController(cameraDescription, ResolutionPreset.medium); - await controller.initialize(); - final bool isSuccess = + await controller.initialize(); + final bool isSuccess = await testGettingMinimumExposureCompensation(controller); - assert(isSuccess); - await controller.dispose(); - } - }, skip: !Platform.isAndroid); - + assert(isSuccess); + await controller.dispose(); + } + }, skip: !Platform.isAndroid); // This tests that the maximum exposure compensation is always a positive value. // Returns whether the maximum value is positive or not. @@ -277,27 +276,27 @@ void main() { 'Getting maximum exposure compensation of camera ${controller.description.name}'); // Get maximum exposure compensation - double maximumExposureCompensation = await controller.getMaxExposureCompensation(); + double maximumExposureCompensation = + await controller.getMaxExposureCompensation(); // Verify maximum exposure compensation is positive expect(maximumExposureCompensation, isPositive); return maximumExposureCompensation > 0; } - testWidgets('Get maximum exposure compensation', - (WidgetTester tester) async { - final List cameras = await availableCameras(); - if (cameras.isEmpty) { - return; - } - for (CameraDescription cameraDescription in cameras) { - final CameraController controller = + testWidgets('Get maximum exposure compensation', (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + for (CameraDescription cameraDescription in cameras) { + final CameraController controller = CameraController(cameraDescription, ResolutionPreset.medium); - await controller.initialize(); - final bool isSuccess = + await controller.initialize(); + final bool isSuccess = await testGettingMaximumExposureCompensation(controller); - assert(isSuccess); - await controller.dispose(); - } - }, skip: !Platform.isAndroid); + assert(isSuccess); + await controller.dispose(); + } + }, skip: !Platform.isAndroid); } diff --git a/packages/camera/test/camera_test.dart b/packages/camera/test/camera_test.dart index 6d3f6f38d449..fbb955689e48 100644 --- a/packages/camera/test/camera_test.dart +++ b/packages/camera/test/camera_test.dart @@ -42,7 +42,7 @@ void main() { final MockCameraConfigurator configurator = MockCameraConfigurator(); final CameraController controller1 = - CameraController.customConfigurator( + CameraController.customConfigurator( description: description, configurator: configurator, ); @@ -50,7 +50,7 @@ void main() { controller1.initialize(); final CameraController controller2 = - CameraController.customConfigurator( + CameraController.customConfigurator( description: description, configurator: configurator, ); @@ -58,12 +58,12 @@ void main() { controller2.initialize(); expect( - () => controller1.start(), + () => controller1.start(), throwsA(isInstanceOf()), ); expect( - () => controller1.stop(), + () => controller1.stop(), throwsA(isInstanceOf()), );