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

api_certify.inc

v0.1.0-build.85 Latest Commit ede38f5

Size
22 KB
Lines
349
Language
cpp
SHA-256
598d4195848f…
View on GitHub

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

1// Public C ABI: optical certification (specification §4). Included after
2// core_internal.inc. It renders with ckey_render and decodes with ckey_scan,
3// so it is its own library, ckey_certify.
4
5namespace {
6
7
8// Straight-alpha RGBA over an opaque backdrop colour or image, to luma 0..1.
9std::vector<double> composite_luma(const std::vector<uint8_t> &rgba, int side, const ckey_rgb *colour, const ckey_backdrop *image) {
10 std::vector<double> luma(static_cast<size_t>(side) * side);
11 const size_t stride = image && image->rgba ? (image->stride ? image->stride : static_cast<size_t>(image->width) * 4) : 0;
12 for (int y = 0; y < side; ++y) for (int x = 0; x < side; ++x) {
13 const size_t i = static_cast<size_t>(y) * side + x;
14 double br = 1, bg = 1, bb = 1;
15 if (colour) { br = colour->r / 255.0; bg = colour->g / 255.0; bb = colour->b / 255.0; }
16 else if (image && image->rgba) {
17 const uint32_t sx = std::min<uint32_t>(image->width - 1, static_cast<uint32_t>((x + .5) * image->width / side));
18 const uint32_t sy = std::min<uint32_t>(image->height - 1, static_cast<uint32_t>((y + .5) * image->height / side));
19 const uint8_t *p = image->rgba + sy * stride + static_cast<size_t>(sx) * 4;
20 const double a = p[3] / 255.0; // the host's pixels are composited over white
21 br = 1 - a + a * p[0] / 255.0; bg = 1 - a + a * p[1] / 255.0; bb = 1 - a + a * p[2] / 255.0;
22 }
23 const double a = rgba[4 * i + 3] / 255.0;
24 const double r = a * rgba[4 * i] / 255.0 + (1 - a) * br, g = a * rgba[4 * i + 1] / 255.0 + (1 - a) * bg, b = a * rgba[4 * i + 2] / 255.0 + (1 - a) * bb;
25 luma[i] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
26 }
27 return luma;
28}
29
30void gaussian_blur(std::vector<double> &img, int side, double sigma) {
31 if (sigma <= 0.05) return;
32 const int r = std::max(1, static_cast<int>(std::ceil(3 * sigma)));
33 std::vector<double> k(static_cast<size_t>(2 * r + 1)); double sum = 0;
34 for (int i = -r; i <= r; ++i) { k[static_cast<size_t>(i + r)] = std::exp(-0.5 * i * i / (sigma * sigma)); sum += k[static_cast<size_t>(i + r)]; }
35 for (auto &v : k) v /= sum;
36 std::vector<double> tmp(img.size());
37 for (int y = 0; y < side; ++y) for (int x = 0; x < side; ++x) {
38 double acc = 0; for (int i = -r; i <= r; ++i) acc += k[static_cast<size_t>(i + r)] * img[static_cast<size_t>(y) * side + std::clamp(x + i, 0, side - 1)];
39 tmp[static_cast<size_t>(y) * side + x] = acc;
40 }
41 for (int y = 0; y < side; ++y) for (int x = 0; x < side; ++x) {
42 double acc = 0; for (int i = -r; i <= r; ++i) acc += k[static_cast<size_t>(i + r)] * tmp[static_cast<size_t>(std::clamp(y + i, 0, side - 1)) * side + x];
43 img[static_cast<size_t>(y) * side + x] = acc;
44 }
45}
46
47double percentile(std::vector<double> v, double q) {
48 if (v.empty()) return 0;
49 const size_t k = std::min(v.size() - 1, static_cast<size_t>(q * (v.size() - 1)));
50 std::nth_element(v.begin(), v.begin() + static_cast<long>(k), v.end());
51 return v[k];
52}
53
54// ---- Realistic scan sweep ----
55// A style is judged by how small it can be before a real reader can no longer
56// find and decode it in a photograph, compared with plain black on white.
57// Each scene places the symbol in a cluttered frame, tilts it (rotation and
58// keystone), adds glare, defocus and sensor noise, and runs the full reader
59// pipeline: ckey_find_borders_gray8 -> ckey_refine_quad_gray8 -> decode. The
60// sweep walks down a ladder of camera resolutions (pixels per module).
61
62constexpr double kLadder[] = {9.0, 7.0, 5.5, 4.5, 3.75, 3.0};
63constexpr int kLadderSteps = 6;
64constexpr int kMaxFrame = 2600; // keeps the sweep fast enough for browsers
65constexpr int kStartStep = 2; // the search starts mid-ladder (5.5 px per module) and walks out
66
67struct Scene { double rotation_deg, keystone; uint32_t seed; };
68constexpr Scene kScenes[] = {{8.0, 0.04, 0x53434e31u}, {-5.0, 0.0, 0x53434e32u}};
69
70bool scene_decodes(const ckey_scalable_symbol &sym, const ckey_style &draw, bool transparent, const ckey_rgb *ground_colour,
71 const ckey_backdrop *ground_image, const ckey_scan_model &m, double ppm, const Scene &sc,
72 const std::vector<uint8_t> &payload, bool &too_big) {
73 const ScalableLayout &L = scalable_layout(static_cast<int>(sym.version), static_cast<int>(sym.layout));
74 const int side = std::max(64, static_cast<int>(std::ceil(31.0 * ppm / L.pitch)));
75 const int margin = std::max(40, side < 900 ? side * 3 / 10 : side * 3 / 25); // dense symbols: a tighter crop
76 const int F = side + 2 * margin;
77 too_big = F > kMaxFrame;
78 if (too_big) return false;
79 ckey_raster_options o; ckey_raster_options_default(&o);
80 o.size_px = static_cast<uint32_t>(side); o.supersample = 2; o.transparent = transparent ? 1u : 0u;
81 std::vector<uint8_t> rgba(static_cast<size_t>(side) * side * 4); size_t need = 0;
82 if (ckey_render_scalable_raster(&sym, &draw, &o, rgba.data(), rgba.size(), &need) != CKEY_OK) return false;
83 const auto symbol = composite_luma(rgba, side, ground_colour, ground_image);
84 // The frame: a lit desk with clutter (deterministic per scene).
85 std::vector<double> frame(static_cast<size_t>(F) * F);
86 uint32_t seed = sc.seed;
87 const auto rnd = [&] { seed = seed * 1664525u + 1013904223u; return (seed >> 8) / 16777216.0; };
88 for (int y = 0; y < F; ++y) for (int x = 0; x < F; ++x) frame[static_cast<size_t>(y) * F + x] = 0.42 + 0.22 * x / F + 0.12 * y / F;
89 for (int k = 0; k < 10; ++k) {
90 const int x0 = static_cast<int>(rnd() * F), y0 = static_cast<int>(rnd() * F), w = 8 + static_cast<int>(rnd() * F / 4), h = 8 + static_cast<int>(rnd() * F / 4);
91 const double v = rnd();
92 for (int y = y0; y < std::min(F, y0 + h); ++y) for (int x = x0; x < std::min(F, x0 + w); ++x) frame[static_cast<size_t>(y) * F + x] = v;
93 }
94 // Symbol corners in the frame: rotated about the centre, top edge narrowed.
95 const double c = F / 2.0, half = side / 2.0, a = sc.rotation_deg * kTau / 360;
96 const double corner[4][2] = {{-1, -1}, {1, -1}, {1, 1}, {-1, 1}};
97 double dst[8];
98 for (int i = 0; i < 4; ++i) {
99 double x = corner[i][0] * half, y = corner[i][1] * half;
100 if (corner[i][1] < 0) x *= 1.0 - sc.keystone;
101 dst[2 * i] = c + x * std::cos(a) - y * std::sin(a); dst[2 * i + 1] = c + x * std::sin(a) + y * std::cos(a);
102 }
103 const double src[8] = {0, 0, static_cast<double>(side), 0, static_cast<double>(side), static_cast<double>(side), 0, static_cast<double>(side)};
104 double h[8];
105 if (!solve_homography(dst, src, h)) return false;
106 for (int y = 0; y < F; ++y) for (int x = 0; x < F; ++x) {
107 ImagePoint p;
108 if (!map_point(h, x + 0.5, y + 0.5, p)) continue;
109 const double u = p.x - 0.5, v = p.y - 0.5;
110 if (u < 0 || v < 0 || u >= side - 1 || v >= side - 1) continue;
111 const int iu = static_cast<int>(u), iv = static_cast<int>(v); const double fu = u - iu, fv = v - iv;
112 const auto at = [&](int X, int Y) { return symbol[static_cast<size_t>(Y) * side + X]; };
113 frame[static_cast<size_t>(y) * F + x] = (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;
114 }
115 for (auto &v : frame) v = v * (1 - m.glare) + m.glare;
116 gaussian_blur(frame, F, m.blur_px);
117 std::vector<uint8_t> gray(frame.size());
118 for (size_t i = 0; i < frame.size(); ++i) {
119 const double u1 = rnd() + 1e-9, u2 = rnd();
120 const double n = std::sqrt(-2 * std::log(u1)) * std::cos(kTau * u2) * m.noise;
121 gray[i] = static_cast<uint8_t>(std::lround(std::clamp(frame[i] + n, 0.0, 1.0) * 255));
122 }
123 double borders[6 * 8];
124 const size_t found = ckey_find_borders_gray8(gray.data(), F, F, F, borders, 6);
125 std::vector<uint8_t> out(65535);
126 for (size_t i = 0; i < found; ++i) {
127 double q[8]; size_t len = 0; uint32_t version = 0, layout = 0; ckey_diagnostics d{};
128 if (ckey_refine_quad_gray8(gray.data(), F, F, F, borders + 8 * i, CKEY_FIELD_SCALABLE, q) > 0 &&
129 ckey_decode_scalable_gray8(gray.data(), F, F, F, q, out.data(), out.size(), &len, &version, &layout, &d) == CKEY_OK)
130 return len == payload.size() && std::equal(payload.begin(), payload.end(), out.begin());
131 }
132 return false;
133}
134
135struct Sweep { double smallest_ppm = 0; int smallest_step = -1; uint32_t scenes = 0, passed = 0; };
136
137// Walks the ladder from large to small; the result is the smallest resolution
138// at which every scene decoded (and every larger one did too).
139Sweep sweep(const ckey_scalable_symbol &sym, const ckey_style &style, bool transparent, const ckey_rgb *colour,
140 const ckey_backdrop *image, const ckey_scan_model &m, const std::vector<uint8_t> &payload) {
141 Sweep r;
142 ckey_style draw = style; draw.policy = CKEY_POLICY_OVERRIDE; // test exactly this style
143 // 1 = every scene decoded, 0 = one failed, -1 = too large to simulate (treated as decoding
144 // when a smaller size decodes, since a larger image of the same symbol only gets easier).
145 const auto step_result = [&](int step) {
146 for (const Scene &sc : kScenes) {
147 bool too_big = false;
148 const bool ok = scene_decodes(sym, draw, transparent, colour, image, m, kLadder[step], sc, payload, too_big);
149 if (too_big) return -1;
150 ++r.scenes; r.passed += ok ? 1 : 0;
151 if (!ok) return 0;
152 }
153 return 1;
154 };
155 int start = kStartStep;
156 int res = step_result(start);
157 while (res == -1 && start + 1 < kLadderSteps) res = step_result(++start); // huge symbols: start smaller
158 if (res == 1) {
159 r.smallest_step = start; r.smallest_ppm = kLadder[start];
160 for (int step = start + 1; step < kLadderSteps && step_result(step) == 1; ++step) { r.smallest_step = step; r.smallest_ppm = kLadder[step]; }
161 } else {
162 for (int step = start - 1; step >= 0; --step) {
163 const int up = step_result(step);
164 if (up == 1) { r.smallest_step = step; r.smallest_ppm = kLadder[step]; break; }
165 }
166 }
167 return r;
168}
169
170// Plain black on white for the same symbol: the reference a style is held to.
171Sweep plain_sweep(const ckey_scalable_symbol &sym, const ckey_scan_model &m, const std::vector<uint8_t> &payload) {
172 // Cached per symbol and model: the reference does not depend on the style being judged.
173 static std::mutex lock;
174 static std::vector<std::pair<std::string, Sweep>> cache;
175 std::string key(reinterpret_cast<const char *>(sym.codeword), sym.codeword_length);
176 char tail[96]; std::snprintf(tail, sizeof(tail), "|%u|%u|%.3f|%.3f|%.3f|%.3f", sym.version, sym.layout, m.px_per_module, m.blur_px, m.noise, m.glare);
177 key += tail;
178 { std::lock_guard<std::mutex> g(lock); for (const auto &[k, v] : cache) if (k == key) return v; }
179 ckey_style plain; ckey_style_default(&plain);
180 const Sweep r = sweep(sym, plain, false, nullptr, nullptr, m, payload);
181 std::lock_guard<std::mutex> g(lock);
182 if (cache.size() >= 16) cache.erase(cache.begin());
183 cache.push_back({key, r});
184 return r;
185}
186
187// Luma separation of marks vs gaps after compositing, at the ladder's top size.
188double separation_of(const ckey_scalable_symbol &sym, const ckey_style &style, bool transparent, const ckey_rgb *colour, const ckey_backdrop *image) {
189 const ScalableLayout &L = scalable_layout(static_cast<int>(sym.version), static_cast<int>(sym.layout));
190 const int side = std::clamp(static_cast<int>(std::ceil(31.0 * 6.0 / L.pitch)), 64, 1600);
191 ckey_style draw = style; draw.policy = CKEY_POLICY_OVERRIDE;
192 ckey_raster_options o; ckey_raster_options_default(&o);
193 o.size_px = static_cast<uint32_t>(side); o.supersample = 2; o.transparent = transparent ? 1u : 0u;
194 std::vector<uint8_t> rgba(static_cast<size_t>(side) * side * 4), mask(static_cast<size_t>(side) * side); size_t need = 0;
195 if (ckey_render_scalable_raster(&sym, &draw, &o, rgba.data(), rgba.size(), &need) != CKEY_OK) return 0;
196 ckey_style mask_style; ckey_style_default(&mask_style);
197 mask_style.modules = style.modules; mask_style.rings = style.rings; mask_style.rays = style.rays;
198 mask_style.logo_href = style.logo_href; mask_style.logo_radius = style.logo_radius; mask_style.logo_plate = style.logo_plate;
199 mask_style.policy = CKEY_POLICY_OVERRIDE;
200 ckey_raster_options mo = o; mo.transparent = 0; mo.format = CKEY_PIXELS_GRAY8; mo.materials = 0;
201 if (ckey_render_scalable_raster(&sym, &mask_style, &mo, mask.data(), mask.size(), &need) != CKEY_OK) return 0;
202 const auto luma = composite_luma(rgba, side, colour, image);
203 std::vector<double> marks, gaps;
204 for (size_t i = 0; i < luma.size(); i += 3) { if (mask[i] < 16) marks.push_back(luma[i]); else if (mask[i] > 239) gaps.push_back(luma[i]); }
205 const bool dark_marks = percentile(marks, 0.5) < percentile(gaps, 0.5);
206 return dark_marks ? percentile(gaps, 0.05) - percentile(marks, 0.95) : percentile(marks, 0.05) - percentile(gaps, 0.95);
207}
208
209const char *grade_name(int g) { return g >= 3 ? "excellent" : g == 2 ? "good" : g == 1 ? "marginal" : "fails"; }
210
211void strip_materials(ckey_style &st) {
212 st.ink_shade = st.ink; st.background_edge = st.background;
213 st.bevel = st.shadow = st.texture = 0;
214}
215
216} // namespace
217
218extern "C" {
219
220void ckey_scan_model_default(ckey_scan_model *m) {
221 if (!m) return;
222 *m = ckey_scan_model{};
223 m->abi_version = CKEY_ABI_VERSION; m->px_per_module = 8.0; m->blur_px = 0.9; m->noise = 0.03; m->glare = 0.2; m->trials = 3;
224}
225
226ckey_status ckey_certify_scalable_style(const ckey_scalable_symbol *s, const ckey_style *style, uint32_t transparent,
227 const ckey_backdrop *backdrop, const ckey_scan_model *model, ckey_style *out_style,
228 ckey_certification *out) {
229 if (!out) return CKEY_INVALID_ARGUMENT;
230 *out = ckey_certification{}; out->abi_version = CKEY_ABI_VERSION;
231 const auto refuse = [&](const char *why) { std::snprintf(out->message, sizeof(out->message), "%s", why); return CKEY_INVALID_ARGUMENT; };
232 if (!s || s->abi_version != CKEY_ABI_VERSION || s->version < 1 || s->version > CKEY_SCALABLE_VERSION_COUNT || s->ecc_level > 3 ||
233 s->layout >= static_cast<uint32_t>(kScalableFamilies)) return refuse("certification needs a Profile 0 symbol");
234 const ScalableLayout &L = scalable_layout(static_cast<int>(s->version), static_cast<int>(s->layout));
235 if (s->codeword_length != L.codeword_bytes) return refuse("symbol codeword does not match its version");
236 ckey_scan_model m; ckey_scan_model_default(&m);
237 if (model) m = *model;
238 if (m.abi_version != CKEY_ABI_VERSION || !std::isfinite(m.px_per_module) || m.px_per_module < 2 || m.px_per_module > 40 ||
239 !std::isfinite(m.blur_px) || m.blur_px < 0 || m.blur_px > 10 || !std::isfinite(m.noise) || m.noise < 0 || m.noise > 0.5 ||
240 !std::isfinite(m.glare) || m.glare < 0 || m.glare > 0.9 || m.trials < 1 || m.trials > 16) return refuse("invalid scan model");
241 if (backdrop && (backdrop->colour_count > 16 || (backdrop->colour_count && !backdrop->colours) ||
242 (backdrop->rgba && (!backdrop->width || !backdrop->height || (backdrop->stride && backdrop->stride < static_cast<size_t>(backdrop->width) * 4)))))
243 return refuse("invalid backdrop");
244 ckey_style st; if (style) st = *style; else ckey_style_default(&st);
245 out->policy = st.policy;
246 const std::string structural = structural_problem(L, st);
247 if (!structural.empty()) return refuse(structural.c_str());
248 std::vector<uint8_t> payload(65535); size_t payload_len = 0; ckey_diagnostics d{};
249 if (ckey_decode_scalable(s->codeword, s->codeword_length, s->version, static_cast<ckey_scalable_layout>(s->layout), payload.data(), payload.size(), &payload_len, &d) != CKEY_OK)
250 return refuse("the symbol's own codeword does not decode");
251 payload.resize(payload_len);
252
253 std::vector<ckey_rgb> extra;
254 const bool has_backdrop = backdrop && (backdrop->colour_count || backdrop->rgba);
255 if (transparent && !has_backdrop) {
256 std::snprintf(out->message, sizeof(out->message), "background unknown: transparent output cannot be certified; pass the host backdrop");
257 return st.policy == CKEY_POLICY_OVERRIDE ? CKEY_OK : CKEY_INVALID_ARGUMENT;
258 }
259 if (transparent) {
260 // The symbol's own background is not drawn: rules test the backdrop.
261 if (backdrop->colour_count) { st.background = backdrop->colours[0]; st.background_edge = backdrop->colours[0]; }
262 for (uint32_t i = 0; i < backdrop->colour_count; ++i) extra.push_back(backdrop->colours[i]);
263 if (backdrop->rgba) { // the backdrop image's darkest and lightest pixels join the contrast rules
264 const size_t stride = backdrop->stride ? backdrop->stride : static_cast<size_t>(backdrop->width) * 4;
265 ckey_rgb dark{255, 255, 255}, light{0, 0, 0}; double dl = 2, ll = -1;
266 for (uint32_t y = 0; y < backdrop->height; y += std::max(1u, backdrop->height / 64)) for (uint32_t x = 0; x < backdrop->width; x += std::max(1u, backdrop->width / 64)) {
267 const uint8_t *p = backdrop->rgba + y * stride + static_cast<size_t>(x) * 4; const double a = p[3] / 255.0;
268 const ckey_rgb c{static_cast<uint8_t>(std::lround(255 * (1 - a) + a * p[0])), static_cast<uint8_t>(std::lround(255 * (1 - a) + a * p[1])), static_cast<uint8_t>(std::lround(255 * (1 - a) + a * p[2]))};
269 const double l = relative_luminance(c);
270 if (l < dl) { dl = l; dark = c; } if (l > ll) { ll = l; light = c; }
271 }
272 extra.push_back(dark); extra.push_back(light);
273 if (!backdrop->colour_count) { st.background = st.background_edge = dark; }
274 }
275 }
276 ckey_style work = st;
277 double contrast = 0, load = 0;
278 std::string advice;
279 // Adaptive repairs a hard failure up front; anything else is left for the sweep to judge.
280 if (st.policy == CKEY_POLICY_ADAPTIVE && !safety_problem(L, *s, work, extra, contrast, load).empty())
281 out->adjustments |= adapt_style(L, *s, work, extra);
282 std::string problem = safety_problem(L, *s, work, extra, contrast, load, &advice);
283 // Grounds the symbol is judged on: its own background, or every backdrop.
284 std::vector<std::pair<const ckey_rgb *, const ckey_backdrop *>> grounds;
285 if (!transparent) grounds.push_back({nullptr, nullptr});
286 else {
287 for (uint32_t i = 0; i < backdrop->colour_count; ++i) grounds.push_back({&backdrop->colours[i], nullptr});
288 if (backdrop->rgba && backdrop->width && backdrop->height) grounds.push_back({nullptr, backdrop});
289 }
290 const auto evaluate = [&](const ckey_style &w, Sweep &worst, double &sep) {
291 worst = Sweep{}; worst.smallest_step = kLadderSteps; sep = 1.0; bool first = true;
292 for (const auto &[colour, image] : grounds) {
293 const Sweep r = sweep(*s, w, transparent != 0, colour, image, m, payload);
294 worst.scenes += r.scenes; worst.passed += r.passed;
295 if (first || r.smallest_step < worst.smallest_step) { worst.smallest_step = r.smallest_step; worst.smallest_ppm = r.smallest_ppm; }
296 first = false;
297 sep = std::min(sep, separation_of(*s, w, transparent != 0, colour, image));
298 }
299 };
300 const Sweep plain = plain_sweep(*s, m, payload);
301 const auto grade_of = [&](const Sweep &r) {
302 if (r.smallest_step < 0 || r.smallest_ppm > m.px_per_module + 1e-9) return 0;
303 if (plain.smallest_step < 0) return 2; // the reference itself fails: judge by the style alone
304 const int behind = plain.smallest_step - r.smallest_step; // ladder steps larger than plain needs
305 return behind <= 0 ? 3 : behind == 1 ? 2 : behind == 2 ? 1 : 0;
306 };
307 Sweep result; double separation = 1.0;
308 evaluate(work, result, separation);
309 int grade = grade_of(result);
310 if (st.policy == CKEY_POLICY_ADAPTIVE && (!problem.empty() || grade < 2)) {
311 out->adjustments |= adapt_style(L, *s, work, extra); // now also the advisory contrast repairs
312 strip_materials(work); out->adjustments |= CKEY_ADJUST_MATERIALS;
313 problem = safety_problem(L, *s, work, extra, contrast, load, &advice);
314 evaluate(work, result, separation);
315 grade = grade_of(result);
316 }
317 out->min_contrast = contrast; out->logo_load = load;
318 out->trials = result.scenes; out->trials_passed = result.passed; out->feature_separation = separation;
319 out->certified = problem.empty() && grade >= 2 ? 1u : 0u;
320 char sweep_text[320];
321 const auto ppm_text = [](const Sweep &r) { char b[48]; if (r.smallest_step < 0) std::snprintf(b, sizeof(b), "not even at %.0f px per module", kLadder[0]); else std::snprintf(b, sizeof(b), "down to %.2g px per module", r.smallest_ppm); return std::string(b); };
322 std::snprintf(sweep_text, sizeof(sweep_text), "%s scan margin: found and decoded in modelled photos %s; plain black on white %s",
323 grade_name(grade), ppm_text(result).c_str(), ppm_text(plain).c_str());
324 if (!advice.empty()) { std::strncat(sweep_text, "; note: ", sizeof(sweep_text) - std::strlen(sweep_text) - 1); std::strncat(sweep_text, advice.c_str(), sizeof(sweep_text) - std::strlen(sweep_text) - 1); }
325 std::string text = out->certified ? std::string("certified (") + grade_name(grade) + "); " + sweep_text
326 : !problem.empty() ? "not certified: " + problem + "; " + sweep_text
327 : std::string("not certified: ") + sweep_text;
328 if (out->adjustments) {
329 text += "; adaptive changes:";
330 if (out->adjustments & CKEY_ADJUST_INK) text += " ink";
331 if (out->adjustments & CKEY_ADJUST_BACKGROUND) text += " background";
332 if (out->adjustments & CKEY_ADJUST_ACCENTS) text += " accents";
333 if (out->adjustments & CKEY_ADJUST_EFFECTS) text += " depth";
334 if (out->adjustments & CKEY_ADJUST_LOGO) text += " logo";
335 if (out->adjustments & CKEY_ADJUST_MATERIALS) text += " materials";
336 }
337 if (st.logo_href && *st.logo_href) text += "; the logo image itself is not modelled";
338 std::snprintf(out->message, sizeof(out->message), "%s", text.c_str());
339 if (out_style && st.policy == CKEY_POLICY_ADAPTIVE && out->certified) {
340 // Hand back the repaired style with the caller's own background
341 // fields restored for transparent output (they are not drawn).
342 if (transparent && style) { work.background = style->background; work.background_edge = style->background_edge; }
343 work.policy = CKEY_POLICY_STRICT; *out_style = work;
344 } else if (out_style && st.policy != CKEY_POLICY_ADAPTIVE) *out_style = style ? *style : st;
345 if (st.policy == CKEY_POLICY_OVERRIDE) return CKEY_OK;
346 return out->certified ? CKEY_OK : CKEY_INVALID_ARGUMENT;
347}
348
349} // extern "C"