From aac9798cc7ce5cf3991d73c55ea3c8c92ff4c69f Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Fri, 15 Apr 2022 09:33:40 +0200 Subject: [PATCH 1/3] On iOS, the half image can only work if the full image is provided, with a custom size. Coordinates are also hardcoded --- .../lib/pages/scan/camera_image_cropper.dart | 192 +++++++++++++++--- 1 file changed, 167 insertions(+), 25 deletions(-) diff --git a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart index 55e90044db7b..aeabda8c1a54 100644 --- a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart +++ b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart @@ -1,5 +1,7 @@ +import 'dart:io'; import 'dart:typed_data'; import 'package:camera/camera.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_ml_barcode_scanner/google_ml_barcode_scanner.dart'; import 'package:smooth_app/pages/scan/abstract_camera_image_getter.dart'; @@ -7,62 +9,130 @@ import 'package:typed_data/typed_buffers.dart'; /// Camera Image Cropper, in order to limit the barcode scan computations. /// +/// Note: on iOS, we can only crop the size, as the coordinates are hardcoded. +/// [width01] and [height01] are thus ignored on this platform. +/// /// Use CameraController with imageFormatGroup: ImageFormatGroup.yuv420 /// [left01], [top01], [width01] and [height01] are values between 0 and 1 /// that delimit the cropping area. /// For instance: /// * left01: 0, top01: 0, width01: 1, height01: .2 delimit the top 20% banner /// * left01: .5, top01: .5, width01: .5, height01: ..5 the bottom right rect -class CameraImageCropper extends AbstractCameraImageGetter { - CameraImageCropper( +abstract class CameraImageCropper extends AbstractCameraImageGetter { + factory CameraImageCropper( + final CameraImage cameraImage, + final CameraDescription cameraDescription, { + required double left01, + required double top01, + required double width01, + required double height01, + }) { + if (Platform.isIOS) { + return _CameraImageCropperImplIOS( + cameraImage, + cameraDescription, + width01: width01, + height01: height01, + ); + } else { + return _CameraImageCropperImplDefault( + cameraImage, + cameraDescription, + left01: left01, + top01: top01, + width01: width01, + height01: height01, + ); + } + } + + CameraImageCropper._( final CameraImage cameraImage, final CameraDescription cameraDescription, { - required this.left01, - required this.top01, required this.width01, required this.height01, - }) : super(cameraImage, cameraDescription) { + }) : assert(width01 > 0 && width01 <= 1), + assert(height01 > 0 && height01 <= 1), + super( + cameraImage, + cameraDescription, + ); + + final double width01; + final double height01; + + int _getEven(final double value) => 2 * (value ~/ 2); + + int _computeWidth() { + if (orientation == 0) { + return _getEven(fullWidth * width01); + } else if (orientation == 90) { + return _getEven(fullWidth * height01); + } + + throw Exception('Orientation $orientation not dealt with for the moment'); + } + + int _computeHeight() { + if (orientation == 0) { + return _getEven(fullHeight * height01); + } else if (orientation == 90) { + return _getEven(fullHeight * width01); + } + + throw Exception('Orientation $orientation not dealt with for the moment'); + } + + int get fullWidth => cameraImage.width; + + int get fullHeight => cameraImage.height; + + int get orientation => cameraDescription.sensorOrientation; +} + +class _CameraImageCropperImplDefault extends CameraImageCropper { + _CameraImageCropperImplDefault( + final CameraImage cameraImage, + final CameraDescription cameraDescription, { + required this.left01, + required this.top01, + required double width01, + required double height01, + }) : super._( + cameraImage, + cameraDescription, + width01: width01, + height01: height01, + ) { _computeCropParameters(); } final double left01; final double top01; - final double width01; - final double height01; late int _left; late int _top; late int _width; late int _height; void _computeCropParameters() { - assert(width01 > 0 && width01 <= 1); - assert(height01 > 0 && height01 <= 1); assert(left01 >= 0 && left01 < 1); assert(top01 >= 0 && top01 < 1); assert(left01 + width01 <= 1); assert(top01 + height01 <= 1); - final int fullWidth = cameraImage.width; - final int fullHeight = cameraImage.height; - final int orientation = cameraDescription.sensorOrientation; - - int _getEven(final double value) => 2 * (value ~/ 2); - if (orientation == 0) { - _width = _getEven(fullWidth * width01); - _height = _getEven(fullHeight * height01); + _width = _computeWidth(); + _height = _computeHeight(); _left = _getEven(fullWidth * left01); _top = _getEven(fullHeight * top01); - return; - } - if (orientation == 90) { - _width = _getEven(fullWidth * height01); - _height = _getEven(fullHeight * width01); + } else if (orientation == 90) { + _width = _computeWidth(); + _height = _computeHeight(); _left = _getEven(fullWidth * top01); _top = _getEven(fullHeight * left01); - return; + } else { + throw Exception('Orientation $orientation not dealt with for the moment'); } - throw Exception('Orientation $orientation not dealt with for the moment'); } // cf. https://en.wikipedia.org/wiki/YUV#Y′UV420p_(and_Y′V12_or_YV12)_to_RGB888_conversion @@ -73,7 +143,10 @@ class CameraImageCropper extends AbstractCameraImageGetter { }; @override - Size getSize() => Size(_width.toDouble(), _height.toDouble()); + Size getSize() => Size( + _width.toDouble(), + _height.toDouble(), + ); @override Uint8List getBytes() { @@ -125,3 +198,72 @@ class CameraImageCropper extends AbstractCameraImageGetter { return planeData; } } + +/// On iOS, coordinates are hardcoded in the native code (0, 0) +/// [https://github.com/danjodanjo/google_ml_barcode_scanner/blob/master/ios/Classes/MLKVisionImage%2BFlutterPlugin.m#L136] +/// +/// The only crop we can do is to shrink the width and/or the height +class _CameraImageCropperImplIOS extends CameraImageCropper { + _CameraImageCropperImplIOS( + final CameraImage cameraImage, + final CameraDescription cameraDescription, { + required double width01, + required double height01, + }) : assert(width01 > 0 && width01 <= 1), + assert(height01 > 0 && height01 <= 1), + super._( + cameraImage, + cameraDescription, + width01: width01, + height01: height01, + ) { + _computeCropParameters(); + } + + late int _width; + late int _height; + + void _computeCropParameters() { + if (orientation == 0) { + _width = _computeWidth(); + _height = _computeHeight(); + } else if (orientation == 90) { + _width = _computeWidth(); + _height = _computeHeight(); + } else { + throw Exception('Orientation $orientation not dealt with for the moment'); + } + } + + @override + Size getSize() => Size( + _width.toDouble(), + _height.toDouble(), + ); + + // Same implementation as [CameraImageFullGetter] + @override + Uint8List getBytes() { + final WriteBuffer allBytes = WriteBuffer(); + for (final Plane plane in cameraImage.planes) { + allBytes.putUint8List(plane.bytes); + } + return allBytes.done().buffer.asUint8List(); + } + + // Same implementation as [CameraImageFullGetter] + @override + List getPlaneMetaData() { + final List planeData = []; + for (final Plane plane in cameraImage.planes) { + planeData.add( + InputImagePlaneMetadata( + bytesPerRow: plane.bytesPerRow, + height: plane.height, + width: plane.width, + ), + ); + } + return planeData; + } +} From 4324ca85cc7341fbbfd1fcf144793e7cfcbb45ea Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Fri, 15 Apr 2022 09:45:22 +0200 Subject: [PATCH 2/3] Fix formatting issue --- .../smooth_app/lib/pages/scan/camera_image_cropper.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart index aeabda8c1a54..d4dee6386018 100644 --- a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart +++ b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart @@ -237,9 +237,9 @@ class _CameraImageCropperImplIOS extends CameraImageCropper { @override Size getSize() => Size( - _width.toDouble(), - _height.toDouble(), - ); + _width.toDouble(), + _height.toDouble(), + ); // Same implementation as [CameraImageFullGetter] @override From d4e36032abe3511ff27bfb402fedcf853f7804a9 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 16 Apr 2022 14:32:58 +0200 Subject: [PATCH 3/3] Move getSize & computeCropParameters to the base class --- .../lib/pages/scan/camera_image_cropper.dart | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart index d4dee6386018..f496489ac1b1 100644 --- a/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart +++ b/packages/smooth_app/lib/pages/scan/camera_image_cropper.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'dart:typed_data'; + import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -56,12 +57,27 @@ abstract class CameraImageCropper extends AbstractCameraImageGetter { super( cameraImage, cameraDescription, - ); + ) { + _computeCropParameters(); + } final double width01; final double height01; - int _getEven(final double value) => 2 * (value ~/ 2); + late int _width; + late int _height; + + void _computeCropParameters() { + if (orientation == 0) { + _width = _computeWidth(); + _height = _computeHeight(); + } else if (orientation == 90) { + _width = _computeWidth(); + _height = _computeHeight(); + } else { + throw Exception('Orientation $orientation not dealt with for the moment'); + } + } int _computeWidth() { if (orientation == 0) { @@ -83,11 +99,19 @@ abstract class CameraImageCropper extends AbstractCameraImageGetter { throw Exception('Orientation $orientation not dealt with for the moment'); } + int _getEven(final double value) => 2 * (value ~/ 2); + int get fullWidth => cameraImage.width; int get fullHeight => cameraImage.height; int get orientation => cameraDescription.sensorOrientation; + + @override + Size getSize() => Size( + _width.toDouble(), + _height.toDouble(), + ); } class _CameraImageCropperImplDefault extends CameraImageCropper { @@ -103,31 +127,25 @@ class _CameraImageCropperImplDefault extends CameraImageCropper { cameraDescription, width01: width01, height01: height01, - ) { - _computeCropParameters(); - } + ); final double left01; final double top01; late int _left; late int _top; - late int _width; - late int _height; + @override void _computeCropParameters() { + super._computeCropParameters(); assert(left01 >= 0 && left01 < 1); assert(top01 >= 0 && top01 < 1); assert(left01 + width01 <= 1); assert(top01 + height01 <= 1); if (orientation == 0) { - _width = _computeWidth(); - _height = _computeHeight(); _left = _getEven(fullWidth * left01); _top = _getEven(fullHeight * top01); } else if (orientation == 90) { - _width = _computeWidth(); - _height = _computeHeight(); _left = _getEven(fullWidth * top01); _top = _getEven(fullHeight * left01); } else { @@ -216,30 +234,7 @@ class _CameraImageCropperImplIOS extends CameraImageCropper { cameraDescription, width01: width01, height01: height01, - ) { - _computeCropParameters(); - } - - late int _width; - late int _height; - - void _computeCropParameters() { - if (orientation == 0) { - _width = _computeWidth(); - _height = _computeHeight(); - } else if (orientation == 90) { - _width = _computeWidth(); - _height = _computeHeight(); - } else { - throw Exception('Orientation $orientation not dealt with for the moment'); - } - } - - @override - Size getSize() => Size( - _width.toDouble(), - _height.toDouble(), - ); + ); // Same implementation as [CameraImageFullGetter] @override