88A from-scratch Rust library for ** decoding and encoding 1D and 2D barcodes** , aiming
99for the widest possible symbology coverage while preserving every variation of a code.
1010
11- ** ▶ [ Live WASM demo] ( https://karpeleslab.github.io/anydcode/ ) ** — encode to a canvas,
12- decode an image, and run live-camera detection, all in the browser (the library
13- compiled to WebAssembly, no wasm-bindgen).
11+ ** ▶ [ Live WASM demo] ( https://karpeleslab.github.io/anydcode/ ) ** — generate any
12+ symbology (Apple App Clip Codes included), decode an image, and run live-camera
13+ scanning with a tracked target-lock overlay, all in the browser (the library compiled
14+ to WebAssembly, no wasm-bindgen).
1415
1516## Goals
1617
@@ -79,10 +80,16 @@ Legend: ✅ done · 🚧 in progress · ⬜ planned.
7980| Royal Mail Mailmark (Barcode C & L) | ✅ | ✅ | ⬜ |
8081| Australia Post 4-State · Japan Post 4-State | ✅ | ✅ | ⬜ |
8182
82- ¹ QR ships a full image sampler: Otsu binarization → finder-pattern detection →
83- perspective recovery (via the bottom-right alignment pattern) → sub-pixel grid
84- sampling → decode. Robust to any-angle rotation, moderate tilt, blur and noise
85- (envelope documented in ` tests/harness_image.rs ` ).
83+ ¹ QR ships a hardened image sampler: a global-Otsu → adaptive (Bradley/Sauvola)
84+ binarization ladder, dual-axis finder detection with a synthesized-third-corner
85+ fallback, perspective recovery via the alignment pattern, and — for curved or
86+ degraded captures — a thin-plate-spline dewarp anchored on every finder, alignment
87+ pattern and timing-pattern crossing, biased module classification, and a
88+ Reed–Solomon-arbitrated brute force over all 32 format configurations when blur
89+ wrecks the format fields. Robust to any-angle rotation, tilt, blur, noise, uneven
90+ lighting and cylinder curvature; clean-render envelope in ` tests/harness_image.rs ` ,
91+ real-capture envelope (bottle and can labels down to ≈4.6 px/module) in
92+ ` tests/real_world.rs ` .
8693
8794² 1D detection is the shared ** ` scan1d ` ** luminance front-end (edge detection →
8895run-length → normalized ` LinearPattern ` ), feeding each symbology's decoder. The
@@ -143,7 +150,12 @@ assert_eq!(encoder.encode(&decoded).unwrap(), encoding);
143150For a camera feed you rarely want to fully decode every frame. The ` detect ` module
144151provides a fast, symbology-agnostic ** locator** that returns the * position* of every
145152code (a bounding quad + coarse family guess) in a single downscaled pass, ** without
146- decoding** — the cheap stage of the two-stage ` Detect ` → ` Analyze ` pipeline:
153+ decoding** — the cheap stage of the two-stage ` Detect ` → ` Analyze ` pipeline. It is
154+ recall-biased but structurally picky: concentric-finder detection backs matrix
155+ guesses, scene-sized merged blobs, text rows/columns masquerading as barcodes and
156+ non-code ink densities are gated out, overlapping duplicates of one physical code are
157+ suppressed, and a close-up code that defeats the texture pass is still reported from
158+ its finder hits alone:
147159
148160``` rust
149161use anyd :: detect :: {locate, LocateOptions };
@@ -158,17 +170,24 @@ for c in &candidates {
158170Benchmark (` cargo bench --bench detect ` , dependency-free, ` std::time::Instant ` ),
159171locating ** all** codes of all families per frame vs. a full QR decode:
160172
161- | resolution | locate median | locate FPS | full-decode FPS | detect speedup |
162- | -----------:| --------------:| -----------:| ---------------- :| ---------------:|
163- | 640×480 | 0.30 ms | ~ 3350 | ~ 1725 | 1.9 × |
164- | 1280×720 | 0.83 ms | ~ 1200 | ~ 635 | 1.9 × |
165- | 1920×1080 | 1.85 ms | ~ 545 | ~ 310 | 1.8 × |
173+ | resolution | locate median | locate FPS | full QR decode | detect speedup |
174+ | -----------:| --------------:| -----------:| ---------------:| ---------------:|
175+ | 640×480 | 0.29 ms | ~ 3400 | 12.1 ms | ~ 41 × |
176+ | 1280×720 | 0.70 ms | ~ 1430 | 14.1 ms | ~ 20 × |
177+ | 1920×1080 | 1.53 ms | ~ 650 | 17.4 ms | ~ 11 × |
166178
167179Recall 100% and false-positive rate 0% on the benchmark's planted-code frames.
168180Locating is comfortably real-time at every resolution; decoding then runs only on the
169181regions the locator hands off (and ` Hints ` lets already-decoded codes be skipped
170182across frames).
171183
184+ The demo page shows the intended live wiring: the main thread runs ` locate ` on a
185+ half-resolution grab ~ 30×/s, a crop worker decodes located regions off-thread —
186+ routed by family, so linear crops take the fast 1D path (` scan_1d ` is
187+ rotation-complete via mirrored patterns and gradient-orientation derotation) — a
188+ second worker sweeps the full frame for self-localizing 2D codes, and decoded locks
189+ are position-tracked across frames so the overlay follows the code between reads.
190+
172191## Command-line tool (` anyd ` )
173192
174193An optional binary is bundled behind the ** ` cli ` ** feature, so the library itself
@@ -184,7 +203,8 @@ $ anyd encode qr "https://example.com" --format png --out qr.png --scale 8
184203$ anyd encode ean13 5901234123457 --format svg --out barcode.svg
185204$ anyd encode qr " HELLO" --format unicode # half-block art in the terminal
186205
187- # Decode a PNG (tries QR, Data Matrix, PDF417, and the 1D front-end)
206+ # Decode a PNG (tries QR, Data Matrix, PDF417, App Clip Code, and the
207+ # rotation-complete 1D front-end)
188208$ anyd decode qr.png
189209QR Code: https://example.com
190210
@@ -212,9 +232,10 @@ Live video: GrayFrame ──▶ Detect ──▶ Candidate ──▶ Analyze
212232```
213233
214234Supporting modules: ** ` imgproc ` ** (Otsu/adaptive binarization, connected components,
215- 4-point homography, sub-pixel grid sampling — the 2D sampler toolkit), ** ` scan1d ` **
216- (generic 1D luminance front-end), ** ` render ` ** (Encoding → grayscale) and
217- ** ` transform ` ** (rotation/perspective/blur/noise) for the test harness.
235+ 4-point homography, thin-plate-spline warps, dominant-orientation estimation,
236+ sub-pixel grid sampling — the 2D sampler toolkit), ** ` scan1d ` ** (generic 1D luminance
237+ front-end), ** ` render ` ** (Encoding → grayscale) and ** ` transform ` **
238+ (rotation/perspective/blur/noise) for the test harness.
218239
219240Correctness is anchored to independent reference vectors, not self-consistency: the
220241ISO/IEC 18004 QR example, the ISO/IEC 16022 Data Matrix example, ISO/IEC 15438 +
0 commit comments