doc_scan_lite 0.0.2
doc_scan_lite: ^0.0.2 copied to clipboard
Lightweight document scanner plugin: live edge detection and perspective crop via classical CV over FFI. No ML models, no Play Services, no OpenCV.
doc_scan_lite #
A lightweight Flutter FFI plugin for live document edge detection and perspective-crop. Classical computer vision only — no ML models, no Google Play Services / ML Kit dependency — targeting a native binary in the hundreds-of-KB range per ABI.
Usage #
The API is layered — pick the level of control you need.
1. Prebuilt UI (drop-in) #
DocScannerView owns the camera, its lifecycle, the live overlay, the capture
button, an optional manual corner-adjust step, and a result preview. One
callback and you have a working scanner:
DocScannerView(
enableManualAdjust: true, // optional drag-to-fix-corners step before crop
onCaptured: (CapturedDocument doc) {
// doc.bytes = grayscale pixels, doc.width x doc.height (deskewed crop)
},
)
2. Prebuilt, customized #
Every visual piece is overridable while keeping the built-in camera/detection plumbing:
DocScannerView(
onCaptured: saveDoc,
lockedColor: Colors.tealAccent, // overlay colors + stroke
searchingColor: Colors.white54,
showResultPreview: false, // skip the built-in result screen
controlsBuilder: (context, c) => // your own capture bar
MyShutterBar(locked: c.isLocked, busy: c.isCapturing, onTap: c.capture),
resultBuilder: (context, doc, dismiss) => MyReviewPage(doc, onDone: dismiss),
loadingBuilder: (context) => const MySplash(),
errorBuilder: (context, err) => MyCameraError(err),
onError: () => toast('No document found'), // detection/warp failed
onCancelled: () => toast('Cancelled'), // user backed out of adjust
)
CapturedDocument.bytesis raw single-channel grayscale (one byte per pixel,width * heightlong), not PNG/JPEG — the pipeline only ever reads the camera's luma plane. UsedecodeCapturedDocument(doc)to get a displayableui.Image(dispose it when done), or expand to RGB and encode with a package likeimage.
3. Full DIY (bring your own camera + UI) #
Drive detection yourself with the low-level pieces — DocScanController +
DocScannerPreview + QuadCornerEditor — when you already manage the camera
or need a bespoke flow. See example/ history or the source docs; the
controller exposes processFrame, currentQuad, isLocked,
captureAndCropLatest, latestFrame, warpFrame, pause / resume, and
onDetectionError.
For a manual corner-correction flow, snapshot controller.latestFrame, show
it under a QuadCornerEditor, then finalize with
controller.warpFrame(frame, adjustedQuad) — pass the same frame you showed
the user, since the live stream will have moved on by the time they finish
dragging.
How it works #
src/ implements the detection pipeline from scratch in C:
- YUV420 Y-plane extraction (grayscale, no RGB conversion)
- 5x5 Gaussian blur
- Canny edge detection (Sobel + non-max suppression + hysteresis)
- Morphological dilate
- Contour tracing (Moore-neighbor border following)
- Douglas-Peucker polygon simplification
- Quad filtering (4 points, convex, area > 20% of frame)
- Corner ordering (TL/TR/BR/BL)
- Homography solve + perspective warp
Exposed via two C functions (src/doc_scan_lite.h):
bool detect_quad(const uint8_t* frame, int width, int height, Quad* out);
bool warp_perspective(const uint8_t* frame, int width, int height, Quad quad,
uint8_t* out_buffer, int out_width, int out_height);
lib/ wraps these with an isolate-based Dart API so detection never blocks
the UI thread:
DocScanController— owns a persistent background isolate, throttles incoming camera frames to a target detection fps independent of the camera's preview rate, smooths corners with a moving average, and exposescurrentQuad/isLocked.DocScannerView— batteries-included widget that owns the camera and the full scan flow (see Usage above).DocScannerPreview— camera preview widget with a live quad overlay.QuadCornerEditor— manual drag-adjustment widget for correcting a bad auto-detection, pure Dart/Flutter with no native cost.captureAndCrop()re-runs detection on a full-resolution frame and returns the warped/cropped image as aCapturedDocument.
Testing #
test/run_tests.shbuilds and runs the C pipeline unit tests standalone (no Flutter/FFI toolchain involved) — corner ordering, Douglas-Peucker, homography, and a full synthetic detect+warp smoke test.example/demonstrates live camera detection end-to-end. Camera streams on emulators are unreliable for this — test on a real device.
Regenerating FFI bindings #
dart run ffigen --config ffigen.yaml
If ffigen's bundled libclang can't find stdbool.h on Linux, point CPATH
at your system clang's resource include dir first:
CPATH="$(clang -print-resource-dir)/include" dart run ffigen --config ffigen.yaml
Non-goals #
No OCR, no multi-page PDF assembly, no ML models or trained weights, no Google Play Services / ML Kit dependency anywhere in the tree.