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

print_sim.h

v0.1.0-build.85 Latest Commit ede38f5

Size
4.4 KB
Lines
74
Language
cpp
SHA-256
a7cdfc800677…
View on GitHub

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

1// Print + camera simulation for CKey symbols (tests and tools only).
2// A symbol SVG is printed at a device resolution (binary ink dots, no grey),
3// ink spreads by a radius and blurs into the paper, then a camera with a
4// given resolution (pixels per millimetre), optical blur, and noise views it.
5// The output is the grayscale frame a decoder would see.
6#ifndef CKEY_TESTS_PRINT_SIM_H
7#define CKEY_TESTS_PRINT_SIM_H
8
9#include "svg_raster.h"
10
11#include <cmath>
12#include <cstdint>
13#include <vector>
14
15namespace printsim {
16
17struct Printer { double dpi; double spread_mm; double blur_mm; double compensate_mm = 0; }; // ink spread (dot gain), paper wicking, pre-shrink of ink
18struct Camera { double px_per_mm; double blur_px; double noise; }; // optics blur sigma; noise amplitude (grey levels)
19
20inline void gaussian(std::vector<double> &img, int w, int h, double sigma) {
21 if (sigma < 0.05) return;
22 const int r = static_cast<int>(std::ceil(3 * sigma)); std::vector<double> k(2 * r + 1); double sum = 0;
23 for (int i = -r; i <= r; ++i) sum += k[i + r] = std::exp(-0.5 * i * i / (sigma * sigma));
24 for (auto &v : k) v /= sum;
25 std::vector<double> tmp(img.size());
26 for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) { double a = 0; for (int i = -r; i <= r; ++i) a += k[i + r] * img[static_cast<size_t>(y) * w + std::clamp(x + i, 0, w - 1)]; tmp[static_cast<size_t>(y) * w + x] = a; }
27 for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) { double a = 0; for (int i = -r; i <= r; ++i) a += k[i + r] * tmp[static_cast<size_t>(std::clamp(y + i, 0, h - 1)) * w + x]; img[static_cast<size_t>(y) * w + x] = a; }
28}
29
30// marks_are_ink: print the symbol's marks in ink on white paper (dark-on-
31// light). Otherwise the background is inked and the marks are bare paper.
32// Returns the camera frame; side_px receives its size. The symbol's 31-unit
33// canvas is printed at width_mm.
34inline std::vector<uint8_t> print_and_capture(const std::string &svg_text, double width_mm, const Printer &pr, const Camera &cam,
35 bool marks_are_ink, uint32_t seed, int &side_px) {
36 const auto svg = svgtest::parse(svg_text);
37 const int dev = std::max(8, static_cast<int>(std::lround(width_mm / 25.4 * pr.dpi)));
38 const auto cover = svgtest::rasterize(svg, dev, dev, 3, 0, 255);
39 std::vector<uint8_t> ink(cover.size());
40 for (size_t i = 0; i < cover.size(); ++i) ink[i] = marks_are_ink ? cover[i] >= 128 : cover[i] < 128; // binary device dots
41 if (pr.compensate_mm > 0) { // print-side compensation: erode ink, keeping at least one dot of every mark
42 std::vector<uint8_t> paper_mask(ink.size()); for (size_t i = 0; i < ink.size(); ++i) paper_mask[i] = !ink[i];
43 const auto to_paper = svgtest::distance_to_set(paper_mask, dev, dev);
44 const double c = pr.compensate_mm / 25.4 * pr.dpi;
45 for (size_t i = 0; i < ink.size(); ++i) if (ink[i] && to_paper[i] <= c) ink[i] = 0;
46 }
47 // Dot gain: ink spreads outward by spread_mm.
48 const auto dist = svgtest::distance_to_set(ink, dev, dev);
49 const double spread_px = pr.spread_mm / 25.4 * pr.dpi;
50 std::vector<double> paper(cover.size());
51 for (size_t i = 0; i < paper.size(); ++i) paper[i] = dist[i] <= spread_px ? 28.0 : 236.0;
52 gaussian(paper, dev, dev, pr.blur_mm / 25.4 * pr.dpi);
53 // Camera: area-sample the printed sheet at px_per_mm, then optics + noise.
54 side_px = std::max(8, static_cast<int>(std::lround(width_mm * cam.px_per_mm)));
55 std::vector<double> frame(static_cast<size_t>(side_px) * side_px);
56 const double scale = static_cast<double>(dev) / side_px;
57 for (int y = 0; y < side_px; ++y) for (int x = 0; x < side_px; ++x) {
58 const double x0 = x * scale, y0 = y * scale; double a = 0; int n = 0;
59 for (double yy = y0; yy < y0 + scale; yy += std::max(0.5, scale / 4)) for (double xx = x0; xx < x0 + scale; xx += std::max(0.5, scale / 4)) {
60 a += paper[static_cast<size_t>(std::min(dev - 1, static_cast<int>(yy))) * dev + std::min(dev - 1, static_cast<int>(xx))]; ++n;
61 }
62 frame[static_cast<size_t>(y) * side_px + x] = a / n;
63 }
64 gaussian(frame, side_px, side_px, cam.blur_px);
65 std::vector<uint8_t> out(frame.size());
66 for (size_t i = 0; i < out.size(); ++i) {
67 seed = seed * 1664525u + 1013904223u;
68 out[i] = static_cast<uint8_t>(std::clamp(frame[i] + cam.noise * (((seed >> 8) & 0xffff) / 32768.0 - 1.0), 0.0, 255.0));
69 }
70 return out;
71}
72
73} // namespace printsim
74#endif