@platox/expo-document-scanner
v0.1.2
Published
Expo document scanner module with camera view, document detection and image processing
Maintainers
Readme
expo-document-scanner
Expo native module for document scanning with real-time detection, auto-capture, manual crop, and image enhancement. Supports iOS and Android.
Features
- Real-time document edge detection with camera preview
- Auto-capture when document is stable and sharp (multi-dimension quality scoring)
- Manual mode with draggable corner handles for precise cropping
- Fullscreen crop view with safe area support
- Standalone
cropImageAPI — crop any image without the camera - Perspective correction and image enhancement (white balance, contrast, sharpening)
- iOS system document scanner (
VNDocumentCameraViewController) support - Kalman filter corner smoothing for stable overlay
Installation
npm install expo-document-scanneriOS
npx pod-installAndroid
No additional configuration needed.
Usage
1. Camera Scanner View
import { DocumentScannerView } from 'expo-document-scanner';
function Scanner() {
return (
<DocumentScannerView
style={{ flex: 1 }}
autoCapture={true}
enableImageEnhancement={true}
overlayStyle="quadrilateral"
captureQualityThreshold={75}
onCapture={(e) => {
const { uri, width, height } = e.nativeEvent;
console.log('Captured:', uri);
}}
onDocumentDetected={(e) => {
const { detected, qualityScore } = e.nativeEvent;
}}
onError={(e) => {
console.error(e.nativeEvent.code, e.nativeEvent.message);
}}
/>
);
}2. Manual Mode
<DocumentScannerView
style={{ flex: 1 }}
manualMode={true}
onCapture={(e) => {
// Photo is captured after user adjusts corners and confirms
// ManualCropView covers the entire screen (fullscreen)
console.log('Cropped:', e.nativeEvent.uri);
}}
/>3. Standalone Crop API
Crop any existing image without the camera. Opens a fullscreen manual crop view with auto-detected document edges.
import { ExpoDocumentScanner } from 'expo-document-scanner';
const result = await ExpoDocumentScanner.cropImage('file:///path/to/photo.jpg', {
enableImageEnhancement: true,
// initialCorners: [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 }],
});
if (!result.cancelled) {
console.log(result.uri); // file:///tmp/cropped_xxx.jpg
console.log(result.base64Uri); // data:image/jpeg;base64,...
console.log(result.width, result.height);
console.log(result.corners); // actual corners used
}Supported URI formats: file://, data: (base64), content:// (Android).
4. iOS System Document Scanner
import { ExpoDocumentScanner } from 'expo-document-scanner';
if (ExpoDocumentScanner.isDocumentScannerAvailable()) {
const result = await ExpoDocumentScanner.openDocumentScanner();
if (!result.cancelled) {
result.pages.forEach((page) => {
console.log(page.uri, page.width, page.height);
});
}
}5. Camera Permissions
Use the cross-platform helpers exported from the package. They handle platform differences internally — PermissionsAndroid on Android, AVCaptureDevice on iOS.
import { requestCameraPermission, getCameraPermissionStatus } from 'expo-document-scanner';
const status = getCameraPermissionStatus();
// 'authorized' | 'denied' | 'restricted' | 'notDetermined' | 'unknown'
if (status !== 'authorized') {
const granted = await requestCameraPermission();
}Permission flow
Enter camera screen
|
v
getCameraPermissionStatus() (sync, native on both platforms)
|
|-- "authorized" --> Render camera
|
|-- "notDetermined" --> requestCameraPermission()
| |
| +--------+--------+
| Android iOS
| PermissionsAndroid AVCaptureDevice
| .request() .requestAccess()
| | |
| +-- OS dialog ---+
| |
| +------+------+
| Allow Deny
| | |
| Render camera Show fallback UI
|
|-- "denied" --> requestCameraPermission()
| (Android: OS dialog can appear again)
|
+-- "restricted" --> Show fallback UI + "Open Settings" button
(permanently denied, user must enable manually)Status values
| Status | iOS | Android |
|--------|-----|---------|
| notDetermined | Never asked | Never asked |
| authorized | User allowed | User allowed |
| denied | User denied | User denied (can ask again) |
| restricted | Restricted by system (e.g. parental controls) | Denied with "Don't ask again" |
| unknown | Unable to determine | Unable to determine |
API Reference
DocumentScannerView Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| autoCapture | boolean | true | Auto-capture when document is stable and sharp |
| sharpnessThreshold | number | 100 | Minimum sharpness for auto-capture (50-200) |
| stabilityFramesRequired | number | 5 | Consecutive stable frames before auto-capture |
| minDocumentArea | number | 0.1 | Minimum document area ratio (0-1) |
| enableFlash | boolean | false | Enable flash |
| showDetectionOverlay | boolean | true | Show detection overlay |
| enableImageEnhancement | boolean | true | Apply enhancement after capture |
| overlayStyle | 'quadrilateral' \| 'rectangle' | 'quadrilateral' | Overlay shape style |
| captureQualityThreshold | number | 75 | Quality score threshold (0-100) for auto-capture |
| manualMode | boolean | false | Enable manual capture + corner adjustment |
DocumentScannerView Events
| Event | Payload | Description |
|-------|---------|-------------|
| onDocumentDetected | DocumentDetectedEventPayload | Document detected/lost in frame |
| onCapture | CaptureEventPayload | Photo captured (auto or manual) |
| onError | ErrorEventPayload | Error occurred |
| onStatusChange | StatusChangeEventPayload | Scanner status changed |
Module Functions
| Function | Returns | Description |
|----------|---------|-------------|
| isCameraAvailable() | boolean | Check camera availability |
| requestCameraPermission() | Promise<boolean> | Request camera permission |
| getCameraPermissionStatus() | CameraPermissionStatus | Get current permission status |
| isDocumentScannerAvailable() | boolean | Check system scanner availability (iOS only) |
| openDocumentScanner() | Promise<DocumentScannerResult> | Open iOS system document scanner |
| cropImage(uri, options?) | Promise<CropImageResult> | Open fullscreen crop view for an existing image |
CropImageOptions
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| enableImageEnhancement | boolean | true | Apply enhancement after cropping |
| initialCorners | Point[] | auto-detect | Initial crop corners (normalized 0-1). Auto-detects document edges if omitted. |
CropImageResult
| Field | Type | Description |
|-------|------|-------------|
| uri | string | Cropped image file:// URI |
| base64Uri | string | Cropped image as data:image/jpeg;base64,... |
| width | number | Image width in pixels |
| height | number | Image height in pixels |
| corners | Point[] | Actual crop corners used |
| cancelled | boolean | true if user cancelled |
Platform Support
| Feature | iOS | Android | Web |
|---------|-----|---------|-----|
| Camera scanner view | Yes | Yes | No |
| Auto-capture | Yes (ML-based detection) | Yes (edge detection) | No |
| Manual crop | Yes (fullscreen) | Yes (fullscreen) | No |
| cropImage API | Yes (with auto-detect) | Yes | No |
| System document scanner | Yes (VNDocumentCameraViewController) | No | No |
| Image enhancement | Yes (Core Image) | Yes (ColorMatrix) | No |
Architecture
ios/
ExpoDocumentScannerModule.swift # Module: functions + cropImage API
ExpoDocumentScannerView.swift # Camera view with detection + auto-capture
ManualCropView.swift # Fullscreen corner adjustment UI
DocumentImageProcessor.swift # Shared: perspective correction, enhancement, detection
android/.../
ExpoDocumentScannerModule.kt # Module: functions + cropImage API
ExpoDocumentScannerView.kt # Camera view with detection + auto-capture
ManualCropView.kt # Fullscreen corner adjustment UI
DocumentImageProcessor.kt # Shared: perspective correction, enhancement, loading
src/
index.ts # Public exports
ExpoDocumentScannerModule.ts # Native module type declarations
ExpoDocumentScannerModule.web.ts # Web stubs
ExpoDocumentScanner.types.ts # TypeScript type definitions
ExpoDocumentScannerView.tsx # React component wrapperLicense
MIT
