How to Create a Flutter Plugin for Passport MRZ Scan on Windows, Linux, Android, iOS, and Web
Given that Dynamsoft Capture Vision offers support for Windows, Linux, Android, iOS and Web, it is feasible to develop a 5 in 1 Flutter plugin for it to encompass the development of desktop, mobile and web platforms. In this article, you will learn how to write an MRZ Flutter plugin by leveraging different platform-specific code, including Java, Swift, Dart, C++ and JavaScript.
This article is Part 1 in a 4-Part Series.
- Part 1 - How to Create a Flutter Plugin for Passport MRZ Scan on Windows, Linux, Android, iOS, and Web
- Part 2 - MRZ Recognition on Android Using Dynamsoft MRZ Scanner SDK
- Part 3 - Developing a Desktop MRZ Scanner for Passports, IDs, and Visas with Dynamsoft C++ Capture Vision SDK
- Part 4 - How to Build a Web MRZ Scanner and Reader with HTML5 and JavaScript
Downloading flutter_ocr_sdk from Pub.dev
https://pub.dev/packages/flutter_ocr_sdk
Initializing the Flutter MRZ Detection Plugin for Multiple Platforms
We create a Flutter plugin project for multiple platforms by executing the following command:
flutter create --org com.dynamsoft --template=plugin --platforms=android,ios,windows,linux,web -a java flutter_ocr_sdk
Cross-Platform Library Linking
The ways of linking third-party libraries in a Flutter plugin project differ across multiple platforms.
Windows and Linux (CMake)
Both platforms use CMake for build configuration. Copy the SDK headers, shared libraries, and resources to the target platform folder and then configure the CMakeLists.txt
file.
Windows Configuration
cmake_minimum_required(VERSION 3.14)
set(PROJECT_NAME "flutter_ocr_sdk")
project(${PROJECT_NAME} LANGUAGES CXX)
set(PLUGIN_NAME "flutter_ocr_sdk_plugin")
link_directories("${PROJECT_SOURCE_DIR}/lib/")
list(APPEND PLUGIN_SOURCES
"flutter_ocr_sdk_plugin.cpp"
"flutter_ocr_sdk_plugin.h"
)
add_library(${PLUGIN_NAME} SHARED
"include/flutter_ocr_sdk/flutter_ocr_sdk_plugin_c_api.h"
"flutter_ocr_sdk_plugin_c_api.cpp"
${PLUGIN_SOURCES}
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64")
set(flutter_ocr_sdk_bundled_libraries
"${PROJECT_SOURCE_DIR}/bin/"
PARENT_SCOPE
)
Linux Configuration
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME "flutter_ocr_sdk")
project(${PROJECT_NAME} LANGUAGES CXX)
set(PLUGIN_NAME "flutter_ocr_sdk_plugin")
link_directories("${PROJECT_SOURCE_DIR}/lib/")
add_library(${PLUGIN_NAME} SHARED
"flutter_ocr_sdk_plugin.cc"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility")
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
set(flutter_ocr_sdk_bundled_libraries
""
PARENT_SCOPE
)
install(DIRECTORY
"${PROJECT_SOURCE_DIR}/../linux/lib/"
DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/"
)
Owing to the fact that the size of the resources is excessively large, a extract_resources.dart
file should be created to download the resources from the internet.
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:http/http.dart' as http;
import 'package:archive/archive.dart';
const _zipUrl =
'https://github.com/yushulx/flutter_ocr_sdk/releases/download/v2.0.0/resources.zip';
const _zipName = 'resources.zip';
Future<void> download() async {
final execDir = Directory(p.dirname(Platform.resolvedExecutable));
final targetDir =
Platform.isWindows ? execDir : Directory(p.join(execDir.path, 'lib'));
targetDir.createSync(recursive: true);
final cacheDir =
Directory(p.join(Directory.systemTemp.path, 'flutter_ocr_sdk'));
cacheDir.createSync(recursive: true);
final zipCacheFile = File(p.join(cacheDir.path, _zipName));
if (!zipCacheFile.existsSync()) {
stdout.writeln('Downloading resources.zip to ${zipCacheFile.path} …');
final resp = await http.get(Uri.parse(_zipUrl));
if (resp.statusCode != 200) {
stderr.writeln('Failed to download ($_zipUrl): HTTP ${resp.statusCode}');
exit(1);
}
zipCacheFile.writeAsBytesSync(resp.bodyBytes);
stdout.writeln('✅ Cached zip in temp.');
} else {
stdout.writeln('✅ Using cached zip: ${zipCacheFile.path}');
}
final bytes = zipCacheFile.readAsBytesSync();
final archive = ZipDecoder().decodeBytes(bytes);
for (final file in archive) {
final outPath = p.join(targetDir.path, file.name);
if (file.isFile) {
final outFile = File(outPath);
outFile.createSync(recursive: true);
outFile.writeAsBytesSync(file.content as List<int>);
} else {
Directory(outPath).createSync(recursive: true);
}
}
stdout.writeln('✅ Resources unpacked to: ${targetDir.path}');
}
Android (Gradle)
Add the Dynamsoft Maven repository and dependency to android/build.gradle:
group 'com.dynamsoft.flutter_ocr_sdk'
version '1.0'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
}
}
rootProject.allprojects {
repositories {
maven {
url "https://download2.dynamsoft.com/maven/aar"
}
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
if (project.android.hasProperty('namespace')) {
namespace 'com.dynamsoft.flutter_ocr_sdk'
}
compileSdkVersion 31
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 21
}
}
dependencies {
implementation 'com.dynamsoft:mrzscannerbundle:3.0.0'
}
iOS (CocoaPods)
Update ios/flutter_ocr_sdk.podspec
to include the SDK dependency:
Pod::Spec.new do |s|
s.name = 'flutter_ocr_sdk'
s.version = '1.0.0'
s.summary = 'A wrapper for Dynamsoft OCR SDK, detecting MRZ in passports, travel documents, and ID cards.'
s.description = <<-DESC
A wrapper for Dynamsoft OCR SDK, detecting MRZ in passports, travel documents, and ID cards.
DESC
s.homepage = 'https://github.com/yushulx/flutter_ocr_sdk'
s.license = { :file => '../LICENSE' }
s.author = { 'yushulx' => 'lingxiao1002@gmail.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '9.0'
s.dependency 'DynamsoftMRZScannerBundle', '3.0.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end
Web (JavaScript)
Include the Dynamsoft Capture Vision JavaScript library in index.html
:
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@2.6.1000/dist/dcv.bundle.min.js"></script>
Dart API Implementation
Platform Interface Definition
Define the cross-platform interface in flutter_ocr_sdk_platform_interface.dart
:
abstract class FlutterOcrSdkPlatform extends PlatformInterface {
FlutterOcrSdkPlatform() : super(token: _token);
static final Object _token = Object();
static FlutterOcrSdkPlatform _instance = MethodChannelFlutterOcrSdk();
static FlutterOcrSdkPlatform get instance => _instance;
static set instance(FlutterOcrSdkPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<int?> init(String path, String key) {
throw UnimplementedError('init() has not been implemented.');
}
Future<List<List<MrzLine>>?> recognizeByBuffer(
Uint8List bytes, int width, int height, int stride, int format) {
throw UnimplementedError('recognizeByBuffer() has not been implemented.');
}
Future<List<List<MrzLine>>?> recognizeByFile(String filename) {
throw UnimplementedError('recognizeByFile() has not been implemented.');
}
Future<int?> loadModel() async {
throw UnimplementedError('loadModel() has not been implemented.');
}
}
init()
: initialize the SDK with the license key of Dynamsoft Capture Vision.loadModel()
: load the MRZ recognition model.recognizeBuffer()
: recognize the MRZ from the image buffer.recognizeFile()
: recognize the MRZ from the image file.
The flutter_ocr_sdk_web.dart
file implements the methods for web, and the flutter_ocr_sdk_method_channel.dart
file implements the methods for Android, iOS, Windows and Linux.
Web Implementation (flutter_ocr_sdk_web.dart)
-
In the
web_dlr_manager.dart
file, declare the JavaScript properties and functions to be called:@JS('Dynamsoft') library dynamsoft; import 'package:flutter_ocr_sdk/template.dart'; import 'vin_result.dart'; import 'model_type.dart'; import 'mrz_result.dart'; import 'ocr_line.dart'; import 'dart:async'; import 'dart:typed_data'; import 'package:js/js.dart'; import 'utils.dart'; import 'dart:js_util'; @JS() @anonymous class RecognitionResult { external String get codeType; external String get jsonString; external String getFieldValue(String field); } @JS() @anonymous class CapturedResult { external List<CapturedItem> get items; } @JS() @anonymous class CapturedItem { external int get type; external String get text; external String get formatString; external Location get location; external int get angle; external Uint8List get bytes; external int get confidence; } @JS() @anonymous class Location { external List<Point> get points; } @JS() @anonymous class Point { external num get x; external num get y; } @JS('License.LicenseManager') class LicenseManager { external static PromiseJsImpl<void> initLicense( String license, bool executeNow); } @JS('Core.CoreModule') class CoreModule { external static PromiseJsImpl<void> loadWasm(List<String> modules); } @JS('DCP.CodeParserModule') class CodeParserModule { external static PromiseJsImpl<void> loadSpec(String resource); } @JS('DLR.LabelRecognizerModule') class LabelRecognizerModule { external static PromiseJsImpl<void> loadRecognitionData(String resource); } @JS('DCP.CodeParser') class CodeParser { external static PromiseJsImpl<CodeParser> createInstance(); external PromiseJsImpl<RecognitionResult> parse(String text); }
-
Create a
CaptureVisionManager
class to interoperate with the JavaScript library:class CaptureVisionManager { CaptureVisionRouter? _cvr; CodeParser? _parser; String modelName = ''; Future<int> init(String key) async { try { await handleThenable(LicenseManager.initLicense(key, true)); await handleThenable(CoreModule.loadWasm(["DLR"])); _parser = await handleThenable(CodeParser.createInstance()); await handleThenable(CodeParserModule.loadSpec("VIN")); await handleThenable(LabelRecognizerModule.loadRecognitionData("VIN")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD1_ID")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_ID")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_VISA")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD3_PASSPORT")); await handleThenable(CodeParserModule.loadSpec("MRTD_TD3_VISA")); await handleThenable(LabelRecognizerModule.loadRecognitionData("MRZ")); _cvr = await handleThenable(CaptureVisionRouter.createInstance()); await handleThenable(_cvr!.initSettings(template)); } catch (e) { print(e); return -1; } return 0; } Future<List<List<OcrLine>>?> recognizeFile(String file) async { CapturedResult capturedResult = await handleThenable(_cvr!.capture(file, modelName)); return await _resultWrapper(capturedResult.items); } Future<List<List<OcrLine>>?> recognizeBuffer(Uint8List bytes, int width, int height, int stride, int format, int rotation) async { final dsImage = jsify({ 'bytes': bytes, 'width': width, 'height': height, 'stride': stride, 'format': format, 'orientation': rotation }); CapturedResult capturedResult = await handleThenable(_cvr!.capture(dsImage, modelName)); return await _resultWrapper(capturedResult.items); } Future<int?> loadModel(ModelType modelType) async { if (modelType == ModelType.mrz) { modelName = "ReadMRZ"; } else { modelName = "ReadVINText"; } return 0; } Future<List<List<OcrLine>>> _resultWrapper(List<dynamic> results) async { List<List<OcrLine>> output = []; List<OcrLine> lines = []; for (CapturedItem result in results) { if (result.type != 4) continue; OcrLine line = OcrLine(); line.confidence = result.confidence; line.text = result.text; line.x1 = result.location.points[0].x.toInt(); line.y1 = result.location.points[0].y.toInt(); line.x2 = result.location.points[1].x.toInt(); line.y2 = result.location.points[1].y.toInt(); line.x3 = result.location.points[2].x.toInt(); line.y3 = result.location.points[2].y.toInt(); line.x4 = result.location.points[3].x.toInt(); line.y4 = result.location.points[3].y.toInt(); RecognitionResult data = await handleThenable(_parser!.parse(result.text)); if (modelName == "ReadMRZ") { String type = data.getFieldValue("documentCode"); String mrzString = line.text; String docType = data.codeType; String nationality = data.getFieldValue("nationality"); String surname = data.getFieldValue("primaryIdentifier"); String givenName = data.getFieldValue("secondaryIdentifier"); String docNumber = type == "P" ? data.getFieldValue("passportNumber") : data.getFieldValue("documentNumber"); String issuingCountry = data.getFieldValue("issuingState"); String birthDate = data.getFieldValue("dateOfBirth"); String gender = data.getFieldValue("sex"); String expiration = data.getFieldValue("dateOfExpiry"); MrzResult mrzResult = MrzResult( type: docType, nationality: nationality, surname: surname, givenName: givenName, docNumber: docNumber, issuingCountry: issuingCountry, birthDate: birthDate, gender: gender, expiration: expiration, lines: mrzString); line.mrzResult = mrzResult; } lines.add(line); } output.add(lines); return output; } }
Mobile/Desktop Implementation (flutter_ocr_sdk_method_channel.dart)
@override
Future<int?> init(String key) async {
if (Platform.isLinux || Platform.isWindows) {
await download();
}
return await methodChannel.invokeMethod<int>('init', {'key': key});
}
@override
Future<List<List<OcrLine>>?> recognizeBuffer(Uint8List bytes, int width,
int height, int stride, int format, int rotation) async {
List<dynamic>? results =
await methodChannel.invokeMethod('recognizeBuffer', {
'bytes': bytes,
'width': width,
'height': height,
'stride': stride,
'format': format,
'rotation': rotation
});
if (results == null || results.isEmpty) return [];
return _resultWrapper(results);
}
@override
Future<List<List<OcrLine>>?> recognizeFile(String filename) async {
List<dynamic>? results = await methodChannel.invokeMethod('recognizeFile', {
'filename': filename,
});
if (results == null || results.isEmpty) return [];
return _resultWrapper(results);
}
List<List<OcrLine>> _resultWrapper(List<dynamic> data) {
List<List<OcrLine>> results = [];
for (List<dynamic> area in data) {
List<OcrLine> lines = [];
for (int i = 0; i < area.length; i++) {
OcrLine line = OcrLine();
Map<dynamic, dynamic> map = area[i];
if (map.isEmpty) continue;
line.confidence = map['confidence'];
line.text = map['text'];
line.x1 = map['x1'];
line.y1 = map['y1'];
line.x2 = map['x2'];
line.y2 = map['y2'];
line.x3 = map['x3'];
line.y3 = map['y3'];
line.x4 = map['x4'];
line.y4 = map['y4'];
if (map['type'] == 'MRZ') {
String mrzString = map['mrzString'];
String docType = map["docType"];
String nationality = map["nationality"];
String surname = map["surname"];
String givenName = map["givenName"];
String docNumber = map["docNumber"];
String issuingCountry = map["issuingCountry"];
String birthDate = map["birthDate"];
String gender = map["gender"];
String expiration = map["expiration"];
MrzResult mrzResult = MrzResult(
type: docType,
nationality: nationality,
surname: surname,
givenName: givenName,
docNumber: docNumber,
issuingCountry: issuingCountry,
birthDate: birthDate,
gender: gender,
expiration: expiration,
lines: mrzString);
line.mrzResult = mrzResult;
} else if (map['type'] == 'VIN') {
String vinString = map['vinString'];
String wmi = map['wmi'];
String region = map['region'];
String vds = map['vds'];
String checkDigit = map['checkDigit'];
String modelYear = map['modelYear'];
String plantCode = map['plantCode'];
String serialNumber = map['serialNumber'];
VinResult vinResult = VinResult(
vinString: vinString,
wmi: wmi,
region: region,
vds: vds,
checkDigit: checkDigit,
modelYear: modelYear,
plantCode: plantCode,
serialNumber: serialNumber);
line.vinResult = vinResult;
}
lines.add(line);
}
results.add(lines);
}
return results;
}
@override
Future<int?> loadModel({ModelType modelType = ModelType.mrz}) async {
String templateName = "ReadPassportAndId";
if (modelType == ModelType.vin) {
templateName = "ReadVINText";
}
int ret = await methodChannel
.invokeMethod('loadModel', {'template': templateName});
return ret;
}
Native Platform Implementations
In the aforementioned section, we have implemented the MRZ recognition logic in Dart. For web applications, the Dart code can be directly utilized. At present, we will write Java, Swift, and C++ code for Android, iOS, Windows, and Linux platforms.
Android (Java)
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
switch (call.method) {
case "init": {
final String license = call.argument("key");
mOCRManager.init(license, activity, result);
break;
}
case "recognizeFile": {
final String filename = call.argument("filename");
final Result r = result;
mExecutor.execute(new Runnable() {
@Override
public void run() {
final ArrayList<ArrayList<HashMap<String, Object>>> results = mOCRManager.recognizeFile(filename);
mHandler.post(new Runnable() {
@Override
public void run() {
r.success(results);
}
});
}
});
}
break;
case "recognizeBuffer": {
final byte[] bytes = call.argument("bytes");
final int width = call.argument("width");
final int height = call.argument("height");
final int stride = call.argument("stride");
final int format = call.argument("format");
final int rotation = call.argument("rotation");
final Result r = result;
mExecutor.execute(new Runnable() {
@Override
public void run() {
final ArrayList<ArrayList<HashMap<String, Object>>> results = mOCRManager.recognizeBuffer(bytes, width, height, stride, format, rotation);
mHandler.post(new Runnable() {
@Override
public void run() {
r.success(results);
}
});
}
});
}
break;
case "loadModel": {
final String name = call.argument("template");
mOCRManager.loadModel(name);
result.success(0);
}
break;
default:
result.notImplemented();
}
}
iOS (Swift)
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments: NSDictionary = call.arguments as! NSDictionary
switch call.method {
case "init":
completionHandlers.append(result)
let license: String = arguments.value(forKey: "key") as! String
LicenseManager.initLicense(license, verificationDelegate: self)
case "loadModel":
let name: String = arguments.value(forKey: "template") as! String
templateName = name
result(0)
case "recognizeFile":
DispatchQueue.global().async {
let filename: String = arguments.value(forKey: "filename") as! String
let res = self.cvr.captureFromFile(filename, templateName: self.templateName)
result(self.wrapResults(result: res))
}
case "recognizeBuffer":
DispatchQueue.global().async {
let buffer: FlutterStandardTypedData =
arguments.value(forKey: "bytes") as! FlutterStandardTypedData
let width: Int = arguments.value(forKey: "width") as! Int
let height: Int = arguments.value(forKey: "height") as! Int
let stride: Int = arguments.value(forKey: "stride") as! Int
let format: Int = arguments.value(forKey: "format") as! Int
let rotation: Int = arguments.value(forKey: "rotation") as! Int
let enumImagePixelFormat = ImagePixelFormat(rawValue: format)
let imageData = ImageData.init()
imageData.bytes = buffer.data
imageData.width = UInt(width)
imageData.height = UInt(height)
imageData.stride = UInt(stride)
imageData.format = enumImagePixelFormat!
imageData.orientation = rotation
let res = self.cvr.captureFromBuffer(imageData, templateName: self.templateName)
result(self.wrapResults(result: res))
}
default:
result(.none)
}
}
Windows(C++)
void FlutterOcrSdkPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
const auto *arguments = std::get_if<EncodableMap>(method_call.arguments());
if (method_call.method_name().compare("init") == 0)
{
std::string license;
int ret = 0;
if (arguments)
{
auto license_it = arguments->find(EncodableValue("key"));
if (license_it != arguments->end())
{
license = std::get<std::string>(license_it->second);
}
ret = manager->Init(license.c_str());
}
result->Success(EncodableValue(ret));
}
else if (method_call.method_name().compare("loadModel") == 0)
{
std::string path, params;
int ret = 0;
if (arguments)
{
auto params_it = arguments->find(EncodableValue("template"));
if (params_it != arguments->end())
{
params = std::get<std::string>(params_it->second);
}
ret = manager->LoadModel(params);
}
result->Success(EncodableValue(ret));
}
else if (method_call.method_name().compare("recognizeFile") == 0)
{
std::string filename;
EncodableList results;
if (arguments)
{
auto filename_it = arguments->find(EncodableValue("filename"));
if (filename_it != arguments->end())
{
filename = std::get<std::string>(filename_it->second);
}
manager->RecognizeFile(result, filename.c_str());
}
}
else if (method_call.method_name().compare("recognizeBuffer") == 0)
{
EncodableList results;
std::vector<unsigned char> bytes;
int width = 0, height = 0, stride = 0, format = 0, rotation = 0;
if (arguments)
{
auto bytes_it = arguments->find(EncodableValue("bytes"));
if (bytes_it != arguments->end())
{
bytes = std::get<vector<unsigned char>>(bytes_it->second);
}
auto width_it = arguments->find(EncodableValue("width"));
if (width_it != arguments->end())
{
width = std::get<int>(width_it->second);
}
auto height_it = arguments->find(EncodableValue("height"));
if (height_it != arguments->end())
{
height = std::get<int>(height_it->second);
}
auto stride_it = arguments->find(EncodableValue("stride"));
if (stride_it != arguments->end())
{
stride = std::get<int>(stride_it->second);
}
auto format_it = arguments->find(EncodableValue("format"));
if (format_it != arguments->end())
{
format = std::get<int>(format_it->second);
}
auto rotation_it = arguments->find(EncodableValue("rotation"));
if (rotation_it != arguments->end())
{
rotation = std::get<int>(rotation_it->second);
}
manager->RecognizeBuffer(result, reinterpret_cast<unsigned char *>(bytes.data()), width, height, stride, format, rotation);
}
}
else
{
result->NotImplemented();
}
}
Linux (C++)
static void flutter_ocr_sdk_plugin_handle_method_call(
FlutterOcrSdkPlugin *self,
FlMethodCall *method_call)
{
bool isAsync = false;
g_autoptr(FlMethodResponse) response = nullptr;
const gchar *method = fl_method_call_get_name(method_call);
FlValue *args = fl_method_call_get_args(method_call);
if (strcmp(method, "init") == 0)
{
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
{
return;
}
FlValue *value = fl_value_lookup_string(args, "key");
if (value == nullptr)
{
return;
}
const char *license = fl_value_get_string(value);
int ret = self->manager->Init(license);
g_autoptr(FlValue) result = fl_value_new_int(ret);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
else if (strcmp(method, "loadModel") == 0)
{
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
{
return;
}
FlValue *value = fl_value_lookup_string(args, "template");
if (value == nullptr)
{
return;
}
const char *params = fl_value_get_string(value);
std::string str(params);
int ret = self->manager->LoadModel(str);
g_autoptr(FlValue) result = fl_value_new_int(ret);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
else if (strcmp(method, "recognizeFile") == 0)
{
isAsync = true;
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
{
return;
}
FlValue *value = fl_value_lookup_string(args, "filename");
if (value == nullptr)
{
return;
}
const char *filename = fl_value_get_string(value);
self->manager->RecognizeFile(method_call, filename);
}
else if (strcmp(method, "recognizeBuffer") == 0)
{
isAsync = true;
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
{
return;
}
FlValue *value = fl_value_lookup_string(args, "bytes");
if (value == nullptr)
{
return;
}
unsigned char *bytes = (unsigned char *)fl_value_get_uint8_list(value);
value = fl_value_lookup_string(args, "width");
if (value == nullptr)
{
return;
}
int width = fl_value_get_int(value);
value = fl_value_lookup_string(args, "height");
if (value == nullptr)
{
return;
}
int height = fl_value_get_int(value);
value = fl_value_lookup_string(args, "stride");
if (value == nullptr)
{
return;
}
int stride = fl_value_get_int(value);
value = fl_value_lookup_string(args, "format");
if (value == nullptr)
{
return;
}
int format = fl_value_get_int(value);
value = fl_value_lookup_string(args, "rotation");
if (value == nullptr)
{
return;
}
int rotation = fl_value_get_int(value);
self->manager->RecognizeBuffer(method_call, bytes, width, height, stride, format, stride * height, rotation);
}
else
{
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
if (!isAsync)
{
fl_method_call_respond(method_call, response, nullptr);
}
}
Full-Stack Application Example
-
Add dependencies to
pubspec.yaml
:dependencies: ... cupertino_icons: ^1.0.2 image_picker: ^1.0.0 shared_preferences: ^2.1.1 camera: path: ./camera/ camera_windows: path: ./windows_camera/ share_plus: ^10.0.2 url_launcher: ^6.1.11 flutter_exif_rotation: ^0.5.1 flutter_lite_camera:
-
Create a stateful widget:
import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:io'; import 'package:flutter_ocr_sdk/flutter_ocr_sdk.dart'; import 'package:flutter_ocr_sdk/flutter_ocr_sdk_platform_interface.dart'; import 'package:flutter_ocr_sdk/mrz_line.dart'; import 'package:flutter_ocr_sdk/mrz_parser.dart'; import 'package:image_picker/image_picker.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'dart:ui' as ui; Future<void> main() async { runApp( MaterialApp( title: 'MRZ OCR', home: Scaffold( appBar: AppBar( title: const Text("MRZ OCR"), ), body: MRZApp(), ), ), ); } class MRZApp extends StatefulWidget { @override MobileState createState() => MobileState(); }
-
Initialize the MRZ scanner plugin with a valid license key:
class MobileState extends State<MRZApp> { late FlutterOcrSdk _mrzDetector; final picker = ImagePicker(); @override void initState() { super.initState(); initSDK(); } Future<int> initSDK() async { int? ret = await detector.init( "LICENSE-KEY"); if (ret == 0) isLicenseValid = true; return await detector.loadModel(modelType: model) ?? -1; } }
-
Build the UI:
Widget build(BuildContext context) { double width = MediaQuery.of(context).size.width; double height = MediaQuery.of(context).size.height; double left = 5; double mrzHeight = 50; double mrzWidth = width - left * 2; return Scaffold( body: Stack(children: [ Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ MaterialButton( textColor: Colors.white, color: Colors.blue, onPressed: () async { showDialog( context: context, builder: (BuildContext context) { return const Center( child: CircularProgressIndicator(), ); }); pictureScan('gallery'); }, child: const Text('Pick gallery image'), ), MaterialButton( textColor: Colors.white, color: Colors.blue, onPressed: () async { showDialog( context: context, builder: (BuildContext context) { return const Center( child: CircularProgressIndicator(), ); }); pictureScan('camera'); }, child: const Text('Pick camera image'), ), ]), ) ]), ); }
-
Press the button to load an image file from the local file system and then recognize MRZ from the image:
void pictureScan(String source) async { XFile? photo; if (kIsWeb || Platform.isAndroid || Platform.isIOS) { if (source == 'camera') { photo = await picker.pickImage(source: ImageSource.camera); } else { photo = await picker.pickImage(source: ImageSource.gallery); } } else if (Platform.isWindows || Platform.isLinux) { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: <String>['jpg', 'png', 'bmp', 'tiff', 'pdf', 'gif'], ); photo = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]); } if (photo == null) { if (!mounted) return; Navigator.pop(context); return; } String information = 'No results'; List<List<MrzLine>>? results = await _mrzDetector.recognizeByFile(photo.path); if (results != null && results.isNotEmpty) { for (List<MrzLine> area in results) { if (area.length == 2) { information = MRZ.parseTwoLines(area[0].text, area[1].text).toString(); } else if (area.length == 3) { information = MRZ .parseThreeLines(area[0].text, area[1].text, area[2].text) .toString(); } } } if (!mounted) return; Navigator.pop(context); Navigator.push( context, MaterialPageRoute( builder: (context) => DisplayPictureScreen( imagePath: photo!.path, mrzInformation: information), ), ); }
-
Display the MRZ information on a stateless widget:
Image getImage(String imagePath) { if (kIsWeb) { return Image.network(imagePath); } else { return Image.file( File(imagePath), fit: BoxFit.contain, height: double.infinity, width: double.infinity, alignment: Alignment.center, ); } } class DisplayPictureScreen extends StatelessWidget { final String imagePath; final String mrzInformation; const DisplayPictureScreen( {Key? key, required this.imagePath, required this.mrzInformation}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('MRZ OCR')), body: Stack( alignment: const Alignment(0.0, 0.0), children: [ getImage(imagePath), Container( decoration: const BoxDecoration( color: Colors.black45, ), child: Text( mrzInformation, style: const TextStyle( fontSize: 14, color: Colors.white, ), ), ), ], ), ); } }
-
Run the app on web, Windows, Linux, Android and iOS platforms.
Web
Windows and Linux
Android and iOS