Julian Steenbakker
Committed by GitHub

Merge pull request #252 from elliots/feature/return-image

optionally return image with barcode
@@ -169,3 +169,41 @@ import 'package:mobile_scanner/mobile_scanner.dart'; @@ -169,3 +169,41 @@ import 'package:mobile_scanner/mobile_scanner.dart';
169 })); 169 }));
170 } 170 }
171 ``` 171 ```
  172 +
  173 +Example with controller and returning images
  174 +
  175 +```dart
  176 +import 'package:mobile_scanner/mobile_scanner.dart';
  177 +
  178 + @override
  179 + Widget build(BuildContext context) {
  180 + return Scaffold(
  181 + appBar: AppBar(title: const Text('Mobile Scanner')),
  182 + body: MobileScanner(
  183 + controller: MobileScannerController(
  184 + facing: CameraFacing.front,
  185 + torchEnabled: true,
  186 + returnImage: true,
  187 + ),
  188 + onDetect: (barcode, args) {
  189 + if (barcode.rawValue == null) {
  190 + debugPrint('Failed to scan Barcode');
  191 + } else {
  192 + final String code = barcode.rawValue!;
  193 + debugPrint('Barcode found! $code');
  194 +
  195 + debugPrint(
  196 + 'Image returned! length: ${barcode.image!.lengthInBytes}b');
  197 + showDialog(
  198 + context: context,
  199 + builder: (context) => Image(image: MemoryImage(barcode.image!)),
  200 + );
  201 + Future.delayed(const Duration(seconds: 2), () {
  202 + Navigator.pop(context);
  203 + });
  204 + }
  205 + },
  206 + ),
  207 + );
  208 + }
  209 +```
  1 +import 'dart:typed_data';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:mobile_scanner/mobile_scanner.dart';
  5 +
  6 +class BarcodeScannerReturningImage extends StatefulWidget {
  7 + const BarcodeScannerReturningImage({Key? key}) : super(key: key);
  8 +
  9 + @override
  10 + _BarcodeScannerReturningImageState createState() =>
  11 + _BarcodeScannerReturningImageState();
  12 +}
  13 +
  14 +class _BarcodeScannerReturningImageState
  15 + extends State<BarcodeScannerReturningImage>
  16 + with SingleTickerProviderStateMixin {
  17 + String? barcode;
  18 + Uint8List? image;
  19 +
  20 + MobileScannerController controller = MobileScannerController(
  21 + torchEnabled: true,
  22 + returnImage: true,
  23 + // formats: [BarcodeFormat.qrCode]
  24 + // facing: CameraFacing.front,
  25 + );
  26 +
  27 + bool isStarted = true;
  28 +
  29 + @override
  30 + Widget build(BuildContext context) {
  31 + return Scaffold(
  32 + backgroundColor: Colors.black,
  33 + body: Builder(
  34 + builder: (context) {
  35 + return Stack(
  36 + children: [
  37 + MobileScanner(
  38 + controller: controller,
  39 + fit: BoxFit.contain,
  40 + // allowDuplicates: true,
  41 + // controller: MobileScannerController(
  42 + // torchEnabled: true,
  43 + // facing: CameraFacing.front,
  44 + // ),
  45 + onDetect: (barcode, args) {
  46 + setState(() {
  47 + this.barcode = barcode.rawValue;
  48 + showDialog(
  49 + context: context,
  50 + builder: (context) => Image(
  51 + image: MemoryImage(image!),
  52 + fit: BoxFit.contain,
  53 + ),
  54 + );
  55 + image = barcode.image;
  56 + });
  57 + },
  58 + ),
  59 + Align(
  60 + alignment: Alignment.bottomCenter,
  61 + child: Container(
  62 + alignment: Alignment.bottomCenter,
  63 + height: 100,
  64 + color: Colors.black.withOpacity(0.4),
  65 + child: Row(
  66 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  67 + children: [
  68 + IconButton(
  69 + color: Colors.white,
  70 + icon: ValueListenableBuilder(
  71 + valueListenable: controller.torchState,
  72 + builder: (context, state, child) {
  73 + if (state == null) {
  74 + return const Icon(
  75 + Icons.flash_off,
  76 + color: Colors.grey,
  77 + );
  78 + }
  79 + switch (state as TorchState) {
  80 + case TorchState.off:
  81 + return const Icon(
  82 + Icons.flash_off,
  83 + color: Colors.grey,
  84 + );
  85 + case TorchState.on:
  86 + return const Icon(
  87 + Icons.flash_on,
  88 + color: Colors.yellow,
  89 + );
  90 + }
  91 + },
  92 + ),
  93 + iconSize: 32.0,
  94 + onPressed: () => controller.toggleTorch(),
  95 + ),
  96 + IconButton(
  97 + color: Colors.white,
  98 + icon: isStarted
  99 + ? const Icon(Icons.stop)
  100 + : const Icon(Icons.play_arrow),
  101 + iconSize: 32.0,
  102 + onPressed: () => setState(() {
  103 + isStarted ? controller.stop() : controller.start();
  104 + isStarted = !isStarted;
  105 + }),
  106 + ),
  107 + Center(
  108 + child: SizedBox(
  109 + width: MediaQuery.of(context).size.width - 200,
  110 + height: 50,
  111 + child: FittedBox(
  112 + child: Text(
  113 + barcode ?? 'Scan something!',
  114 + overflow: TextOverflow.fade,
  115 + style: Theme.of(context)
  116 + .textTheme
  117 + .headline4!
  118 + .copyWith(color: Colors.white),
  119 + ),
  120 + ),
  121 + ),
  122 + ),
  123 + IconButton(
  124 + color: Colors.white,
  125 + icon: ValueListenableBuilder(
  126 + valueListenable: controller.cameraFacingState,
  127 + builder: (context, state, child) {
  128 + if (state == null) {
  129 + return const Icon(Icons.camera_front);
  130 + }
  131 + switch (state as CameraFacing) {
  132 + case CameraFacing.front:
  133 + return const Icon(Icons.camera_front);
  134 + case CameraFacing.back:
  135 + return const Icon(Icons.camera_rear);
  136 + }
  137 + },
  138 + ),
  139 + iconSize: 32.0,
  140 + onPressed: () => controller.switchCamera(),
  141 + ),
  142 + SizedBox(
  143 + width: 50,
  144 + height: 50,
  145 + child: image != null
  146 + ? Image(
  147 + image: MemoryImage(image!),
  148 + fit: BoxFit.contain,
  149 + )
  150 + : Container(),
  151 + ),
  152 + ],
  153 + ),
  154 + ),
  155 + ),
  156 + ],
  157 + );
  158 + },
  159 + ),
  160 + );
  161 + }
  162 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 import 'package:mobile_scanner_example/barcode_scanner_controller.dart'; 2 import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
  3 +import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
3 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart'; 4 import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
4 5
5 void main() => runApp(const MaterialApp(home: MyHome())); 6 void main() => runApp(const MaterialApp(home: MyHome()));
@@ -31,6 +32,17 @@ class MyHome extends StatelessWidget { @@ -31,6 +32,17 @@ class MyHome extends StatelessWidget {
31 onPressed: () { 32 onPressed: () {
32 Navigator.of(context).push( 33 Navigator.of(context).push(
33 MaterialPageRoute( 34 MaterialPageRoute(
  35 + builder: (context) => const BarcodeScannerReturningImage(),
  36 + ),
  37 + );
  38 + },
  39 + child:
  40 + const Text('MobileScanner with Controller (returning image)'),
  41 + ),
  42 + ElevatedButton(
  43 + onPressed: () {
  44 + Navigator.of(context).push(
  45 + MaterialPageRoute(
34 builder: (context) => 46 builder: (context) =>
35 const BarcodeScannerWithoutController(), 47 const BarcodeScannerWithoutController(),
36 ), 48 ),
@@ -22,6 +22,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -22,6 +22,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
22 // Image to be sent to the texture 22 // Image to be sent to the texture
23 var latestBuffer: CVImageBuffer! 23 var latestBuffer: CVImageBuffer!
24 24
  25 + // Return image buffer with the Barcode event
  26 + var returnImage: Bool = false
  27 +
25 // var analyzeMode: Int = 0 28 // var analyzeMode: Int = 0
26 var analyzing: Bool = false 29 var analyzing: Bool = false
27 var position = AVCaptureDevice.Position.back 30 var position = AVCaptureDevice.Position.back
@@ -61,6 +64,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -61,6 +64,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
61 stop(result) 64 stop(result)
62 case "analyzeImage": 65 case "analyzeImage":
63 analyzeImage(call, result) 66 analyzeImage(call, result)
  67 +
64 default: 68 default:
65 result(FlutterMethodNotImplemented) 69 result(FlutterMethodNotImplemented)
66 } 70 }
@@ -86,6 +90,16 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -86,6 +90,16 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
86 return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer) 90 return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
87 } 91 }
88 92
  93 + private func ciImageToJpeg(ciImage: CIImage) -> Data {
  94 +
  95 + // let ciImage = CIImage(cvPixelBuffer: latestBuffer)
  96 + let context:CIContext = CIContext.init(options: nil)
  97 + let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!
  98 + let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)
  99 +
  100 + return uiImage.jpegData(compressionQuality: 0.8)!;
  101 + }
  102 +
89 // Gets called when a new image is added to the buffer 103 // Gets called when a new image is added to the buffer
90 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 104 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
91 105
@@ -108,7 +122,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -108,7 +122,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
108 scanner.process(image) { [self] barcodes, error in 122 scanner.process(image) { [self] barcodes, error in
109 if error == nil && barcodes != nil { 123 if error == nil && barcodes != nil {
110 for barcode in barcodes! { 124 for barcode in barcodes! {
111 - let event: [String: Any?] = ["name": "barcode", "data": barcode.data] 125 +
  126 + var event: [String: Any?] = ["name": "barcode", "data": barcode.data]
  127 + if (returnImage && latestBuffer != nil) {
  128 + let image: CIImage = CIImage(cvPixelBuffer: latestBuffer)
  129 +
  130 + event["image"] = FlutterStandardTypedData(bytes: ciImageToJpeg(ciImage: image))
  131 + }
112 sink?(event) 132 sink?(event)
113 } 133 }
114 } 134 }
@@ -168,6 +188,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -168,6 +188,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
168 188
169 let argReader = MapArgumentReader(call.arguments as? [String: Any]) 189 let argReader = MapArgumentReader(call.arguments as? [String: Any])
170 190
  191 + returnImage = argReader.bool(key: "returnImage") ?? false
  192 +
171 // let ratio: Int = argReader.int(key: "ratio") 193 // let ratio: Int = argReader.int(key: "ratio")
172 let torch: Bool = argReader.bool(key: "torch") ?? false 194 let torch: Bool = argReader.bool(key: "torch") ?? false
173 let facing: Int = argReader.int(key: "facing") ?? 1 195 let facing: Int = argReader.int(key: "facing") ?? 1
1 import 'dart:async'; 1 import 'dart:async';
2 import 'dart:io'; 2 import 'dart:io';
  3 +import 'dart:typed_data';
3 4
4 import 'package:flutter/cupertino.dart'; 5 import 'package:flutter/cupertino.dart';
5 import 'package:flutter/foundation.dart'; 6 import 'package:flutter/foundation.dart';
@@ -44,6 +45,8 @@ class MobileScannerController { @@ -44,6 +45,8 @@ class MobileScannerController {
44 late final ValueNotifier<CameraFacing> cameraFacingState; 45 late final ValueNotifier<CameraFacing> cameraFacingState;
45 final Ratio? ratio; 46 final Ratio? ratio;
46 final bool? torchEnabled; 47 final bool? torchEnabled;
  48 + // Whether to return the image buffer with the Barcode event
  49 + final bool returnImage;
47 50
48 /// If provided, the scanner will only detect those specific formats. 51 /// If provided, the scanner will only detect those specific formats.
49 /// 52 ///
@@ -65,6 +68,7 @@ class MobileScannerController { @@ -65,6 +68,7 @@ class MobileScannerController {
65 this.torchEnabled, 68 this.torchEnabled,
66 this.formats, 69 this.formats,
67 this.autoResume = true, 70 this.autoResume = true,
  71 + this.returnImage = false,
68 }) { 72 }) {
69 // In case a new instance is created before calling dispose() 73 // In case a new instance is created before calling dispose()
70 if (_controllerHashcode != null) { 74 if (_controllerHashcode != null) {
@@ -89,13 +93,15 @@ class MobileScannerController { @@ -89,13 +93,15 @@ class MobileScannerController {
89 void handleEvent(Map event) { 93 void handleEvent(Map event) {
90 final name = event['name']; 94 final name = event['name'];
91 final data = event['data']; 95 final data = event['data'];
  96 +
92 switch (name) { 97 switch (name) {
93 case 'torchState': 98 case 'torchState':
94 final state = TorchState.values[data as int? ?? 0]; 99 final state = TorchState.values[data as int? ?? 0];
95 torchState.value = state; 100 torchState.value = state;
96 break; 101 break;
97 case 'barcode': 102 case 'barcode':
98 - final barcode = Barcode.fromNative(data as Map? ?? {}); 103 + final image = returnImage ? event['image'] as Uint8List : null;
  104 + final barcode = Barcode.fromNative(data as Map? ?? {}, image);
99 barcodesController.add(barcode); 105 barcodesController.add(barcode);
100 break; 106 break;
101 case 'barcodeMac': 107 case 'barcodeMac':
@@ -169,6 +175,7 @@ class MobileScannerController { @@ -169,6 +175,7 @@ class MobileScannerController {
169 arguments['formats'] = formats!.map((e) => e.rawValue).toList(); 175 arguments['formats'] = formats!.map((e) => e.rawValue).toList();
170 } 176 }
171 } 177 }
  178 + arguments['returnImage'] = returnImage;
172 179
173 // Start the camera with arguments 180 // Start the camera with arguments
174 Map<String, dynamic>? startResult = {}; 181 Map<String, dynamic>? startResult = {};
@@ -12,6 +12,11 @@ class Barcode { @@ -12,6 +12,11 @@ class Barcode {
12 /// Returns null if the corner points can not be determined. 12 /// Returns null if the corner points can not be determined.
13 final List<Offset>? corners; 13 final List<Offset>? corners;
14 14
  15 + /// Returns raw bytes of the image buffer
  16 + ///
  17 + /// Returns null if the image was not returned
  18 + final Uint8List? image;
  19 +
15 /// Returns barcode format 20 /// Returns barcode format
16 final BarcodeFormat format; 21 final BarcodeFormat format;
17 22
@@ -74,6 +79,7 @@ class Barcode { @@ -74,6 +79,7 @@ class Barcode {
74 79
75 Barcode({ 80 Barcode({
76 this.corners, 81 this.corners,
  82 + this.image,
77 this.format = BarcodeFormat.ean13, 83 this.format = BarcodeFormat.ean13,
78 this.rawBytes, 84 this.rawBytes,
79 this.type = BarcodeType.text, 85 this.type = BarcodeType.text,
@@ -91,7 +97,7 @@ class Barcode { @@ -91,7 +97,7 @@ class Barcode {
91 }); 97 });
92 98
93 /// Create a [Barcode] from native data. 99 /// Create a [Barcode] from native data.
94 - Barcode.fromNative(Map data) 100 + Barcode.fromNative(Map data, this.image)
95 : corners = toCorners(data['corners'] as List?), 101 : corners = toCorners(data['corners'] as List?),
96 format = toFormat(data['format'] as int), 102 format = toFormat(data['format'] as int),
97 rawBytes = data['rawBytes'] as Uint8List?, 103 rawBytes = data['rawBytes'] as Uint8List?,