Skip to content

Commit c4c43f8

Browse files
MagicalTuxclaude
andcommitted
appclip: camera detection of printed App Clip Codes
Ports the reference raster scanner (rs/appclipcode) to a grayscale GrayFrame pipeline stage, wired into pipeline::scan_2d: - Sobel edge field; strong edges keep their unit normals - ring Hough: edge pixels vote along +-normal at each of the 11 expected ring-edge radii over an outer-radius sweep (on a <=320px copy), peaks hill-climbed at full resolution on an edge-vs-quiet-zone circle score - affine/tilt estimate from the outer-circle edge cloud's second moments, refined by coordinate descent over centre, angle and both axis scales - rotation search (the code has no fiducial): every 1 degree scored by the Otsu separability of per-position boundary contrast, best peaks refined to 0.1 degree - bit recovery: gap/arc from thresholded contrast (Otsu + a sweep of plausible visible counts), 2-means over arc luminance for the foreground/third bit in both polarities, template-byte distance ordering attempts, Reed-Solomon arbitrating every one — first surviving URL wins The port is deliberately color-blind: every Apple palette separates the three colors by >=50/255 luminance, so grayscale sampling loses nothing. One upstream deviation, deliberate: the Go scanner inserts a synthetic separator bit and expects arc colors to start at ring bit 129 — consistent only with its own synthetic-test renderer. Apple's actual generator (and our SVG renderer, byte-verified against Apple fixtures) burns the separator as the first visible arc's color, so the reconstruction here appends sampled colors directly after the 128 gap bits. Covered by generate -> rasterize -> scan round-trips (multiple sizes and rotations, off-centre, gray surround), a scan_2d pipeline test, and a no-circle rejection test; ANYD_APPCLIP_DEBUG dumps candidates and rotation hypotheses. Busy no-circle crops cost ~100ms native / ~240ms wasm extra in scan_all — same order as the QR fallback ladder and bounded by the 4-candidate cap; linear crops (decode1d) are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b306eb8 commit c4c43f8

5 files changed

Lines changed: 1204 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Legend: ✅ done · 🚧 in progress · ⬜ planned.
4444
| Han Xin (versions 1–3, Numeric/Text/Byte) | ✅ | ✅ | ⬜ |
4545
| DotCode (GF(113) RS) | ✅ | ✅ | ⬜ |
4646
| Grid Matrix (all 13 versions) | ✅ | ✅ | ⬜ |
47-
| Apple App Clip Code (URL codec + SVG, camera & NFC variants)⁵ | ✅ | ✅ | ⬜ |
47+
| Apple App Clip Code (URL codec + SVG, camera & NFC variants)⁵ | ✅ | ✅ | ✅ |
4848

4949
### 2D — stacked
5050

@@ -108,7 +108,10 @@ in `tests/pdf417_image.rs`.
108108
from-scratch implementation following the public reverse engineering of Apple's
109109
generator ([rs/appclipcode], MIT), byte-verified against `URLCompression.framework`
110110
output. Encode produces the circular five-ring SVG (all 18 Apple color templates,
111-
camera and NFC center glyphs); decode is structural (ring bits → URL). The trained
111+
camera and NFC center glyphs); decode covers both the structural path (ring bits →
112+
URL) and camera detection — a grayscale ring detector (gradient-normal Hough over the
113+
five-ring geometry, affine/tilt refinement, rotation search, RS-arbitrated bit
114+
recovery) wired into `pipeline::scan_2d`. The trained
112115
Huffman tables embed ~1.7 MB, so the module sits behind the default-on `appclip`
113116
cargo feature (`default-features = false` for a lean build). Apple, App Clips and
114117
related marks are trademarks of Apple Inc.

‎src/codes/appclip/mod.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
//! foreground or a derived third color. See `doc/SPEC.md` of [rs/appclipcode] for the
2121
//! full write-up.
2222
//!
23-
//! Encoding and structural decoding (bits → URL) are implemented; camera detection of
24-
//! the printed circle is not yet wired into the live pipeline.
23+
//! Encoding, structural decoding (bits → URL) and camera detection ([`scan`], a
24+
//! grayscale ring detector + sampler over a [`crate::image::GrayFrame`]) are all
25+
//! implemented; [`crate::pipeline::scan_2d`] runs the detector automatically.
2526
//!
2627
//! [rs/appclipcode]: https://github.com/rs/appclipcode
2728
//!
@@ -45,11 +46,13 @@
4546
mod codec;
4647
mod gf;
4748
mod huffman;
49+
mod scan;
4850
mod svg;
4951
mod tables;
5052
mod url;
5153

5254
pub use codec::{decode_payload, encode_payload};
55+
pub use scan::scan;
5356
pub use svg::{Color, LogoKind, Palette, template_palette, third_color};
5457
pub use url::{compress_url, decompress_url};
5558

0 commit comments

Comments
 (0)