Super QR Code Scanner
A robust Flutter FFI plugin for scanning QR codes from images using OpenCV and ZXing.
Zero setup required! Just add to your pubspec.yaml and start scanning.
Features
- ✅ Multi-platform support: Android, iOS, macOS, Linux, Windows
- ✅ Multiple QR codes: Detect up to 50 QR codes in a single image
- ✅ High accuracy: 6 detection strategies with image preprocessing
- ✅ Non-blocking UI: Async API with isolate-based processing
- ✅ Native threading: C++ worker threads for parallel execution
- ✅ Type-safe: Strong type checking with custom exception hierarchy
- ✅ Configurable: Adjust scanning parameters for speed vs accuracy
- ✅ Logging: Built-in debug logging for troubleshooting
- ✅ Validated input: Automatic validation of image paths and data
- ✅ Memory-safe: Proper native memory management
- ✅ Zero setup: All dependencies bundled, no external configuration needed
Supported Platforms
| Platform | Minimum Version | Status | Architecture |
|---|---|---|---|
| Android | API 21 (Android 5.0) | ✅ Tested | arm64-v8a, armeabi-v7a |
| iOS | iOS 12.0+ | ✅ Tested | arm64, x86_64 (simulator) |
| macOS | macOS 10.14+ | ✅ Tested | arm64 (Apple Silicon), x86_64 (Intel) |
| Linux | Ubuntu 18.04+ | ⚠️ Experimental | x64 |
| Windows | Windows 10+ | ⚠️ Experimental | x64 |
Notes:
- Android and iOS are fully tested and production-ready
- Desktop platforms (macOS/Linux/Windows) are functional but require more testing
- All platforms use the same native libraries and API
Installation
Add to your pubspec.yaml:
dependencies:
super_qr_code_scanner: ^1.0.0
image_picker: ^1.0.7 # Optional: For picking images
Run:
flutter pub get
Platform Setup
To reduce package size, native libraries are not bundled. Run the setup command to download libraries for your target platforms:
# For Android and iOS (default - recommended for most apps)
dart run super_qr_code_scanner:setup
# For specific platforms
dart run super_qr_code_scanner:setup --platforms android,ios
# For all platforms
dart run super_qr_code_scanner:setup --platforms android,ios,macos,linux,windows
This downloads OpenCV and ZXing libraries from our public GitHub releases and places them in the plugin's native directories. The setup is safe to run multiple times - it skips downloads if libraries are already present.
Note: Libraries are hosted at Panha-Devs/super_qr_code_scanner_artifacts.
Quick Start
Basic Usage
import 'package:super_qr_code_scanner/super_qr_code_scanner.dart';
// Initialize scanner (singleton - done once)
final scanner = SuperQRCodeScanner();
// Scan from file path (async - runs in isolate)
Future<void> scanImage() async {
try {
final results = await scanner.scanImageFile('/path/to/image.jpg');
for (final qr in results) {
print('Format: ${qr.format}');
print('Content: ${qr.content}');
}
if (results.isEmpty) {
print('No QR codes found');
}
} on InvalidParameterException catch (e) {
print('Invalid input: ${e.message}');
} on ImageProcessingException catch (e) {
print('Failed to process: ${e.message}');
}
}
Scan from Raw Bytes
import 'dart:typed_data';
Future<void> scanFromBytes(Uint8List imageData, int width, int height) async {
final scanner = SuperQRCodeScanner();
// Scan RGB image (3 channels) - runs in isolate
final results = await scanner.scanImageBytes(
imageData,
width,
height,
3, // channels: 1=grayscale, 3=RGB, 4=RGBA
);
print('Found ${results.length} QR codes');
}
Complete Example with Image Picker
import 'package:flutter/material.dart';
import 'package:super_qr_code_scanner/super_qr_code_scanner.dart';
import 'package:image_picker/image_picker.dart';
class QRScannerDemo extends StatefulWidget {
@override
State<QRScannerDemo> createState() => _QRScannerDemoState();
}
class _QRScannerDemoState extends State<QRScannerDemo> {
final scanner = SuperQRCodeScanner();
final picker = ImagePicker();
List<QRCode> results = [];
bool isScanning = false;
@override
void initState() {
super.initState();
// Optional: Configure scanner for accuracy
scanner.updateConfig(QRScannerConfig.accurateConfig);
}
Future<void> pickAndScan() async {
try {
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
);
if (image == null) return;
setState(() => isScanning = true);
// Scan in isolate - UI remains responsive
final qrCodes = await scanner.scanImageFile(image.path);
setState(() {
results = qrCodes;
isScanning = false;
});
} catch (e) {
setState(() => isScanning = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('QR Scanner')),
body: Column(
children: [
ElevatedButton(
onPressed: isScanning ? null : pickAndScan,
child: Text(isScanning ? 'Scanning...' : 'Pick Image'),
),
if (results.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: results.length,
itemBuilder: (context, index) {
final qr = results[index];
return Card(
child: ListTile(
title: Text(qr.content),
subtitle: Text('Format: ${qr.format}'),
),
);
},
),
)
else
Padding(
padding: EdgeInsets.all(16),
child: Text('No QR codes found'),
),
],
),
);
}
}
Configuration
Predefined Configs
// Fast scanning (fewer strategies, good for real-time)
scanner.updateConfig(QRScannerConfig.fastConfig);
// Balanced (default, recommended for most use cases)
scanner.updateConfig(QRScannerConfig.defaultConfig);
// Accurate (all strategies, thorough but slower)
scanner.updateConfig(QRScannerConfig.accurateConfig);
Custom Configuration
scanner.updateConfig(QRScannerConfig(
tryHarder: true, // More thorough scanning
tryRotate: true, // Try rotating image
tryDownscale: true, // Try downscaling
maxNumberOfSymbols: 50, // Max QR codes to detect
enableLogging: true, // Enable debug logs
));
Debug Logging
// Enable logging in main()
void main() {
QRScannerLogger.setEnabled(true);
QRScannerLogger.setLevel(LogLevel.debug); // debug, info, warning, error
runApp(MyApp());
}
// Or via config
scanner.updateConfig(
QRScannerConfig(enableLogging: true)
);
Exception Handling
The plugin provides specific exception types:
| Exception | Description |
|---|---|
QRScannerException |
Base exception class |
LibraryLoadException |
Failed to load native library |
ImageProcessingException |
Failed to process image |
InvalidParameterException |
Invalid input parameters |
try {
final results = scanner.scanImageFile(imagePath);
// Process results
} on LibraryLoadException catch (e) {
// Native library failed to load
print('Library error: ${e.message}');
print('Details: ${e.details}');
} on InvalidParameterException catch (e) {
// Invalid image path, format, or parameters
print('Invalid input: ${e.message}');
} on ImageProcessingException catch (e) {
// Image processing failed
print('Processing error: ${e.message}');
} on QRScannerException catch (e) {
// Generic scanner error
print('Scanner error: ${e.message}');
}
Models
QRCode
class QRCode {
final String content; // The decoded QR code content
final String format; // Format type (e.g., "QRCode")
final QRCodePosition? position; // Optional position data
}
QRCodePosition
class QRCodePosition {
final int x;
final int y;
final int width;
final int height;
}Supported Image Formats
The plugin automatically validates input:
- **File formats**: JPG, JPEG, PNG, BMP, WEBP, TIFF, TIF
- **Max file size**: 50MB
- **Max dimensions**: 10000x10000 pixels
- **Color modes**: Grayscale (1 channel), RGB (3 channels), RGBA (4 channels)
- **Path validation**: Checks file exists and is readable
- **Data validation**: Verifies raw image data size matches dimensionsg, bmp, webp, tiff, tif
- Image dimensions (max 10000x10000)
- Raw image data size matches dimensions
- Valid channel count (1, 3, or 4)
## API Reference
### Methods
#### `Future<List<QRCode>> scanImageFile(String imagePath)`
Scans QR codes from an image file. Runs in a separate isolate to avoid blocking the UI.
**Returns:** `Future<List<QRCode>>` - Found QR codes (empty if none)
**Throws:** `InvalidParameterException`, `ImageProcessingException`
```dart
final results = await scanner.scanImageFile('/path/to/image.jpg');
Future<List<QRCode>> scanImageBytes(List<int> imageData, int width, int height, int channels)
Scans QR codes from raw image data. Runs in a separate isolate to avoid blocking the UI.
Parameters:
imageData: Raw pixel datawidth: Image width in pixelsheight: Image height in pixelschannels: 1 (grayscale), 3 (RGB), or 4 (RGBA)
Returns: Future<List<QRCode>> - Found QR codes (empty if none)
final results = await scanner.scanImageBytes(data, width, height, 3);
updateConfig(QRScannerConfig config)
Updates scanner configuration.
Models
QRCode
class QRCode {
final String content; // QR code text content
final String format; // Format (e.g., "QR_CODE")
}
Performance Architecture
Multi-Level Threading
The plugin uses a sophisticated threading architecture for optimal performance:
Flutter Layer (Dart):
- Uses
compute()to run FFI calls in separate Dart isolates - Prevents UI thread blocking during image processing
- Each scan runs in its own isolated environment
Native Layer (C++):
- Uses
std::async(std::launch::async)for parallel execution - Worker threads handle image loading and processing
- Multiple detection strategies run concurrently
Result:
- ✅ UI remains responsive even during heavy processing
- ✅ True parallel execution on multi-core devices
- ✅ No manual thread management required
- ✅ Automatic memory cleanup
Why This Matters
// ❌ Old synchronous approach (blocks UI)
final results = scanImageSync(path); // UI freezes!
// ✅ New async approach (smooth UI)
final results = await scanner.scanImageFile(path); // UI responsive!
```Platform Setup
### Android
- **Min SDK**: 21 (Android 5.0)
- **Permissions**: Add to `AndroidManifest.xml` if using camera:
```xml
<uses-permission android:name="android.permission.CAMERA" />
iOS
- Min Version: iOS 12.0
- Permissions: Add to
Info.plistif using camera:<key>NSCameraUsageDescription</key> <string>We need camera access to scan QR codes</string> <key>NSPhotoLibraryUsageDescription</key> <string>We need photo library access to select images</string>
Dependencies (Automatic)
The package is completely self-contained:
Android:
- Op"No QR codes found"
- Ensure image has good quality and lighting
- Try
accurateConfigfor difficult images - Check image isn't too blurry or small
"Invalid image path"
- Use absolute paths
- Verify file exists and is readable
- Check supported formats
Build errors on Android
- Clean build:
flutter clean && flutter pub get - Check NDK is installed in Android Studio
Build errors on iOS
- Run
cd ios && pod install - Clean Xcode build folder
Library not found error
// Enable logging to see detailed error messages
QRScannerLogger.setEnabled(true);
QRScannerLogger.setLevel(LogLevel.debug);
```t
4. **Adaptive threshold**: Otsu and adaptive thresholding
5. **Inverted**: Scan inverted grayscale image
6. **Sharpened**: Apply sharpening kernel
## Requirements
### No setup required!
The package is completely self-contained with custom-compiled native libraries:
**Android:**
- Min SDK: 21 (Android 5.0)
- OpenCV 4.14.0-pre: Custom compiled, bundled as native libraries
- ZXing-C++ 2.3.0: Custom compiled, bundled as static libraries
- NDK: Automatically managed by Flutter
**iOS:**
- iOS 12.0+
- OpenCV 4.14.0-pre: Custom compiled, bundled as frameworks
- ZXing-C++ 2.3.0: Custom compiled, bundled as static libraries
- No CocoaPods dependencies required
**Desktop (macOS, Linux, Windows):**
- OpenCV 4.14.0-pre: Custom compiled, bundled
- ZXing-C++ 2.3.0: Custom compiled, bundled
- All native libraries included
**First build may take 2-5 minutes** as native code is compiled. Subsequent builds are fast.
## Example App
See the [example app](example/) for a complete working implementation with image picker.
## Troubleshooting
### Android build errors with .cxx directory
If you encounter build errors related to CMake or native compilation, try removing the `.cxx` directory:
```bash
cd android
rm -rf .cxx
cd ..
flutter clean
flutter pub get
This directory contains cached CMake build artifacts that may become corrupted.
Library not found error
// Enable logging to see detailed error messages
QRScannerLogger.setEnabled(true);
QRScannerLogger.setLevel(LogLevel.debug);
No QR codes detected
- Ensure image quality is good
- Try
accurateConfigfor difficult images - Check that QR code is clearly visible
- Verify supported format (standard QR codes only)
UI still freezes during scan
- Ensure you're using
awaitwith scan methods - Check that you're not blocking the UI thread elsewhere
- Verify Flutter version supports
compute()(Flutter 1.0+)
Memory issues
- Reduce image size before scanning
- Ensure proper disposal of resources
- Check that file size is within limits (50MB)
- Multiple concurrent scans may increase memory usage
License
See LICENSE file for details.