Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[camera] zoom feature #1304

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.7

* Add zoom feature.

## 0.5.6+4

* Android: Use CameraDevice.TEMPLATE_RECORD to improve image streaming.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
Expand Down Expand Up @@ -61,6 +62,10 @@ public class Camera {
private CamcorderProfile recordingProfile;
private int currentOrientation = ORIENTATION_UNKNOWN;

public float zoomLevel = 1f;
private Rect zoom;
protected CameraCharacteristics cameraCharacteristics;

// Mirrors camera.dart
public enum ResolutionPreset {
low,
Expand Down Expand Up @@ -106,6 +111,8 @@ public void onOrientationChanged(int i) {
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//noinspection ConstantConditions
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
cameraCharacteristics = characteristics;

//noinspection ConstantConditions
isFrontFacing =
characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT;
Expand Down Expand Up @@ -249,6 +256,7 @@ public void takePicture(String filePath, @NonNull final Result result) {
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(pictureImageReader.getSurface());
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation());
setScalerCropRegion(captureBuilder, zoom);

cameraCaptureSession.capture(
captureBuilder.build(),
Expand Down Expand Up @@ -320,6 +328,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
cameraCaptureSession = session;
captureRequestBuilder.set(
CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
setScalerCropRegion(captureRequestBuilder, zoom);
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
if (onSuccessCallback != null) {
onSuccessCallback.run();
Expand Down Expand Up @@ -510,6 +519,41 @@ public void dispose() {
orientationEventListener.disable();
}

public void zoom(int step) throws CameraAccessException {
changeZoom(step);
}

private void changeZoom(int step) throws CameraAccessException {
calculateZoom(step);
setScalerCropRegion(captureRequestBuilder, zoom);
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
}

private void calculateZoom(int step) {
zoomLevel += step;

if (zoomLevel < 1f) {
zoomLevel = 1f;
return;
}

Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);

float ratio = (float) 1 / zoomLevel;
int croppedWidth = rect.width() - Math.round((float) rect.width() * ratio);
int croppedHeight = rect.height() - Math.round((float) rect.height() * ratio);
zoom =
new Rect(
croppedWidth / 2,
croppedHeight / 2,
rect.width() - croppedWidth / 2,
rect.height() - croppedHeight / 2);
}

private void setScalerCropRegion(CaptureRequest.Builder captureRequestBuilder, Rect zoom) {
captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}

private int getMediaOrientation() {
final int sensorOrientationOffset =
(currentOrientation == ORIENTATION_UNKNOWN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,36 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
camera.takePicture(call.argument("path"), result);
break;
}
case "zoom":
{
try {
camera.zoom(call.argument("step"));
result.success(null);
} catch (CameraAccessException e) {
result.error("CameraAccess", e.getMessage(), null);
}
break;
}
case "zoomIn":
{
try {
camera.zoom(1);
result.success(null);
} catch (CameraAccessException e) {
result.error("CameraAccess", e.getMessage(), null);
}
break;
}
case "zoomOut":
{
try {
camera.zoom(-1);
result.success(null);
} catch (CameraAccessException e) {
result.error("CameraAccess", e.getMessage(), null);
}
break;
}
case "prepareForVideoRecording":
{
// This optimization is not required for Android.
Expand Down
26 changes: 25 additions & 1 deletion packages/camera/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,17 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
controller.value.isRecordingVideo
? onStopButtonPressed
: null,
)
),
IconButton(
icon: const Icon(Icons.zoom_in),
color: Colors.blue,
onPressed: controller != null ? onZoomInButtonPressed : null,
),
IconButton(
icon: const Icon(Icons.zoom_out),
color: Colors.blue,
onPressed: controller != null ? onZoomOutButtonPressed : null,
),
],
);
}
Expand Down Expand Up @@ -329,6 +339,20 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
});
}

void onZoomInButtonPressed() {
if (controller == null) {
return;
}
controller.zoomIn();
}

void onZoomOutButtonPressed() {
if (controller == null) {
return;
}
controller.zoomOut();
}

void onPauseButtonPressed() {
pauseVideoRecording().then((_) {
if (mounted) setState(() {});
Expand Down
27 changes: 27 additions & 0 deletions packages/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
enableAudio:(BOOL)enableAudio
dispatchQueue:(dispatch_queue_t)dispatchQueue
error:(NSError **)error;
@property(nonatomic, assign) int zoom;

- (void)start;
- (void)stop;
Expand All @@ -205,6 +206,7 @@ - (void)stopVideoRecordingWithResult:(FlutterResult)result;
- (void)startImageStreamWithMessenger:(NSObject<FlutterBinaryMessenger> *)messenger;
- (void)stopImageStream;
- (void)captureToFile:(NSString *)filename result:(FlutterResult)result;
- (void)zoom:(NSUInteger *)step;
@end

@implementation FLTCam {
Expand All @@ -219,6 +221,8 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
dispatchQueue:(dispatch_queue_t)dispatchQueue
error:(NSError **)error {
self = [super init];
_zoom = 1;

NSAssert(self, @"super init cannot be nil");
@try {
_resolutionPreset = getResolutionPresetForString(resolutionPreset);
Expand Down Expand Up @@ -272,6 +276,19 @@ - (void)stop {
[_captureSession stopRunning];
}

- (void)zoom:(NSUInteger *)step {
_zoom += step;

if (_zoom < 1) {
_zoom = 1;
return;
}

[_captureDevice lockForConfiguration:NULL];
[_captureDevice setVideoZoomFactor:_zoom];
[_captureDevice unlockForConfiguration];
}

- (void)captureToFile:(NSString *)path result:(FlutterResult)result {
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
if (_resolutionPreset == max) {
Expand Down Expand Up @@ -871,6 +888,16 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re
} else if ([@"stopImageStream" isEqualToString:call.method]) {
[_camera stopImageStream];
result(nil);
} else if ([@"zoomIn" isEqualToString:call.method]) {
[_camera zoom:1];
result(nil);
} else if ([@"zoomOut" isEqualToString:call.method]) {
[_camera zoom:-1];
result(nil);
} else if ([@"zoom" isEqualToString:call.method]) {
NSUInteger step = ((NSNumber *)call.arguments[@"step"]).unsignedIntegerValue;
[_camera zoom:step];
result(nil);
} else if ([@"pauseVideoRecording" isEqualToString:call.method]) {
[_camera pauseVideoRecording];
result(nil);
Expand Down
16 changes: 16 additions & 0 deletions packages/camera/lib/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -585,4 +585,20 @@ class CameraController extends ValueNotifier<CameraValue> {
await _eventSubscription?.cancel();
}
}

Future<void> zoomIn() async {
await _channel.invokeMethod<void>('zoomIn');
}

///
/// change zoom by specific [step].
/// with a negative step, the zoom will be 1
///
Future<void> zoom(int step) async {
await _channel.invokeMethod<void>('zoom', <String, dynamic>{'step': step});
}

Future<void> zoomOut() async {
await _channel.invokeMethod<void>('zoomOut');
}
}
2 changes: 1 addition & 1 deletion packages/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.6+3
version: 0.5.7

authors:
- Flutter Team <flutter-dev@googlegroups.com>
Expand Down
4 changes: 3 additions & 1 deletion packages/connectivity/ios/Classes/ConnectivityPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ - (NSString*)convertCLAuthorizationStatusToString:(CLAuthorizationStatus)status
case kCLAuthorizationStatusAuthorizedWhenInUse: {
return @"authorizedWhenInUse";
}
default: { return @"unknown"; }
default: {
return @"unknown";
}
}
}

Expand Down