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';
}));
}
```
Example with controller and returning images
```dart
import 'package:mobile_scanner/mobile_scanner.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Mobile Scanner')),
body: MobileScanner(
controller: MobileScannerController(
facing: CameraFacing.front,
torchEnabled: true,
returnImage: true,
),
onDetect: (barcode, args) {
if (barcode.rawValue == null) {
debugPrint('Failed to scan Barcode');
} else {
final String code = barcode.rawValue!;
debugPrint('Barcode found! $code');
debugPrint(
'Image returned! length: ${barcode.image!.lengthInBytes}b');
showDialog(
context: context,
builder: (context) => Image(image: MemoryImage(barcode.image!)),
);
Future.delayed(const Duration(seconds: 2), () {
Navigator.pop(context);
});
}
},
),
);
}
```
\ No newline at end of file
... ...
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerReturningImage extends StatefulWidget {
const BarcodeScannerReturningImage({Key? key}) : super(key: key);
@override
_BarcodeScannerReturningImageState createState() =>
_BarcodeScannerReturningImageState();
}
class _BarcodeScannerReturningImageState
extends State<BarcodeScannerReturningImage>
with SingleTickerProviderStateMixin {
String? barcode;
Uint8List? image;
MobileScannerController controller = MobileScannerController(
torchEnabled: true,
returnImage: true,
// formats: [BarcodeFormat.qrCode]
// facing: CameraFacing.front,
);
bool isStarted = true;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
return Stack(
children: [
MobileScanner(
controller: controller,
fit: BoxFit.contain,
// allowDuplicates: true,
// controller: MobileScannerController(
// torchEnabled: true,
// facing: CameraFacing.front,
// ),
onDetect: (barcode, args) {
setState(() {
this.barcode = barcode.rawValue;
showDialog(
context: context,
builder: (context) => Image(
image: MemoryImage(image!),
fit: BoxFit.contain,
),
);
image = barcode.image;
});
},
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.torchState,
builder: (context, state, child) {
if (state == null) {
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
}
switch (state as TorchState) {
case TorchState.off:
return const Icon(
Icons.flash_off,
color: Colors.grey,
);
case TorchState.on:
return const Icon(
Icons.flash_on,
color: Colors.yellow,
);
}
},
),
iconSize: 32.0,
onPressed: () => controller.toggleTorch(),
),
IconButton(
color: Colors.white,
icon: isStarted
? const Icon(Icons.stop)
: const Icon(Icons.play_arrow),
iconSize: 32.0,
onPressed: () => setState(() {
isStarted ? controller.stop() : controller.start();
isStarted = !isStarted;
}),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 200,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: controller.cameraFacingState,
builder: (context, state, child) {
if (state == null) {
return const Icon(Icons.camera_front);
}
switch (state as CameraFacing) {
case CameraFacing.front:
return const Icon(Icons.camera_front);
case CameraFacing.back:
return const Icon(Icons.camera_rear);
}
},
),
iconSize: 32.0,
onPressed: () => controller.switchCamera(),
),
SizedBox(
width: 50,
height: 50,
child: image != null
? Image(
image: MemoryImage(image!),
fit: BoxFit.contain,
)
: Container(),
),
],
),
),
),
],
);
},
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_returning_image.dart';
import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
void main() => runApp(const MaterialApp(home: MyHome()));
... ... @@ -31,6 +32,17 @@ class MyHome extends StatelessWidget {
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const BarcodeScannerReturningImage(),
),
);
},
child:
const Text('MobileScanner with Controller (returning image)'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const BarcodeScannerWithoutController(),
),
... ...
... ... @@ -22,6 +22,9 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
// Image to be sent to the texture
var latestBuffer: CVImageBuffer!
// Return image buffer with the Barcode event
var returnImage: Bool = false
// var analyzeMode: Int = 0
var analyzing: Bool = false
var position = AVCaptureDevice.Position.back
... ... @@ -61,6 +64,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
stop(result)
case "analyzeImage":
analyzeImage(call, result)
default:
result(FlutterMethodNotImplemented)
}
... ... @@ -86,6 +90,16 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
}
private func ciImageToJpeg(ciImage: CIImage) -> Data {
// let ciImage = CIImage(cvPixelBuffer: latestBuffer)
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!
let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)
return uiImage.jpegData(compressionQuality: 0.8)!;
}
// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
... ... @@ -108,7 +122,13 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
scanner.process(image) { [self] barcodes, error in
if error == nil && barcodes != nil {
for barcode in barcodes! {
let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
var event: [String: Any?] = ["name": "barcode", "data": barcode.data]
if (returnImage && latestBuffer != nil) {
let image: CIImage = CIImage(cvPixelBuffer: latestBuffer)
event["image"] = FlutterStandardTypedData(bytes: ciImageToJpeg(ciImage: image))
}
sink?(event)
}
}
... ... @@ -168,6 +188,8 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
let argReader = MapArgumentReader(call.arguments as? [String: Any])
returnImage = argReader.bool(key: "returnImage") ?? false
// let ratio: Int = argReader.int(key: "ratio")
let torch: Bool = argReader.bool(key: "torch") ?? false
let facing: Int = argReader.int(key: "facing") ?? 1
... ...
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
... ... @@ -44,6 +45,8 @@ class MobileScannerController {
late final ValueNotifier<CameraFacing> cameraFacingState;
final Ratio? ratio;
final bool? torchEnabled;
// Whether to return the image buffer with the Barcode event
final bool returnImage;
/// If provided, the scanner will only detect those specific formats.
///
... ... @@ -65,6 +68,7 @@ class MobileScannerController {
this.torchEnabled,
this.formats,
this.autoResume = true,
this.returnImage = false,
}) {
// In case a new instance is created before calling dispose()
if (_controllerHashcode != null) {
... ... @@ -89,13 +93,15 @@ class MobileScannerController {
void handleEvent(Map event) {
final name = event['name'];
final data = event['data'];
switch (name) {
case 'torchState':
final state = TorchState.values[data as int? ?? 0];
torchState.value = state;
break;
case 'barcode':
final barcode = Barcode.fromNative(data as Map? ?? {});
final image = returnImage ? event['image'] as Uint8List : null;
final barcode = Barcode.fromNative(data as Map? ?? {}, image);
barcodesController.add(barcode);
break;
case 'barcodeMac':
... ... @@ -169,6 +175,7 @@ class MobileScannerController {
arguments['formats'] = formats!.map((e) => e.rawValue).toList();
}
}
arguments['returnImage'] = returnImage;
// Start the camera with arguments
Map<String, dynamic>? startResult = {};
... ...
... ... @@ -12,6 +12,11 @@ class Barcode {
/// Returns null if the corner points can not be determined.
final List<Offset>? corners;
/// Returns raw bytes of the image buffer
///
/// Returns null if the image was not returned
final Uint8List? image;
/// Returns barcode format
final BarcodeFormat format;
... ... @@ -74,6 +79,7 @@ class Barcode {
Barcode({
this.corners,
this.image,
this.format = BarcodeFormat.ean13,
this.rawBytes,
this.type = BarcodeType.text,
... ... @@ -91,7 +97,7 @@ class Barcode {
});
/// Create a [Barcode] from native data.
Barcode.fromNative(Map data)
Barcode.fromNative(Map data, this.image)
: corners = toCorners(data['corners'] as List?),
format = toFormat(data['format'] as int),
rawBytes = data['rawBytes'] as Uint8List?,
... ...