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

scalable_tests.cpp

v0.1.0-build.85 Latest Commit ede38f5

Size
22 KB
Lines
315
Language
cpp
SHA-256
7c65cda625b0…
View on GitHub

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

1// Profile 0 (scalable family) tests. White-box: includes the core
2// translation unit to check layout invariants; built without ckey_core.
3#include "../src/ckey.cpp"
4#include "check.h"
5#include "svg_raster.h"
6#include "print_sim.h"
7
8#include <cstdio>
9
10namespace {
11
12constexpr uint64_t kPrintKatV3 = 0x1908543413f369b8ull, kPrintKatV31 = 0x684c56624d3558a8ull; // stated in spec/CKey-Format-v0.28.0.md
13constexpr uint64_t kScalableKatV2 = 0xb97ef7acabd3c8d7ull, kScalableKatV31 = 0xc7df4dfd1c36458bull; // stated in spec/CKey-Format-v0.27.0.md
14
15std::vector<uint8_t> pseudo(size_t n, uint32_t seed) {
16 std::vector<uint8_t> out(n);
17 for (auto &b : out) { seed = seed * 1664525u + 1013904223u; b = static_cast<uint8_t>(seed >> 24); }
18 return out;
19}
20
21std::string render_styled(const ckey_scalable_symbol &s, const ckey_style &st) {
22 size_t n = 0;
23 if (ckey_render_scalable_styled_svg(&s, &st, 8, nullptr, 0, &n) != CKEY_INSUFFICIENT_BUFFER) return {};
24 std::string svg(n, '\0'); CKEY_CHECK(ckey_render_scalable_styled_svg(&s, &st, 8, svg.data(), n, &n) == CKEY_OK);
25 svg.resize(n - 1); return svg;
26}
27
28// Decodes a rendered SVG with the exact quad; returns the detected version or 0.
29uint32_t decode_svg(const std::string &svg, double unit, const std::vector<uint8_t> &payload) {
30 const int side = static_cast<int>(std::ceil(31 * unit));
31 const auto px = svgtest::rasterize(svgtest::parse(svg), side, side, 3);
32 const double k = static_cast<double>(side) / 31.0, lo = k, hi = 30 * k;
33 const double q[8] = {lo, lo, hi, lo, hi, hi, lo, hi};
34 std::vector<uint8_t> out(65535); size_t len = 0; uint32_t version = 0; ckey_diagnostics d{};
35 if (ckey_decode_scalable_gray8(px.data(), side, side, side, q, out.data(), out.size(), &len, &version, nullptr, &d) != CKEY_OK) return 0;
36 return len == payload.size() && std::equal(payload.begin(), payload.end(), out.begin()) ? version : 0;
37}
38
39std::string render(const ckey_scalable_symbol &s) {
40 size_t n = 0; CKEY_CHECK(ckey_render_scalable_svg(&s, 8, nullptr, 0, &n) == CKEY_INSUFFICIENT_BUFFER);
41 std::string svg(n, '\0'); CKEY_CHECK(ckey_render_scalable_svg(&s, 8, svg.data(), n, &n) == CKEY_OK);
42 svg.resize(n - 1); return svg;
43}
44
45// Renders and decodes with the exact quad; returns the detected version or 0.
46uint32_t image_round_trip(const ckey_scalable_symbol &s, const std::vector<uint8_t> &payload, double px_per_module, bool blur) {
47 const ScalableLayout &L = scalable_layout(static_cast<int>(s.version));
48 const double unit = px_per_module / L.pitch; // pixels per canonical unit
49 const int side = static_cast<int>(std::ceil(31 * unit));
50 auto px = svgtest::rasterize(svgtest::parse(render(s)), side, side, 3);
51 if (blur) {
52 auto out = px;
53 for (int y = 1; y < side - 1; ++y) for (int x = 1; x < side - 1; ++x) {
54 int t = 0; for (int dy = -1; dy <= 1; ++dy) for (int dx = -1; dx <= 1; ++dx) t += px[static_cast<size_t>(y + dy) * side + x + dx];
55 out[static_cast<size_t>(y) * side + x] = static_cast<uint8_t>(t / 9);
56 }
57 px.swap(out);
58 }
59 const double k = static_cast<double>(side) / 31.0, lo = k, hi = 30 * k;
60 const double q[8] = {lo, lo, hi, lo, hi, hi, lo, hi};
61 std::vector<uint8_t> out(65535); size_t len = 0; uint32_t version = 0; ckey_diagnostics d{};
62 if (ckey_decode_scalable_gray8(px.data(), side, side, side, q, out.data(), out.size(), &len, &version, nullptr, &d) != CKEY_OK) return 0;
63 if (len != payload.size() || !std::equal(payload.begin(), payload.end(), out.begin())) return 0;
64 return version;
65}
66
67} // namespace
68
69int main() {
70 // ---- Table invariants (both layout families) ----
71 for (int f = 0; f < kScalableFamilies; ++f) for (int v = 1; v <= kScalableVersions; ++v) {
72 const ScalableLayout &L = scalable_layout(v, f); const double p = L.pitch;
73 const double ring_limit = kScalableDialInner - (f == 0 ? 2.38 : 2.4) * p;
74 CKEY_CHECK(L.dial_slots % 8 == 0);
75 for (int S : L.ring_slots) CKEY_CHECK(S % 8 == 0);
76 CKEY_CHECK(L.ring_radius(L.rings - 1) <= ring_limit + 1e-9); // ring band clears the dial
77 CKEY_CHECK(L.ring_radius(L.rings) > ring_limit - 1e-9); // and uses all the room it has
78 CKEY_CHECK(L.ring_key_slot() + L.ring_key_span <= L.ring_slots.back());
79 CKEY_CHECK(L.codeword_bytes <= CKEY_SCALABLE_MAX_CODEWORD_BYTES);
80 if (f == 1) {
81 // Print family: every mark and gap is at least about one module.
82 for (int k = 0; k < L.rings; ++k) {
83 const double spacing = kTau * L.ring_radius(k) / L.ring_slots[static_cast<size_t>(k)];
84 CKEY_CHECK(spacing >= p - 1e-9 && spacing - 2 * L.dot >= 0.39 * p);
85 const double exact = kTau * L.ring_radius(k) / (8.0 * p); // slot count decided with slack
86 CKEY_CHECK(std::abs(exact - std::round(exact)) > 1e-6);
87 }
88 CKEY_CHECK(kTau * kScalableDialInner / L.dial_slots - L.ray_width >= 0.6 * p);
89 CKEY_CHECK(L.ring_pitch - 2 * L.dot >= 0.39 * p);
90 }
91 // Grid Key ink keeps a pitch of clearance from the frame, finders, and radial alphabet.
92 const DenseGeometry &g = L.geom; double worst = 1e9;
93 for (double x = g.grid_x0; x <= g.grid_x1 + 1e-12; x += (g.grid_x1 - g.grid_x0) / 8)
94 for (double y = g.grid_y0; y <= g.grid_y1 + 1e-12; y += (g.grid_y1 - g.grid_y0) / 8) {
95 DenseGeometry probe = g; probe.grid_x0 = probe.grid_y0 = 1e6; probe.grid_x1 = probe.grid_y1 = 1e6; // ignore itself
96 probe.module_half = 0; probe.frame_clear = probe.finder_clear = probe.polar_clear = 0;
97 worst = std::min(worst, dense_site_margin(probe, x, y));
98 }
99 CKEY_CHECK(worst >= p - 1e-9);
100 // Site set decided with slack (interoperable across libm implementations).
101 double slack = 1e9;
102 for (int row = 0; row < L.grid; ++row) for (int col = 0; col < L.grid; ++col)
103 slack = std::min(slack, std::abs(dense_site_margin(L.geom, (col + .5) * p, (row + .5) * p)));
104 CKEY_CHECK(slack > 1e-6);
105 }
106
107 // ---- P0.1 (print family): capacities, and every version decodes ----
108 std::printf("P0.1 (print) payload bytes at level M:");
109 size_t previous_print = 0;
110 for (uint32_t v = 1; v <= kScalableVersions; ++v) {
111 const size_t m = ckey_scalable_capacity(v, CKEY_ECC_M, CKEY_LAYOUT_PRINT);
112 std::printf(" %zu", m);
113 CKEY_CHECK(m > previous_print && m < ckey_scalable_capacity(v, CKEY_ECC_M, CKEY_LAYOUT_STANDARD));
114 previous_print = m;
115 }
116 std::printf("\n");
117 {
118 ckey_scalable_symbol ps{}; ckey_diagnostics pd{};
119 for (uint32_t v = 1; v <= kScalableVersions; ++v) {
120 const auto payload = pseudo(ckey_scalable_capacity(v, CKEY_ECC_M, CKEY_LAYOUT_PRINT), 3000u + v);
121 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), v, CKEY_ECC_M, CKEY_LAYOUT_PRINT, &ps, &pd) == CKEY_OK && ps.layout == CKEY_LAYOUT_PRINT);
122 const ScalableLayout &L = scalable_layout(static_cast<int>(v), 1);
123 const double unit = 6.0 / L.pitch; const int side = static_cast<int>(std::ceil(31 * unit));
124 const auto px = svgtest::rasterize(svgtest::parse(render(ps)), side, side, 3);
125 const double k = static_cast<double>(side) / 31.0, q[8] = {k, k, 30 * k, k, 30 * k, 30 * k, k, 30 * k};
126 std::vector<uint8_t> out(65535); size_t len = 0; uint32_t version = 0, layout = 9;
127 const ckey_status st = ckey_decode_scalable_gray8(px.data(), side, side, side, q, out.data(), out.size(), &len, &version, &layout, &pd);
128 if (st != CKEY_OK || version != v || layout != CKEY_LAYOUT_PRINT) std::printf("P0.1 v%u: status %d version %u layout %u\n", v, st, version, layout);
129 CKEY_CHECK(st == CKEY_OK && version == v && layout == CKEY_LAYOUT_PRINT && len == payload.size() && std::equal(payload.begin(), payload.end(), out.begin()));
130 }
131 std::printf("P0.1: all 31 versions decode at 6 px/module and are told apart from P0 by their keys\n");
132 }
133
134 // ---- Capacity grows smoothly ----
135 std::printf(" v modules sites L M Q H (payload bytes)\n");
136 size_t previous_m = 0;
137 for (int v = 1; v <= kScalableVersions; ++v) {
138 const size_t m = ckey_scalable_capacity(static_cast<uint32_t>(v), CKEY_ECC_M, CKEY_LAYOUT_STANDARD);
139 std::printf("%2d %6d %7zu %5zu %5zu %5zu %5zu\n", v, kScalableGrid[v - 1], scalable_layout(v).nodes.size(),
140 ckey_scalable_capacity(v, CKEY_ECC_L, CKEY_LAYOUT_STANDARD), m, ckey_scalable_capacity(v, CKEY_ECC_Q, CKEY_LAYOUT_STANDARD), ckey_scalable_capacity(v, CKEY_ECC_H, CKEY_LAYOUT_STANDARD));
141 CKEY_CHECK(m > previous_m);
142 if (previous_m) CKEY_CHECK(static_cast<double>(m) / previous_m < 1.6); // no large jumps
143 CKEY_CHECK(ckey_scalable_capacity(v, CKEY_ECC_L, CKEY_LAYOUT_STANDARD) > m && m > ckey_scalable_capacity(v, CKEY_ECC_Q, CKEY_LAYOUT_STANDARD) && ckey_scalable_capacity(v, CKEY_ECC_Q, CKEY_LAYOUT_STANDARD) > ckey_scalable_capacity(v, CKEY_ECC_H, CKEY_LAYOUT_STANDARD));
144 previous_m = m;
145 }
146 CKEY_CHECK(ckey_scalable_capacity(0, CKEY_ECC_M, CKEY_LAYOUT_STANDARD) == 0 && ckey_scalable_capacity(32, CKEY_ECC_M, CKEY_LAYOUT_STANDARD) == 0);
147
148 // ---- Auto version selection is the smallest that fits ----
149 ckey_scalable_symbol s{}; ckey_diagnostics d{};
150 const std::string text33 = "https://citeory.com/tour?ref=ckey"; // 33 characters
151 CKEY_CHECK(text33.size() == 33);
152 CKEY_CHECK(ckey_encode_scalable(reinterpret_cast<const uint8_t *>(text33.data()), text33.size(), 0, CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
153 CKEY_CHECK(ckey_scalable_capacity(s.version, CKEY_ECC_M, CKEY_LAYOUT_STANDARD) >= 33 && (s.version == 1 || ckey_scalable_capacity(s.version - 1, CKEY_ECC_M, CKEY_LAYOUT_STANDARD) < 33));
154 std::printf("33-character payload -> Profile 0 v%u (%d modules across) at level M\n", s.version, kScalableGrid[s.version - 1]);
155
156 // ---- Byte-level ECC: correct up to parity/2 errors in every block ----
157 for (int level = 0; level < 4; ++level) {
158 const auto payload = pseudo(ckey_scalable_capacity(14, static_cast<ckey_ecc_level>(level), CKEY_LAYOUT_STANDARD), 5u + level);
159 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), 14, static_cast<ckey_ecc_level>(level), CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
160 const auto blocks = scalable_blocks(s.codeword_length, level);
161 auto damaged = s;
162 // Interleaved position of (block b, byte j) is the count of earlier bytes.
163 for (size_t b = 0; b < blocks.size(); ++b) {
164 const size_t t = (blocks[b].n - blocks[b].k) / 2;
165 for (size_t e = 0; e < t; ++e) {
166 const size_t j = (e * 7 + 3) % blocks[b].n; size_t pos = 0;
167 for (size_t jj = 0; jj < j; ++jj) for (size_t bb = 0; bb < blocks.size(); ++bb) pos += jj < blocks[bb].n;
168 for (size_t bb = 0; bb < b; ++bb) pos += j < blocks[bb].n;
169 damaged.codeword[pos] ^= static_cast<uint8_t>(0x5a + e);
170 }
171 }
172 std::vector<uint8_t> out(65535); size_t len = 0;
173 CKEY_CHECK(ckey_decode_scalable(damaged.codeword, damaged.codeword_length, 14, CKEY_LAYOUT_STANDARD, out.data(), out.size(), &len, &d) == CKEY_OK);
174 CKEY_CHECK(len == payload.size() && std::equal(payload.begin(), payload.end(), out.begin()));
175 }
176
177 // ---- Every version: render, rasterize, decode; the keys identify the version ----
178 for (int v = 1; v <= kScalableVersions; ++v) {
179 for (int kind = 0; kind < 3; ++kind) {
180 const size_t cap = ckey_scalable_capacity(v, CKEY_ECC_M, CKEY_LAYOUT_STANDARD);
181 const auto payload = kind == 0 ? pseudo(cap, 100u + v) : std::vector<uint8_t>(cap, kind == 1 ? 0x00 : 0xff);
182 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), static_cast<uint32_t>(v), CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
183 const uint32_t detected = image_round_trip(s, payload, 6.0, false);
184 if (detected != static_cast<uint32_t>(v)) std::printf("version %d payload kind %d: detected %u\n", v, kind, detected);
185 CKEY_CHECK(detected == static_cast<uint32_t>(v));
186 }
187 const auto payload = pseudo(ckey_scalable_capacity(v, CKEY_ECC_M, CKEY_LAYOUT_STANDARD), 900u + v);
188 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), static_cast<uint32_t>(v), CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
189 CKEY_CHECK(image_round_trip(s, payload, 8.0, true) == static_cast<uint32_t>(v));
190 }
191 std::printf("all %d versions decode from rendered images (6 px/module clean, 8 px/module blurred) and are identified by their keys\n", kScalableVersions);
192 // ---- Known-answer vectors: FNV-1a-64 over every site in placement order
193 // of (identity little-endian, printed value), payload "CKey renderer
194 // test" at level M. Integer-only, so platform independent. ----
195 {
196 const std::string text = "CKey renderer test";
197 for (const auto &[version, expected] : {std::pair{2u, kScalableKatV2}, std::pair{31u, kScalableKatV31}}) {
198 CKEY_CHECK(ckey_encode_scalable(reinterpret_cast<const uint8_t *>(text.data()), text.size(), version, CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
199 const ScalableLayout &L = scalable_layout(static_cast<int>(version));
200 uint64_t hash = 0xcbf29ce484222325ull;
201 const auto fnv = [&](uint8_t b) { hash = (hash ^ b) * 0x100000001b3ull; };
202 for (size_t i = 0; i < L.nodes.size(); ++i) { for (int k = 0; k < 4; ++k) fnv(static_cast<uint8_t>(L.nodes[i].id >> (8 * k))); fnv(scalable_site_value(L, &s, i) ? 1 : 0); }
203 std::printf("Profile 0 v%u conformance hash %016llx\n", version, static_cast<unsigned long long>(hash));
204 CKEY_CHECK(hash == expected);
205 }
206 }
207 // ---- Design system ----
208 {
209 ckey_style base; ckey_style_default(&base);
210 const auto payload = pseudo(ckey_scalable_capacity(11, CKEY_ECC_M, CKEY_LAYOUT_STANDARD), 4242);
211 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), 11, CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
212 CKEY_CHECK(ckey_check_scalable_style(&s, &base, &d) == CKEY_OK);
213 CKEY_CHECK(render_styled(s, base) == render(s)); // the plain renderer is the default style
214
215 // Every payload-mark shape option decodes, at a coarse and a fine version.
216 for (uint32_t version : {2u, 20u}) {
217 const auto data = pseudo(ckey_scalable_capacity(version, CKEY_ECC_M, CKEY_LAYOUT_STANDARD), 77 + version);
218 CKEY_CHECK(ckey_encode_scalable(data.data(), data.size(), version, CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
219 const double unit = 7.0 / (29.0 / ckey_scalable_modules_across(version));
220 for (int shape = 0; shape < 4; ++shape) for (int rings = 0; rings < 2; ++rings) for (int rays = 0; rays < 2; ++rays) {
221 ckey_style st = base;
222 st.modules = static_cast<ckey_module_shape>(shape); st.rings = static_cast<ckey_ring_style>(rings); st.rays = static_cast<ckey_ray_cap>(rays);
223 const uint32_t got = decode_svg(render_styled(s, st), unit, data);
224 if (got != version) std::printf("style shape %d rings %d rays %d failed at v%u\n", shape, rings, rays, version);
225 CKEY_CHECK(got == version);
226 }
227 }
228 std::printf("design system: 16 mark-shape combinations decode at v2 and v20\n");
229
230 // Concept-art look: champagne sheen, navy vignette, sage/grey accents, depth, grain.
231 ckey_style art; CKEY_CHECK(ckey_style_preset_fill(&art, CKEY_PRESET_CONCEPT) == CKEY_OK);
232 CKEY_CHECK(ckey_style_preset_fill(&art, static_cast<ckey_style_preset>(7)) == CKEY_INVALID_ARGUMENT);
233 CKEY_CHECK(ckey_style_preset_fill(&art, CKEY_PRESET_CONCEPT) == CKEY_OK);
234 // The default is black on white (specification §4).
235 CKEY_CHECK(base.ink.r == 0 && base.ink.g == 0 && base.background.r == 255 && base.background.b == 255);
236 CKEY_CHECK(ckey_encode_scalable(payload.data(), payload.size(), 11, CKEY_ECC_M, CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
237 CKEY_CHECK(ckey_check_scalable_style(&s, &art, &d) == CKEY_OK);
238 std::printf("concept style: %s\n", d.message);
239 CKEY_CHECK(decode_svg(render_styled(s, art), 7.0 / 0.5, payload) == 11);
240
241 // The checker refuses near-invisible styles, with a reason; lower contrast is a warning
242 // (certification's photo sweep decides whether it scans).
243 ckey_style low = base; low.ink = low.ink_shade = {0xe8, 0xe8, 0xe8};
244 CKEY_CHECK(ckey_check_scalable_style(&s, &low, &d) == CKEY_INVALID_ARGUMENT);
245 std::printf("refused: %s\n", d.message);
246 ckey_style mixed = base; mixed.background = mixed.background_edge = {0x80, 0x80, 0x80}; mixed.accents[0] = {0xff, 0xff, 0xff}; mixed.accent_count = 1; mixed.accent_percent = 10;
247 CKEY_CHECK(ckey_check_scalable_style(&s, &mixed, &d) == CKEY_INVALID_ARGUMENT);
248 ckey_style light = base; light.background = light.background_edge = {0xf4, 0xf1, 0xe8}; light.ink = light.ink_shade = {0x10, 0x10, 0x10};
249 CKEY_CHECK(ckey_check_scalable_style(&s, &light, &d) == CKEY_OK);
250 light.shadow = 0.6; CKEY_CHECK(ckey_check_scalable_style(&s, &light, &d) == CKEY_OK && std::string(d.message).find("certify it") != std::string::npos);
251 size_t n = 0; CKEY_CHECK(ckey_render_scalable_styled_svg(&s, &low, 8, nullptr, 0, &n) == CKEY_INVALID_ARGUMENT);
252
253 // Logos: the empty centre costs nothing; a larger logo needs a plate and
254 // must stay within half the worst block's correction. The largest logo
255 // the checker accepts at each level still decodes (hidden marks are
256 // corrected by Reed-Solomon, as with QR).
257 ckey_style logo = base; logo.logo_href = "data:image/png;base64,iVBORw0KGgo=";
258 CKEY_CHECK(ckey_check_scalable_style(&s, &logo, &d) == CKEY_OK && ckey_scalable_logo_load(&s, 0) == 0);
259 logo.logo_radius = 5.0; CKEY_CHECK(ckey_check_scalable_style(&s, &logo, &d) == CKEY_INVALID_ARGUMENT); // no plate
260 logo.logo_plate = 1; logo.logo_radius = ckey_scalable_logo_max_radius(11, CKEY_LAYOUT_STANDARD) + 0.1;
261 CKEY_CHECK(ckey_check_scalable_style(&s, &logo, &d) == CKEY_INVALID_ARGUMENT); // would hide the Ring Key
262 for (uint32_t version : {6u, 11u, 20u}) for (int level = 0; level < 4; ++level) {
263 const auto data = pseudo(ckey_scalable_capacity(version, static_cast<ckey_ecc_level>(level), CKEY_LAYOUT_STANDARD), 900 + version * 4 + level);
264 CKEY_CHECK(ckey_encode_scalable(data.data(), data.size(), version, static_cast<ckey_ecc_level>(level), CKEY_LAYOUT_STANDARD, &s, &d) == CKEY_OK);
265 double best = 0;
266 for (double r = 3.0; r <= ckey_scalable_logo_max_radius(version, CKEY_LAYOUT_STANDARD); r += 0.05) { logo.logo_radius = r; if (ckey_check_scalable_style(&s, &logo, &d) == CKEY_OK) best = r; }
267 logo.logo_radius = best;
268 const uint32_t got = best > 0 ? decode_svg(render_styled(s, logo), 7.0 / (29.0 / ckey_scalable_modules_across(version)), data) : version;
269 std::printf("v%u level %c: largest accepted logo radius %.2f (load %.0f%%) -> %s\n", version, "LMQH"[level], best,
270 100 * ckey_scalable_logo_load(&s, best), got == version ? "decodes" : "FAILS");
271 CKEY_CHECK(got == version);
272 }
273 }
274 // ---- Print: device-grid bitmaps, and P0.1's advantage on a printer model ----
275 {
276 const std::string text = "CKEY-CAL P0.1 120B 0.42in ";
277 std::string payload = text; while (payload.size() < 120) payload += "0123456789";
278 payload.resize(120);
279 const std::vector<uint8_t> bytes(payload.begin(), payload.end());
280 // Old inkjet on plain paper (600 dpi, 0.045 mm ink spread), black on paper, camera 23 px/mm.
281 const printsim::Printer inkjet{600, 0.045, 0.03}; const printsim::Camera camera{23, 0.7, 4};
282 bool decoded[2] = {false, false};
283 for (int family = 0; family < 2; ++family) {
284 ckey_scalable_symbol ps{}; ckey_diagnostics pd{};
285 CKEY_CHECK(ckey_encode_scalable(bytes.data(), bytes.size(), 0, CKEY_ECC_M, static_cast<ckey_scalable_layout>(family), &ps, &pd) == CKEY_OK);
286 size_t side = 0;
287 CKEY_CHECK(ckey_render_scalable_print(&ps, nullptr, 0.5, 600, nullptr, 0, &side) == CKEY_INSUFFICIENT_BUFFER && side == 300);
288 std::vector<uint8_t> bitmap(side * side);
289 CKEY_CHECK(ckey_render_scalable_print(&ps, nullptr, 0.5, 600, bitmap.data(), bitmap.size(), &side) == CKEY_OK);
290 for (uint8_t v : bitmap) CKEY_CHECK(v == 0 || v == 255); // no grey for a printer to halftone
291 int cam_side = 0;
292 const auto frame = printsim::print_and_capture(render(ps), 0.42 * 25.4, inkjet, camera, true, 99, cam_side);
293 const double k = cam_side / 31.0, q[8] = {k, k, 30 * k, k, 30 * k, 30 * k, k, 30 * k};
294 std::vector<uint8_t> out(65535); size_t len = 0; uint32_t version = 0, layout = 0;
295 decoded[family] = ckey_decode_scalable_gray8(frame.data(), cam_side, cam_side, cam_side, q, out.data(), out.size(), &len, &version, &layout, &pd) == CKEY_OK &&
296 len == bytes.size() && std::equal(bytes.begin(), bytes.end(), out.begin());
297 }
298 std::printf("120-byte payload printed 0.42 in wide on the old-inkjet model: P0 %s, P0.1 %s\n", decoded[0] ? "decodes" : "fails", decoded[1] ? "decodes" : "fails");
299 CKEY_CHECK(!decoded[0] && decoded[1]);
300
301 // P0.1 conformance vectors (same method as Profile 0's).
302 const std::string kat = "CKey renderer test";
303 for (const auto &[version, expected] : {std::pair{3u, kPrintKatV3}, std::pair{31u, kPrintKatV31}}) {
304 ckey_scalable_symbol ps{}; ckey_diagnostics pd{};
305 CKEY_CHECK(ckey_encode_scalable(reinterpret_cast<const uint8_t *>(kat.data()), kat.size(), version, CKEY_ECC_M, CKEY_LAYOUT_PRINT, &ps, &pd) == CKEY_OK);
306 const ScalableLayout &L = scalable_layout(static_cast<int>(version), 1);
307 uint64_t hash = 0xcbf29ce484222325ull;
308 const auto fnv = [&](uint8_t b) { hash = (hash ^ b) * 0x100000001b3ull; };
309 for (size_t i = 0; i < L.nodes.size(); ++i) { for (int k = 0; k < 4; ++k) fnv(static_cast<uint8_t>(L.nodes[i].id >> (8 * k))); fnv(scalable_site_value(L, &ps, i) ? 1 : 0); }
310 std::printf("Profile 0.1 v%u conformance hash %016llx\n", version, static_cast<unsigned long long>(hash));
311 CKEY_CHECK(hash == expected);
312 }
313 }
314 std::printf("CKey Profile 0 tests passed\n");
315}