- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
and are two Flutter plugins built on top of the Dynamsoft Capture Vision SDK. They provide easy-to-use, cross-platform APIs for adding MRZ recognition and barcode scanning capabilities to Flutter apps. In this article, you'll learn how to create a Flutter app that integrates both plugins to scan machine-readable zones (MRZ) and 1D/2D barcodes. Instead of starting from scratch, we'll combine two existing example projects into a unified solution.
Demo Video: Flutter MRZ and Barcode Scanner
Prerequisites
The app will include:
We'll start by modifying the source code from the Flutter MRZ Scanner project to add UI and functionality.
Step 1: Install Dependencies
Add the following dependencies to your pubspec.yaml file:
flutter_barcode_sdk: ^3.0.3
flutter_ocr_sdk: ^2.0.3
Step 2: Initialize the SDKs
Since both plugins wrap the same Dynamsoft SDK, you can initialize them with a shared license key in global.dart.
FlutterOcrSdk detector = FlutterOcrSdk();
FlutterBarcodeSdk barcodeReader = FlutterBarcodeSdk();
bool isLicenseValid = false;
Future<int> initSDK() async {
String licenseKey =
"LICENSE-KEY";
int? ret = await detector.init(licenseKey);
ret = await barcodeReader.init();
ret = await barcodeReader.setLicense(licenseKey);
if (ret == 0) isLicenseValid = true;
return await detector.loadModel(modelType: ModelType.mrz) ?? -1;
}
Step 3: Toggle MRZ and Barcode Modes
In home_page.dart, update the toggle button to switch between MRZ and Barcode modes:
ToggleButtons(
borderRadius: BorderRadius.circular(10),
isSelected: [isMrzSelected, !isMrzSelected],
selectedColor: Colors.white,
fillColor: Colors.orange,
color: Colors.grey,
children: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('MRZ'),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('Barcode'),
),
],
onPressed: (index) {
setState(() {
isMrzSelected = (index == 0);
});
},
)
The global boolean variable isMrzSelected determines which scanning mode is active.
Step 4: Scan from Image File
Update the scanImage() method in home_page.dart to perform MRZ or barcode recognition depending on the selected mode:
void openMrzResultPage(OcrLine information) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MrzResultPage(information: information),
),
);
}
void openBarcodeResultPage(List<BarcodeResult> barcodeResults) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BarcodeResultPage(barcodeResults: barcodeResults),
),
);
}
void scanImage() async {
XFile? photo = await picker.pickImage(source: ImageSource.gallery);
if (photo == null) {
return;
}
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
File rotatedImage = await FlutterExifRotation.rotateImage(
path: photo.path,
);
photo = XFile(rotatedImage.path);
}
Uint8List fileBytes = await photo.readAsBytes();
ui.Image image = await decodeImageFromList(fileBytes);
ByteData? byteData = await image.toByteData(
format: ui.ImageByteFormat.rawRgba,
);
if (byteData != null) {
if (isMrzSelected) {
List<List<OcrLine>>? results = await detector.recognizeBuffer(
byteData.buffer.asUint8List(),
image.width,
image.height,
byteData.lengthInBytes ~/ image.height,
ImagePixelFormat.IPF_ARGB_8888.index,
ImageRotation.rotation0.value,
);
if (results != null && results[0].isNotEmpty) {
openMrzResultPage(results[0][0]);
} else {
showAlert(context, "OCR Result", "Recognition Failed!");
}
} else {
List<BarcodeResult>? results = await barcodeReader.decodeImageBuffer(
byteData.buffer.asUint8List(),
image.width,
image.height,
byteData.lengthInBytes ~/ image.height,
ImagePixelFormat.IPF_ARGB_8888.index,
);
if (results.isNotEmpty) {
openBarcodeResultPage(results);
} else {
showAlert(context, "Barcode Result", "Detection Failed!");
}
}
}
}
Both result pages already exist in the sample projects. Rename them to avoid conflicts.
Step 5: Real-time Scanning
In camera_manager.dart, update functions like webCamera(), processId(), and _decodeFrame() to call the appropriate APIs based on isMrzSelected. This enables real-time MRZ and barcode scanning across platforms.
Future<void> webCamera() async {
_isWebFrameStarted = true;
try {
while (!(controller == null || isFinished || cbIsMounted() == false)) {
XFile file = await controller!.takePicture();
dynamic results;
if (isMrzSelected) {
results = await detector.recognizeFile(file.path);
ocrLines = results;
} else {
results = await barcodeReader.decodeFile(file.path);
barcodeResults = results;
}
if (results == null || !cbIsMounted()) return;
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results);
}
}
} catch (e) {
print(e);
}
_isWebFrameStarted = false;
}
Future<void> processId(
Uint8List bytes, int width, int height, int stride, int format) async {
int rotation = 0;
bool isAndroidPortrait = false;
if (MediaQuery.of(context).size.width <
MediaQuery.of(context).size.height) {
if (Platform.isAndroid) {
rotation = OCR.ImageRotation.rotation90.value;
isAndroidPortrait = true;
}
}
dynamic results;
if (isMrzSelected) {
ocrLines = await detector.recognizeBuffer(
bytes, width, height, stride, format, rotation);
results = ocrLines;
} else {
barcodeResults = await barcodeReader.decodeImageBuffer(
bytes, width, height, stride, format);
if (isAndroidPortrait &&
barcodeResults != null &&
barcodeResults!.isNotEmpty) {
barcodeResults =
rotate90barcode(barcodeResults!, previewSize!.height.toInt());
}
results = barcodeResults;
}
_isScanAvailable = true;
if (results == null || !cbIsMounted()) return;
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results!);
}
}
Future<void> _decodeFrame(Uint8List rgb, int width, int height) async {
if (isDecoding) return;
isDecoding = true;
dynamic results;
if (isMrzSelected) {
ocrLines = await detector.recognizeBuffer(
rgb,
width,
height,
width * 3,
ImagePixelFormat.IPF_RGB_888.index,
OCR.ImageRotation.rotation0.value);
results = ocrLines;
} else {
barcodeResults = await barcodeReader.decodeImageBuffer(
rgb, width, height, width * 3, ImagePixelFormat.IPF_RGB_888.index);
results = barcodeResults;
}
if (cbIsMounted()) {
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results!);
}
}
isDecoding = false;
}
Step 6: Display Scan Overlays
To render overlays for MRZ and barcode results:
Run the app on Windows, Linux, Android, iOS, and Web:
flutter run -d chrome # Web
flutter run -d linux # Linux
flutter run -d windows # Windows
flutter run # Android or iOS
Source Code
Demo Video: Flutter MRZ and Barcode Scanner
Prerequisites
- for Dynamsoft Capture Vision SDK.
- Download the two example projects: and . We'll merge these projects to create a new app.
The app will include:
- A toggle button to switch between MRZ and Barcode scanning modes
- A button to load an image file for detection
- A button to open the camera for live detection
- A camera view for real-time MRZ and 1D/2D barcode scanning
- A result view to display scan results from camera streams and images
We'll start by modifying the source code from the Flutter MRZ Scanner project to add UI and functionality.
Step 1: Install Dependencies
Add the following dependencies to your pubspec.yaml file:
flutter_barcode_sdk: ^3.0.3
flutter_ocr_sdk: ^2.0.3
Step 2: Initialize the SDKs
Since both plugins wrap the same Dynamsoft SDK, you can initialize them with a shared license key in global.dart.
FlutterOcrSdk detector = FlutterOcrSdk();
FlutterBarcodeSdk barcodeReader = FlutterBarcodeSdk();
bool isLicenseValid = false;
Future<int> initSDK() async {
String licenseKey =
"LICENSE-KEY";
int? ret = await detector.init(licenseKey);
ret = await barcodeReader.init();
ret = await barcodeReader.setLicense(licenseKey);
if (ret == 0) isLicenseValid = true;
return await detector.loadModel(modelType: ModelType.mrz) ?? -1;
}
Step 3: Toggle MRZ and Barcode Modes
In home_page.dart, update the toggle button to switch between MRZ and Barcode modes:
ToggleButtons(
borderRadius: BorderRadius.circular(10),
isSelected: [isMrzSelected, !isMrzSelected],
selectedColor: Colors.white,
fillColor: Colors.orange,
color: Colors.grey,
children: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('MRZ'),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Text('Barcode'),
),
],
onPressed: (index) {
setState(() {
isMrzSelected = (index == 0);
});
},
)
The global boolean variable isMrzSelected determines which scanning mode is active.
Step 4: Scan from Image File
Update the scanImage() method in home_page.dart to perform MRZ or barcode recognition depending on the selected mode:
void openMrzResultPage(OcrLine information) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MrzResultPage(information: information),
),
);
}
void openBarcodeResultPage(List<BarcodeResult> barcodeResults) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BarcodeResultPage(barcodeResults: barcodeResults),
),
);
}
void scanImage() async {
XFile? photo = await picker.pickImage(source: ImageSource.gallery);
if (photo == null) {
return;
}
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
File rotatedImage = await FlutterExifRotation.rotateImage(
path: photo.path,
);
photo = XFile(rotatedImage.path);
}
Uint8List fileBytes = await photo.readAsBytes();
ui.Image image = await decodeImageFromList(fileBytes);
ByteData? byteData = await image.toByteData(
format: ui.ImageByteFormat.rawRgba,
);
if (byteData != null) {
if (isMrzSelected) {
List<List<OcrLine>>? results = await detector.recognizeBuffer(
byteData.buffer.asUint8List(),
image.width,
image.height,
byteData.lengthInBytes ~/ image.height,
ImagePixelFormat.IPF_ARGB_8888.index,
ImageRotation.rotation0.value,
);
if (results != null && results[0].isNotEmpty) {
openMrzResultPage(results[0][0]);
} else {
showAlert(context, "OCR Result", "Recognition Failed!");
}
} else {
List<BarcodeResult>? results = await barcodeReader.decodeImageBuffer(
byteData.buffer.asUint8List(),
image.width,
image.height,
byteData.lengthInBytes ~/ image.height,
ImagePixelFormat.IPF_ARGB_8888.index,
);
if (results.isNotEmpty) {
openBarcodeResultPage(results);
} else {
showAlert(context, "Barcode Result", "Detection Failed!");
}
}
}
}
Both result pages already exist in the sample projects. Rename them to avoid conflicts.
Step 5: Real-time Scanning
In camera_manager.dart, update functions like webCamera(), processId(), and _decodeFrame() to call the appropriate APIs based on isMrzSelected. This enables real-time MRZ and barcode scanning across platforms.
Future<void> webCamera() async {
_isWebFrameStarted = true;
try {
while (!(controller == null || isFinished || cbIsMounted() == false)) {
XFile file = await controller!.takePicture();
dynamic results;
if (isMrzSelected) {
results = await detector.recognizeFile(file.path);
ocrLines = results;
} else {
results = await barcodeReader.decodeFile(file.path);
barcodeResults = results;
}
if (results == null || !cbIsMounted()) return;
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results);
}
}
} catch (e) {
print(e);
}
_isWebFrameStarted = false;
}
Future<void> processId(
Uint8List bytes, int width, int height, int stride, int format) async {
int rotation = 0;
bool isAndroidPortrait = false;
if (MediaQuery.of(context).size.width <
MediaQuery.of(context).size.height) {
if (Platform.isAndroid) {
rotation = OCR.ImageRotation.rotation90.value;
isAndroidPortrait = true;
}
}
dynamic results;
if (isMrzSelected) {
ocrLines = await detector.recognizeBuffer(
bytes, width, height, stride, format, rotation);
results = ocrLines;
} else {
barcodeResults = await barcodeReader.decodeImageBuffer(
bytes, width, height, stride, format);
if (isAndroidPortrait &&
barcodeResults != null &&
barcodeResults!.isNotEmpty) {
barcodeResults =
rotate90barcode(barcodeResults!, previewSize!.height.toInt());
}
results = barcodeResults;
}
_isScanAvailable = true;
if (results == null || !cbIsMounted()) return;
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results!);
}
}
Future<void> _decodeFrame(Uint8List rgb, int width, int height) async {
if (isDecoding) return;
isDecoding = true;
dynamic results;
if (isMrzSelected) {
ocrLines = await detector.recognizeBuffer(
rgb,
width,
height,
width * 3,
ImagePixelFormat.IPF_RGB_888.index,
OCR.ImageRotation.rotation0.value);
results = ocrLines;
} else {
barcodeResults = await barcodeReader.decodeImageBuffer(
rgb, width, height, width * 3, ImagePixelFormat.IPF_RGB_888.index);
results = barcodeResults;
}
if (cbIsMounted()) {
cbRefreshUi();
if (isReadyToGo && results != null) {
handleResults(results!);
}
}
isDecoding = false;
}
Step 6: Display Scan Overlays
To render overlays for MRZ and barcode results:
Update createCameraPreview() in camera_page.dart:
List<Widget> createCameraPreview() {
Positioned widget;
if (isMrzSelected) {
widget = Positioned(
top: 0.0,
right: 0.0,
bottom: 0,
left: 0.0,
child: createOverlay(_cameraManager.ocrLines),
);
} else {
widget = Positioned(
top: 0.0,
right: 0.0,
bottom: 0,
left: 0.0,
child: createOverlay(_cameraManager.barcodeResults),
);
}
if (!kIsWeb &&
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
return [
SizedBox(width: 640, height: 480, child: _cameraManager.getPreview()),
widget,
];
} else {
if (_cameraManager.controller != null &&
_cameraManager.previewSize != null) {
double width = _cameraManager.previewSize!.width;
double height = _cameraManager.previewSize!.height;
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
if (MediaQuery.of(context).size.width <
MediaQuery.of(context).size.height) {
width = _cameraManager.previewSize!.height;
height = _cameraManager.previewSize!.width;
}
}
return [
SizedBox(
width: width,
height: height,
child: _cameraManager.getPreview(),
),
widget,
];
} else {
return [const CircularProgressIndicator()];
}
}
}
Add overlay rendering logic for barcodes in global.dart:
class OverlayPainter extends CustomPainter {
List<List<OcrLine>>? ocrResults;
List<BarcodeResult>? barcodeResults;
OverlayPainter(dynamic results) {
if (results is List<List<OcrLine>>) {
ocrResults = results;
} else if (results is List<BarcodeResult>) {
barcodeResults = results;
}
}
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 5
..style = PaintingStyle.stroke;
final textStyle = TextStyle(
color: Colors.red,
fontSize: 16,
);
if (ocrResults != null) {
for (List<OcrLine> area in ocrResults!) {
for (OcrLine line in area) {
canvas.drawLine(Offset(line.x1.toDouble(), line.y1.toDouble()),
Offset(line.x2.toDouble(), line.y2.toDouble()), paint);
canvas.drawLine(Offset(line.x2.toDouble(), line.y2.toDouble()),
Offset(line.x3.toDouble(), line.y3.toDouble()), paint);
canvas.drawLine(Offset(line.x3.toDouble(), line.y3.toDouble()),
Offset(line.x4.toDouble(), line.y4.toDouble()), paint);
canvas.drawLine(Offset(line.x4.toDouble(), line.y4.toDouble()),
Offset(line.x1.toDouble(), line.y1.toDouble()), paint);
final textSpan = TextSpan(
text: line.text,
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
);
textPainter.layout();
final offset = Offset(
line.x1.toDouble(),
line.y1.toDouble(),
);
textPainter.paint(canvas, offset);
}
}
}
if (barcodeResults != null) {
for (var result in barcodeResults!) {
double minX = result.x1.toDouble();
double minY = result.y1.toDouble();
if (result.x2 < minX) minX = result.x2.toDouble();
if (result.x3 < minX) minX = result.x3.toDouble();
if (result.x4 < minX) minX = result.x4.toDouble();
if (result.y2 < minY) minY = result.y2.toDouble();
if (result.y3 < minY) minY = result.y3.toDouble();
if (result.y4 < minY) minY = result.y4.toDouble();
canvas.drawLine(Offset(result.x1.toDouble(), result.y1.toDouble()),
Offset(result.x2.toDouble(), result.y2.toDouble()), paint);
canvas.drawLine(Offset(result.x2.toDouble(), result.y2.toDouble()),
Offset(result.x3.toDouble(), result.y3.toDouble()), paint);
canvas.drawLine(Offset(result.x3.toDouble(), result.y3.toDouble()),
Offset(result.x4.toDouble(), result.y4.toDouble()), paint);
canvas.drawLine(Offset(result.x4.toDouble(), result.y4.toDouble()),
Offset(result.x1.toDouble(), result.y1.toDouble()), paint);
TextPainter textPainter = TextPainter(
text: TextSpan(
text: result.text,
style: const TextStyle(
color: Colors.yellow,
fontSize: 22.0,
),
),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
textPainter.layout(minWidth: 0, maxWidth: size.width);
textPainter.paint(canvas, Offset(minX, minY));
}
}
}
@override
bool shouldRepaint(OverlayPainter oldDelegate) => true;
}
Run the app on Windows, Linux, Android, iOS, and Web:
flutter run -d chrome # Web
flutter run -d linux # Linux
flutter run -d windows # Windows
flutter run # Android or iOS
Source Code