Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

reader_pipeline_tests.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
12 KB
Lines
172
Language
cpp
SHA-256
1978f460d0d2…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1// Simulated reader: renders symbols, composites them into full camera-sized
2// frames with rotation, keystone, blur, noise, and clutter, then runs the
3// exact pipeline the iPhone reader uses -- ckey_find_borders_gray8, then
4// ckey_refine_quad_gray8 per profile, then the ckey_decode_*_gray8 calls.
5#include "ckey/ckey.h"
6#include "check.h"
7#include "svg_raster.h"
8
9#include <cmath>
10#include <cstdio>
11#include <string>
12#include <vector>
13
14namespace {
15
16struct Rng { uint32_t s; uint32_t next() { s = s * 1664525u + 1013904223u; return s; } double unit() { return (next() >> 8) / 16777216.0; } };
17
18// Profile 0 symbols are encoded at this version (set per case).
19uint32_t g_scalable_version = 1;
20ckey_scalable_layout g_scalable_layout = CKEY_LAYOUT_STANDARD; // Profile 0 (standard) or 0.1 (print)
21
22std::vector<uint8_t> payload_for(int profile, uint32_t seed) {
23 const size_t n = profile == 0 ? ckey_scalable_capacity(g_scalable_version, CKEY_ECC_M, g_scalable_layout)
24 : profile == 1 ? 16 : profile == 2 ? 120 : 1200;
25 std::vector<uint8_t> p(n); Rng r{seed}; for (auto &b : p) b = static_cast<uint8_t>(r.next() >> 24); return p;
26}
27
28std::string render(int profile, const std::vector<uint8_t> &payload) {
29 ckey_diagnostics d{}; size_t n = 0; std::string svg;
30 if (profile == 0) { ckey_scalable_symbol s{}; ckey_encode_scalable(payload.data(), payload.size(), g_scalable_version, CKEY_ECC_M, g_scalable_layout, &s, &d); ckey_render_scalable_svg(&s, 8, nullptr, 0, &n); svg.resize(n); ckey_render_scalable_svg(&s, 8, svg.data(), n, &n); }
31 else if (profile == 1) { ckey_symbol s{}; ckey_encode_locator(payload.data(), payload.size(), &s, &d); ckey_render_svg(&s, 8, nullptr, 0, &n); svg.resize(n); ckey_render_svg(&s, 8, svg.data(), n, &n); }
32 else if (profile == 2) { ckey_text_symbol s{}; ckey_encode_text(payload.data(), payload.size(), &s, &d); ckey_render_text_svg(&s, 8, nullptr, 0, &n); svg.resize(n); ckey_render_text_svg(&s, 8, svg.data(), n, &n); }
33 else { ckey_extended_symbol s{}; ckey_encode_extended(payload.data(), payload.size(), &s, &d); ckey_render_extended_svg(&s, 8, nullptr, 0, &n); svg.resize(n); ckey_render_extended_svg(&s, 8, svg.data(), n, &n); }
34 svg.resize(n - 1); return svg;
35}
36
37void solve(const double src[8], const double dst[8], double h[8]) {
38 double a[8][9]{};
39 for (int i = 0; i < 4; ++i) {
40 const double x = src[2 * i], y = src[2 * i + 1], u = dst[2 * i], v = dst[2 * i + 1];
41 const double r0[9] = {x, y, 1, 0, 0, 0, -u * x, -u * y, u}, r1[9] = {0, 0, 0, x, y, 1, -v * x, -v * y, v};
42 for (int j = 0; j < 9; ++j) { a[2 * i][j] = r0[j]; a[2 * i + 1][j] = r1[j]; }
43 }
44 for (int c = 0; c < 8; ++c) {
45 int p = c; for (int r = c + 1; r < 8; ++r) if (std::abs(a[r][c]) > std::abs(a[p][c])) p = r;
46 for (int j = 0; j < 9; ++j) std::swap(a[p][j], a[c][j]);
47 for (int j = 8; j >= c; --j) a[c][j] /= a[c][c];
48 for (int r = 0; r < 8; ++r) if (r != c) { const double f = a[r][c]; for (int j = c; j < 9; ++j) a[r][j] -= f * a[c][j]; }
49 }
50 for (int i = 0; i < 8; ++i) h[i] = a[i][8];
51}
52
53struct Scene { int profile; int frame_w, frame_h; double symbol_px; double rotation_deg; double keystone; bool blur; bool dark_on_light; uint32_t seed; };
54
55// Builds a camera frame: clutter background, the symbol (including its own
56// quiet margin) warped by rotation + keystone, optional 3x3 blur, noise.
57std::vector<uint8_t> make_frame(const Scene &sc, const std::string &svg) {
58 const int units = sc.profile == 3 ? 124 : 31;
59 const int raster = static_cast<int>(std::ceil(sc.symbol_px * units / (sc.profile == 3 ? 116.0 : 29.0)));
60 auto sym = svgtest::rasterize(svgtest::parse(svg), raster, raster, 3);
61 if (sc.dark_on_light) for (auto &v : sym) v = static_cast<uint8_t>(250 - v);
62 std::vector<uint8_t> frame(static_cast<size_t>(sc.frame_w) * sc.frame_h);
63 Rng rng{sc.seed};
64 for (int y = 0; y < sc.frame_h; ++y) for (int x = 0; x < sc.frame_w; ++x)
65 frame[static_cast<size_t>(y) * sc.frame_w + x] = static_cast<uint8_t>(70 + 50.0 * x / sc.frame_w + 30.0 * y / sc.frame_h);
66 for (int k = 0; k < 14; ++k) { // clutter rectangles of varied brightness
67 const int x0 = static_cast<int>(rng.unit() * sc.frame_w), y0 = static_cast<int>(rng.unit() * sc.frame_h);
68 const int w = 20 + static_cast<int>(rng.unit() * sc.frame_w / 5), h = 20 + static_cast<int>(rng.unit() * sc.frame_h / 5);
69 const uint8_t v = static_cast<uint8_t>(rng.unit() * 255);
70 for (int y = y0; y < std::min(sc.frame_h, y0 + h); ++y) for (int x = x0; x < std::min(sc.frame_w, x0 + w); ++x) frame[static_cast<size_t>(y) * sc.frame_w + x] = v;
71 }
72 // Destination corners of the symbol raster in the frame.
73 const double cx = sc.frame_w / 2.0, cy = sc.frame_h / 2.0, half = raster / 2.0, a = sc.rotation_deg * 3.14159265358979 / 180;
74 double dst[8]; const double corner[4][2] = {{-1, -1}, {1, -1}, {1, 1}, {-1, 1}};
75 for (int i = 0; i < 4; ++i) {
76 double x = corner[i][0] * half, y = corner[i][1] * half;
77 x *= 1.0 - sc.keystone * (corner[i][1] < 0 ? 1 : 0); // narrow the top edge
78 dst[2 * i] = cx + x * std::cos(a) - y * std::sin(a); dst[2 * i + 1] = cy + x * std::sin(a) + y * std::cos(a);
79 }
80 const double src[8] = {0, 0, static_cast<double>(raster), 0, static_cast<double>(raster), static_cast<double>(raster), 0, static_cast<double>(raster)};
81 double inv[8]; solve(dst, src, inv);
82 double x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9; for (int i = 0; i < 4; ++i) { x0 = std::min(x0, dst[2 * i]); x1 = std::max(x1, dst[2 * i]); y0 = std::min(y0, dst[2 * i + 1]); y1 = std::max(y1, dst[2 * i + 1]); }
83 for (int y = std::max(0, static_cast<int>(y0)); y < std::min(sc.frame_h, static_cast<int>(y1) + 1); ++y)
84 for (int x = std::max(0, static_cast<int>(x0)); x < std::min(sc.frame_w, static_cast<int>(x1) + 1); ++x) {
85 const double d = inv[6] * x + inv[7] * y + 1, u = (inv[0] * x + inv[1] * y + inv[2]) / d - .5, v = (inv[3] * x + inv[4] * y + inv[5]) / d - .5;
86 if (u < 0 || v < 0 || u >= raster - 1 || v >= raster - 1) continue;
87 const int iu = static_cast<int>(u), iv = static_cast<int>(v); const double fu = u - iu, fv = v - iv;
88 const auto at = [&](int xx, int yy) { return static_cast<double>(sym[static_cast<size_t>(yy) * raster + xx]); };
89 frame[static_cast<size_t>(y) * sc.frame_w + x] = static_cast<uint8_t>(std::lround(
90 (at(iu, iv) * (1 - fu) + at(iu + 1, iv) * fu) * (1 - fv) + (at(iu, iv + 1) * (1 - fu) + at(iu + 1, iv + 1) * fu) * fv));
91 }
92 if (sc.blur) {
93 std::vector<uint8_t> out(frame);
94 for (int y = 1; y < sc.frame_h - 1; ++y) for (int x = 1; x < sc.frame_w - 1; ++x) {
95 int t = 0; for (int dy = -1; dy <= 1; ++dy) for (int dx = -1; dx <= 1; ++dx) t += frame[static_cast<size_t>(y + dy) * sc.frame_w + x + dx];
96 out[static_cast<size_t>(y) * sc.frame_w + x] = static_cast<uint8_t>(t / 9);
97 }
98 frame.swap(out);
99 }
100 for (auto &v : frame) v = static_cast<uint8_t>(std::clamp(static_cast<int>(v) + static_cast<int>(rng.unit() * 13) - 6, 0, 255));
101 return frame;
102}
103
104// The reader's loop: up to six border candidates; for each, refine to every
105// profile's field and try each decoder. Returns the decoded profile or 0.
106int scan(const std::vector<uint8_t> &frame, int w, int h, const std::vector<uint8_t> &expected, size_t *borders_found) {
107 double borders[6 * 8];
108 const size_t n = ckey_find_borders_gray8(frame.data(), w, h, w, borders, 6);
109 if (borders_found) *borders_found = n;
110 std::vector<uint8_t> out(65535); size_t len = 0; ckey_diagnostics d{}; // P0 payloads exceed 2,048 bytes
111 for (size_t i = 0; i < n; ++i) {
112 double q[8]; uint32_t version = 0;
113 if (ckey_refine_quad_gray8(frame.data(), w, h, w, borders + 8 * i, CKEY_FIELD_SCALABLE, q) > 0 &&
114 ckey_decode_scalable_gray8(frame.data(), w, h, w, q, out.data(), out.size(), &len, &version, nullptr, &d) == CKEY_OK &&
115 len == expected.size() && std::equal(expected.begin(), expected.end(), out.begin())) return 0;
116 if (ckey_refine_quad_gray8(frame.data(), w, h, w, borders + 8 * i, CKEY_FIELD_LOCATOR, q) > 0 &&
117 ckey_decode_gray8(frame.data(), w, h, w, q, out.data(), out.size(), &d) == CKEY_OK &&
118 expected.size() == 16 && std::equal(expected.begin(), expected.end(), out.begin())) return 1;
119 if (ckey_refine_quad_gray8(frame.data(), w, h, w, borders + 8 * i, CKEY_FIELD_TEXT, q) > 0 &&
120 ckey_decode_text_gray8(frame.data(), w, h, w, q, out.data(), out.size(), &len, &d) == CKEY_OK &&
121 len == expected.size() && std::equal(expected.begin(), expected.end(), out.begin())) return 2;
122 if (ckey_refine_quad_gray8(frame.data(), w, h, w, borders + 8 * i, CKEY_FIELD_EXTENDED, q) > 0 &&
123 ckey_decode_extended_gray8(frame.data(), w, h, w, q, out.data(), out.size(), &len, &d) == CKEY_OK &&
124 len == expected.size() && std::equal(expected.begin(), expected.end(), out.begin())) return 3;
125 }
126 return -1;
127}
128
129} // namespace
130
131int main() {
132 struct Case { int profile, frame_w, frame_h; double px_per_unit; uint32_t version; ckey_scalable_layout layout = CKEY_LAYOUT_STANDARD; };
133 // Field width in pixels = px/unit x field units; frame sizes match the
134 // reader's portrait capture (1080p and 4K).
135 // Profile 0 cases give px/unit as 8 px per module (px/unit = 8 / pitch).
136 const Case cases[] = {{1, 1080, 1920, 10, 0}, {1, 1080, 1920, 14, 0}, {2, 1080, 1920, 14, 0}, {2, 1080, 1920, 20, 0},
137 {3, 2160, 3840, 13, 0}, {3, 2160, 3840, 16, 0},
138 {0, 1080, 1920, 8.0 * 29 / 29, 1}, {0, 1080, 1920, 8.0 * 31 / 29, 2}, {0, 1080, 1920, 8.0 * 58 / 29, 11},
139 {0, 1080, 1920, 8.0 * 108 / 29, 20}, {0, 2160, 3840, 7.0 * 232 / 29, 31},
140 // Profile 0.1 (print layout): the reader must find and decode it through the same pipeline.
141 {0, 1080, 1920, 8.0 * 31 / 29, 2, CKEY_LAYOUT_PRINT}, {0, 1080, 1920, 8.0 * 58 / 29, 11, CKEY_LAYOUT_PRINT},
142 {0, 1080, 1920, 6.0 * 108 / 29, 20, CKEY_LAYOUT_PRINT}};
143 const double rotations[] = {0, 7, 18, 33};
144 int total = 0, passed = 0, skipped = 0;
145 for (const auto &c : cases) {
146 g_scalable_version = c.version; g_scalable_layout = c.layout;
147 int ok = 0, n = 0;
148 for (double rotation : rotations) for (double keystone : {0.0, 0.08}) for (int variant = 0; variant < 2; ++variant) {
149 const Scene sc{c.profile, c.frame_w, c.frame_h, c.px_per_unit * (c.profile == 3 ? 116 : 29), rotation, keystone,
150 variant == 1, variant == 1 && c.profile != 3, static_cast<uint32_t>(rotation * 13 + keystone * 100 + variant)};
151 // Skip scenes where the rotated symbol cannot fit inside the frame
152 // (no reader could see its whole border).
153 const double extent = sc.symbol_px * (c.profile == 3 ? 124.0 / 116 : 31.0 / 29) *
154 (std::abs(std::cos(rotation * 3.14159265358979 / 180)) + std::abs(std::sin(rotation * 3.14159265358979 / 180)));
155 if (extent > 0.98 * std::min(c.frame_w, c.frame_h)) { ++skipped; continue; }
156 const auto payload = payload_for(c.profile, 7u + static_cast<uint32_t>(rotation) + variant);
157 const auto frame = make_frame(sc, render(c.profile, payload));
158 size_t borders = 0;
159 const int result = scan(frame, c.frame_w, c.frame_h, payload, &borders);
160 ++n; ok += result == c.profile;
161 if (result != c.profile)
162 std::printf(" miss: P%d %.0f px/unit rot %.0f keystone %.2f %s%s (borders found %zu)\n", c.profile, c.px_per_unit, rotation,
163 keystone, variant ? "blur" : "sharp", sc.dark_on_light ? " dark-on-light" : "", borders);
164 }
165 if (c.profile == 0) std::printf("P0%s v%u %dx%d @ %.0f px/unit: %d/%d scenes decoded\n", c.layout == CKEY_LAYOUT_PRINT ? ".1" : "", c.version, c.frame_w, c.frame_h, c.px_per_unit, ok, n);
166 else std::printf("P%d %dx%d @ %.0f px/unit: %d/%d scenes decoded\n", c.profile, c.frame_w, c.frame_h, c.px_per_unit, ok, n);
167 total += n; passed += ok;
168 }
169 std::printf("simulated reader: %d/%d scenes decoded (%d impossible scenes skipped)\n", passed, total, skipped);
170 CKEY_CHECK(passed == total);
171 std::printf("CKey reader pipeline tests passed\n");
172}