passport_scanner 1.1.0
passport_scanner: ^1.1.0 copied to clipboard
Scans passports with the device camera or from a gallery photo and extracts the MRZ (name, document number, nationality, dates) on Android and iOS.
Passport Scanner #
Easily scan passports to extract their information from the MRZ code. This package reads the MRZ (Machine Readable Zone) and parses it to extract the information, either live from the device camera or from a still image picked from the gallery.
Setup #
Since this package is using ML Kit for text recognition, you must satisfy its requirements:
iOS #
- Minimum iOS Deployment Target: 15.5
- Xcode 15.3.0 or newer
- Swift 5
- ML Kit does not support 32-bit architectures (i386 and armv7). ML Kit does support 64-bit architectures (x86_64 and arm64). Check this list to see if your device has the required device capabilities. More info here.
Your Podfile should look like this:
platform :ios, '15.5' # or newer version
...
# add this line:
$iOSVersion = '15.5' # or newer version
post_install do |installer|
# add these lines:
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7"
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# add these lines:
target.build_configurations.each do |config|
if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
end
end
end
end
Notice that the minimum IPHONEOS_DEPLOYMENT_TARGET is 15.5, you can set it to something newer but not older.
Android #
- minSdkVersion: 21
- targetSdkVersion: 35
- compileSdkVersion: 35
Camera and photo permissions #
There is no need to request permissions yourself; the camera and the photo picker each ask for what they need.
On iOS, add the usage descriptions to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>The camera is used to scan the machine-readable zone of a passport.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photos are read so a passport you already photographed can be scanned.</string>
The photo entry is only needed if you use the gallery scan.
Usage #
Scanning with the camera #
Add the PassportScannerWidget to your scaffold and pass an onScanned
listener to get the result data. onScanned receives the parsed MRZResult
and the path of a JPEG of the scanned passport page, or null if saving the
image failed:
Scaffold(
appBar: AppBar(title: Text('Passport Scanner')),
body: PassportScannerWidget(
precision: 2, // identical validated reads required before success
showFlashButton: true,
onScanned: (result, imagePath) {
print('Scanned: ${result.documentNumber}, ${result.givenNames} ${result.surnames}');
print('Passport image saved at: $imagePath');
},
onParsingFailed: (lines) => print('Could not parse: $lines'),
onNoMrzFound: () => print('Aim the MRZ inside the frame'),
),
)
| Parameter | Type | Default | Description |
|---|---|---|---|
onScanned |
void Function(MRZResult, String? imagePath) |
required | Called once when the same MRZ has been read precision times. imagePath is a JPEG of the passport page alone — its edges detected around the MRZ and warped upright to the ID-3 aspect ratio — in the temporary directory. Falls back to the scan area as framed when the edges cannot be found. |
onParsingFailed |
void Function(List<String> lines)? |
null |
Called when MRZ-shaped lines were found but failed check-digit validation, with the lines as fed to the parser. Throttled to once every 2 seconds. |
onNoMrzFound |
void Function()? |
null |
Called when an analyzed frame contains no MRZ-shaped lines. Throttled to once every 2 seconds. |
precision |
int |
2 |
Number of identical, validated reads required before onScanned fires. Must be at least 1; 1 accepts the first validated read. |
showFlashButton |
bool |
false |
Whether to show a torch toggle in the top-left corner of the preview. |
onScanned fires only once. To scan another passport, rebuild the widget
(for example, push a new scanner screen).
The result is an MRZResult object, and it includes this information:
documentType
countryCode
surnames
givenNames
documentNumber
nationalityCountryCode
birthDate
sex
expiryDate
personalNumber
personalNumber2
Scanning an image from the gallery #
scanPassportFromGallery() opens the system photo picker and reads the MRZ from
whatever the user chooses — no widget, no camera:
final scan = await scanPassportFromGallery();
if (scan.isSuccess) {
final MRZResult result = scan.result!;
print('Scanned: ${result.documentNumber}, ${result.givenNames} ${result.surnames}');
print('Passport page: ${scan.imagePath}'); // the page alone, cut out of the photo
print('Original photo: ${scan.sourcePath}');
} else {
switch (scan.failure!) {
case PassportScanFailure.cancelled: // the picker was dismissed
case PassportScanFailure.unreadableImage: // missing or undecodable file
case PassportScanFailure.noMrzFound: // no MRZ-shaped lines in the image
case PassportScanFailure.invalidMrz: // lines found, check digits failed
}
}
Every returned result is check-digit validated, exactly like a camera scan. On
success imagePath is a JPEG of the passport page alone, cut out of the photo
around the MRZ and warped upright, just like a camera capture; sourcePath is
the photo as picked. If the page's edges cannot be found, imagePath is the
source photo. The image is read as stored first, then retried at 90°, 270° and 180°, so a photo
that was taken sideways or upside down still scans; pass tryRotations: false
to skip those retries. On failure, scan.mrzLines holds the lines as they were
fed to the parser, which is useful for diagnostics.
To scan an image you already have on disk, use scanPassportImage(path). When
scanning several images in a row, create one PassportImageScanner, call
scanFile / scanFromGallery on it and dispose() it when done — that reuses
a single ML Kit recognizer instead of creating one per image:
final scanner = PassportImageScanner();
for (final path in paths) {
final scan = await scanner.scanFile(path);
...
}
await scanner.dispose();
Support Us #
This package was created inside OpenCode. You can support us by liking it on Pub, starring it on GitHub, sharing ideas on how we could enhance a certain functionality, or reporting issues and creating pull requests.