code wiki / (root) / nx_phototwin.nx

nx_phototwin.nx source

↩ module page · 509 lines · 26393 B

1// nx_phototwin.nx -- THE PHOTOGRAPH-TO-SUBSTRATE TWIN (2026-08-24, /compare/phototwin PT2 PT3 PT7). 2// license_tier: ORIGINAL No hw writes (Rule 26). 3// 4// nx_phototwin report <image> <image.icc> <substrate.icc> [conf] gamut-coverage report of a photograph 5// nx_phototwin proof <image> <image.icc> <substrate.icc> <outdir> [conf] twin.png (chroma-clipped) + heat.png (dE00 map) + proof.txt 6// nx_phototwin wash <ledger.tsv> <sku> [conf] wash-fastness ledger verdict for one SKU 7// nx_phototwin bench <ledger.tsv> [conf] the swatch bench over EVERY sku, refusing partial coverage 8// EXIT: 0 OK | 1 REFUSED (tolerance exceeded, wash failed) | 2 usage | 3 UNOBSERVABLE (undecodable image, unreadable 9// profile or conf, no ledger rows) -- the third state is never a pass and never a failure of the subject. 10// 11// TWO PROFILES, TWO ROLES. Pixels are interpreted through the IMAGE profile (RGB -> TRC -> matrix -> XYZ(D50) -> CIELAB, 12// one ruler, nx_colorsci_lib) and tested against the SUBSTRATE profile's gamut -- EXACTLY for a matrix/TRC profile 13// (linear RGB through its inverse in [0,1]; the cube is the gamut). Out-of-gamut colours are chroma-clipped at constant 14// L and hue (the twin), then rendered back through the image profile so the soft proof displays in the image's own 15// space. dE00 between photograph and twin is the proof: mean and max against knowledge/colorsci.conf (printing.org bar). 16// MEASURED 2026-08-24 before this shape: one profile for both roles made the narrow-substrate control VACUOUS (the 17// image's codes were read as substrate codes and were inside by construction), and a segment-maxima descriptor read 18// 18 percent of an sRGB photograph outside sRGB -- both retired here. The descriptor stays in the lib for MEASURED 19// swatch gamuts (PT7), which have no matrix. 20// The mapping is MEMOISED PER EXACT 24-BIT COLOUR (a photograph has ~1e5-1e6 distinct colours, never 4e6); the 21// unique-colour count is printed so the cost is on the record. 22// SUBSTRATE HONESTY: until a swatch bench measures the pigment-on-Supima profile, a proof whose substrate is the image 23// profile itself is a positive CONTROL and says so in proof.txt. 24// WASH LEDGER (knowledge/phototwin_wash.tsv): rows swatch sku blank line cycle L a b, Lab measured before (cycle 0) 25// and after declared ISO 105-C06 cycles; dE00 per cycle against cycle 0; a SKU with no rows is UNMEASURED (exit 3). 26import "nx_syscalls.nx" 27import "nx_colorsci_lib.nx" 28import "nx_img_to_rgb.nx" 29import "nx_png_write.nx" 30 31const PT_EXIT_OK: i64 = 0 32const PT_EXIT_REFUSED: i64 = 1 33const PT_EXIT_USAGE: i64 = 2 34const PT_EXIT_UNOBSERVABLE: i64 = 3 35const PT_CONF_DEFAULT: *u8 = "knowledge/colorsci.conf" 36const PT_CHANNELS: i64 = 3 37const PT_BYTE: i64 = 256 38const PT_BYTE_MAX: i64 = 255 39const PT_MEMO_SLOTS: i64 = 2097152 // 2^21 slots: above any photograph's distinct-colour count 40const PT_MEMO_MASK: i64 = 2097151 41const PT_HASH_KNUTH: i64 = 2654435761 // Knuth multiplicative hashing constant (TAOCP vol 3, 6.4) 42const PT_HASH_SHIFT: i64 = 11 // 32 - 21: take the top bits of the 32-bit product 43const PT_U32_MASK: i64 = 4294967295 44const PT_MEMO_EMPTY: i64 = 0 - 1 45const PT_MEMO_ARRAYS: i64 = 4 // key, permil, sector-or-codes, de 46const PT_SLOT: i64 = 8 47const PT_PATH_CAP: i64 = 4096 48const PT_LINE_CAP: i64 = 4096 49const PT_PERMIL: i64 = 1000 50const PT_DEG_FULL: i64 = 360 51const PT_TAB: i64 = 9 52const PT_NL: i64 = 10 53const PT_HASH: i64 = 35 54const PT_LF_SWATCH: i64 = 0 55const PT_LF_SKU: i64 = 1 56const PT_LF_CYCLE: i64 = 4 57const PT_LF_L: i64 = 5 58const PT_LF_A: i64 = 6 59const PT_LF_B: i64 = 7 60const PT_LEDGER_MAX_ROWS: i64 = 65536 61const PT_ROW_STRIDE: i64 = 8 // swatch_off swatch_len sku_off sku_len cycle L a b 62const PT_MIN_CYCLES: i64 = 2 // a swatch proves fastness only with cycle 0 AND at least one later cycle 63const PT_HEAT_MAX: i64 = 255 64const PT_MODE_0644: i64 = 420 65const PT_R_PIXELS: i64 = 0 66const PT_R_UNIQUE: i64 = 1 67const PT_R_INSIDE: i64 = 2 68const PT_R_OUTSIDE: i64 = 3 69const PT_R_WORST_PERMIL: i64 = 4 70const PT_R_SECT0: i64 = 8 71const PT_STATS_SLOTS: i64 = 4 72const PT_OUT_SLOTS: i64 = 8 73 74func pt_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 75func pt_wn(v: i64) -> i64 { 76 var m: i64 = v 77 if m < 0 { pt_w("-" as *u8); m = 0 - m } 78 let t: *u8 = sys_mmap(32) 79 var k: i64 = 0 80 if m == 0 { t[0] = 48 as u8; k = 1 } 81 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 82 let o: *u8 = sys_mmap(32) 83 var i: i64 = 0 84 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 85 sys_write(1, o, k) 86 return 0 87} 88func pt_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var p: i64 = o; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } d[p] = 0 as u8; return p } 89func pt_catn(d: *u8, o: i64, v: i64) -> i64 { 90 var m: i64 = v 91 var p: i64 = o 92 if m < 0 { d[p] = 45 as u8; p = p + 1; m = 0 - m } 93 let t: *u8 = sys_mmap(32) 94 var k: i64 = 0 95 if m == 0 { t[0] = 48 as u8; k = 1 } 96 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 97 var i: i64 = 0 98 while i < k { d[p] = t[k - 1 - i]; p = p + 1; i = i + 1 } 99 d[p] = 0 as u8 100 return p 101} 102func pt_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 103func pt_write_text(path: *u8, s: *u8, n: i64) -> i64 { 104 let fd: i64 = sys_openat_wr(path, PT_MODE_0644) 105 if fd < 0 { return 0 - 1 } 106 let wr: i64 = sys_write(fd, s, n) 107 sys_close(fd) 108 return wr 109} 110// conf: a missing key is named and the run is UNOBSERVABLE 111func pt_conf(path: *u8, key: *u8, missing: *i64) -> i64 { 112 let v: i64 = cs_conf_int(path, key) 113 if v == CS_CONF_MISS { pt_w(" CONF-MISSING key=" as *u8); pt_w(key); pt_w(" in " as *u8); pt_w(path); pt_w("\n" as *u8); missing[0] = missing[0] + 1; return 0 } 114 return v 115} 116 117// ---- memo: exact 24-bit colour -> (permil, sector or mapped codes, dE00 micro) ---- 118func pt_memo_alloc() -> *i64 { 119 let m: *i64 = sys_mmap(PT_MEMO_SLOTS * PT_SLOT * PT_MEMO_ARRAYS) as *i64 120 var i: i64 = 0 121 while i < PT_MEMO_SLOTS { m[i] = PT_MEMO_EMPTY; i = i + 1 } 122 return m 123} 124func pt_memo_arr(m: *i64, k: i64) -> *i64 { return (m as i64 + PT_MEMO_SLOTS * PT_SLOT * k) as *i64 } 125func pt_memo_slot(m: *i64, key: i64) -> i64 { 126 var h: i64 = ((key * PT_HASH_KNUTH) & PT_U32_MASK) >> PT_HASH_SHIFT 127 var probes: i64 = 0 128 while probes < PT_MEMO_SLOTS { 129 let k: i64 = m[h] 130 if k == key { return h } 131 if k == PT_MEMO_EMPTY { return h } 132 h = (h + 1) & PT_MEMO_MASK 133 probes = probes + 1 134 } 135 return PT_MEMO_EMPTY 136} 137 138// pt_gamut_report: coverage of the photograph (through img) against the substrate's EXACT gamut (sub). 139// out: pixels unique inside outside worst_permil, then per-hue-sector outside counts at out[PT_R_SECT0 + s]. 140func pt_gamut_report(cs: *i64, img: *i64, sub: *i64, sect: *i64, rgb: *u8, w: i64, h: i64, memo: *i64, out: *i64) -> i64 { 141 let lab: *i64 = sys_mmap(PT_SLOT * PT_CHANNELS) as *i64 142 let sec: *i64 = sys_mmap(PT_SLOT * PT_CHANNELS) as *i64 143 let npix: i64 = w * h 144 let perm: *i64 = pt_memo_arr(memo, 1) 145 let hsec: *i64 = pt_memo_arr(memo, 2) 146 var unique: i64 = 0 147 var inside: i64 = 0 148 var outside: i64 = 0 149 var worst: i64 = 0 150 var i: i64 = 0 151 while i < npix { 152 let r: i64 = rgb[i * PT_CHANNELS] as i64 153 let gg: i64 = rgb[i * PT_CHANNELS + 1] as i64 154 let b: i64 = rgb[i * PT_CHANNELS + 2] as i64 155 let key: i64 = (r * PT_BYTE + gg) * PT_BYTE + b 156 let s: i64 = pt_memo_slot(memo, key) 157 if s == PT_MEMO_EMPTY { return 0 - 1 } 158 if memo[s] != key { 159 memo[s] = key 160 cs_prof_rgb_to_lab(cs, img, r, gg, b, lab) 161 perm[s] = cs_prof_gamut_permil(cs, sub, lab[0], lab[1], lab[2]) 162 cs_gbd_sector(cs, sect, lab[0], lab[1], lab[2], sec) 163 hsec[s] = sec[1] 164 unique = unique + 1 165 } 166 let pm: i64 = perm[s] 167 if pm <= PT_PERMIL { inside = inside + 1 } 168 else { outside = outside + 1; out[PT_R_SECT0 + hsec[s]] = out[PT_R_SECT0 + hsec[s]] + 1; if pm > worst { worst = pm } } 169 i = i + 1 170 } 171 out[PT_R_PIXELS] = npix 172 out[PT_R_UNIQUE] = unique 173 out[PT_R_INSIDE] = inside 174 out[PT_R_OUTSIDE] = outside 175 out[PT_R_WORST_PERMIL] = worst 176 return unique 177} 178func pt_print_report(out: *i64, hs: i64) -> i64 { 179 pt_w(" pixels=" as *u8); pt_wn(out[PT_R_PIXELS]); pt_w(" unique_colours=" as *u8); pt_wn(out[PT_R_UNIQUE]) 180 pt_w(" inside=" as *u8); pt_wn(out[PT_R_INSIDE]); pt_w(" outside=" as *u8); pt_wn(out[PT_R_OUTSIDE]) 181 if out[PT_R_INSIDE] + out[PT_R_OUTSIDE] == out[PT_R_PIXELS] { pt_w(" partition=SUMS" as *u8) } else { pt_w(" partition=LEAK" as *u8) } 182 pt_w(" out_permil=" as *u8) 183 if out[PT_R_PIXELS] > 0 { pt_wn((out[PT_R_OUTSIDE] * PT_PERMIL) / out[PT_R_PIXELS]) } else { pt_wn(0) } 184 pt_w(" worst_permil_of_gamut=" as *u8); pt_wn(out[PT_R_WORST_PERMIL]); pt_w("\n" as *u8) 185 var s: i64 = 0 186 while s < hs { 187 if out[PT_R_SECT0 + s] > 0 { 188 pt_w(" sector hue_deg=" as *u8); pt_wn((s * PT_DEG_FULL) / hs); pt_w("-" as *u8); pt_wn(((s + 1) * PT_DEG_FULL) / hs) 189 pt_w(" out_pixels=" as *u8); pt_wn(out[PT_R_SECT0 + s]); pt_w("\n" as *u8) 190 } 191 s = s + 1 192 } 193 return 0 194} 195 196// pt_gamut_map: chroma-clip every out-of-gamut colour against the substrate (memoised), render the twin through the 197// image profile, and record dE00 per pixel. stats: [0] sum dE micro [1] max dE micro [2] pixels above max_tol [3] moved 198func pt_gamut_map(cs: *i64, img: *i64, sub: *i64, rgb: *u8, w: i64, h: i64, memo: *i64, iters: i64, twin: *u8, de: *i64, max_tol_micro: i64, stats: *i64) -> i64 { 199 let lab: *i64 = sys_mmap(PT_SLOT * PT_CHANNELS) as *i64 200 let mapped: *i64 = sys_mmap(PT_SLOT * PT_CHANNELS) as *i64 201 let codes: *i64 = sys_mmap(PT_SLOT * PT_CHANNELS) as *i64 202 let mcode: *i64 = pt_memo_arr(memo, 2) 203 let mde: *i64 = pt_memo_arr(memo, 3) 204 let npix: i64 = w * h 205 var i: i64 = 0 206 while i < PT_MEMO_SLOTS { mde[i] = PT_MEMO_EMPTY; i = i + 1 } 207 var sum: i64 = 0 208 var mx: i64 = 0 209 var above: i64 = 0 210 var moved: i64 = 0 211 i = 0 212 while i < npix { 213 let r: i64 = rgb[i * PT_CHANNELS] as i64 214 let gg: i64 = rgb[i * PT_CHANNELS + 1] as i64 215 let b: i64 = rgb[i * PT_CHANNELS + 2] as i64 216 let key: i64 = (r * PT_BYTE + gg) * PT_BYTE + b 217 let s: i64 = pt_memo_slot(memo, key) 218 if s == PT_MEMO_EMPTY { return 0 - 1 } 219 if memo[s] != key { memo[s] = key; mde[s] = PT_MEMO_EMPTY } 220 if mde[s] == PT_MEMO_EMPTY { 221 cs_prof_rgb_to_lab(cs, img, r, gg, b, lab) 222 let before: i64 = cs_prof_gamut_clip(cs, sub, lab[0], lab[1], lab[2], iters, mapped) 223 var dmicro: i64 = 0 224 var packed: i64 = key 225 if before > PT_PERMIL { 226 dmicro = fq_to_micro(cs_de2000(cs, lab[0], lab[1], lab[2], mapped[0], mapped[1], mapped[2])) 227 cs_prof_lab_to_rgb(cs, img, mapped[0], mapped[1], mapped[2], codes) 228 packed = (codes[0] * PT_BYTE + codes[1]) * PT_BYTE + codes[2] 229 } 230 mcode[s] = packed 231 mde[s] = dmicro 232 } 233 let pk: i64 = mcode[s] 234 twin[i * PT_CHANNELS] = (pk / (PT_BYTE * PT_BYTE)) as u8 235 twin[i * PT_CHANNELS + 1] = ((pk / PT_BYTE) % PT_BYTE) as u8 236 twin[i * PT_CHANNELS + 2] = (pk % PT_BYTE) as u8 237 let d: i64 = mde[s] 238 de[i] = d 239 sum = sum + d 240 if d > mx { mx = d } 241 if d > max_tol_micro { above = above + 1 } 242 if pk != key { moved = moved + 1 } 243 i = i + 1 244 } 245 stats[0] = sum 246 stats[1] = mx 247 stats[2] = above 248 stats[3] = moved 249 return 0 250} 251 252// pt_soft_proof: twin.png + heat.png + proof.txt; returns the exit code (0 OK, 1 REFUSED, 3 UNOBSERVABLE) 253func pt_soft_proof(cs: *i64, img: *i64, sub: *i64, rgb: *u8, w: i64, h: i64, memo: *i64, outdir: *u8, conf: *u8, missing: *i64, control: i64) -> i64 { 254 let iters: i64 = pt_conf(conf, "clip_iters" as *u8, missing) 255 let mean_tol: i64 = pt_conf(conf, "soft_proof_mean_tol_de00_micro" as *u8, missing) 256 let max_tol: i64 = pt_conf(conf, "soft_proof_max_tol_de00_micro" as *u8, missing) 257 let heat_fs: i64 = pt_conf(conf, "heat_full_scale_de00_micro" as *u8, missing) 258 if missing[0] > 0 { return PT_EXIT_UNOBSERVABLE } 259 let npix: i64 = w * h 260 let twin: *u8 = sys_mmap(npix * PT_CHANNELS) 261 let de: *i64 = sys_mmap(npix * PT_SLOT) as *i64 262 let stats: *i64 = sys_mmap(PT_SLOT * PT_STATS_SLOTS) as *i64 263 if pt_gamut_map(cs, img, sub, rgb, w, h, memo, iters, twin, de, max_tol, stats) < 0 { pt_w(" MEMO-FULL: more distinct colours than the memo holds\n" as *u8); return PT_EXIT_UNOBSERVABLE } 264 let heat: *u8 = sys_mmap(npix * PT_CHANNELS) 265 var i: i64 = 0 266 while i < npix { 267 var v: i64 = 0 268 if heat_fs > 0 { v = (de[i] * PT_HEAT_MAX) / heat_fs } 269 if v > PT_HEAT_MAX { v = PT_HEAT_MAX } 270 heat[i * PT_CHANNELS] = v as u8 271 heat[i * PT_CHANNELS + 1] = v as u8 272 heat[i * PT_CHANNELS + 2] = v as u8 273 i = i + 1 274 } 275 let p1: *u8 = sys_mmap(PT_PATH_CAP) 276 var o: i64 = pt_cat(p1, 0, outdir) 277 o = pt_cat(p1, o, "/twin.png" as *u8) 278 let p2: *u8 = sys_mmap(PT_PATH_CAP) 279 o = pt_cat(p2, 0, outdir) 280 o = pt_cat(p2, o, "/heat.png" as *u8) 281 let p3: *u8 = sys_mmap(PT_PATH_CAP) 282 o = pt_cat(p3, 0, outdir) 283 o = pt_cat(p3, o, "/proof.txt" as *u8) 284 let w1: i64 = nx_png_write_rgb(p1, twin, w, h) 285 let w2: i64 = nx_png_write_rgb(p2, heat, w, h) 286 var mean: i64 = 0 287 if npix > 0 { mean = stats[0] / npix } 288 var verdict: *u8 = "PROOF-OK" as *u8 289 var rc: i64 = PT_EXIT_OK 290 if mean > mean_tol { verdict = "PROOF-REFUSED-MEAN" as *u8; rc = PT_EXIT_REFUSED } 291 if stats[1] > max_tol { verdict = "PROOF-REFUSED-MAX" as *u8; rc = PT_EXIT_REFUSED } 292 let txt: *u8 = sys_mmap(PT_LINE_CAP) 293 var t: i64 = pt_cat(txt, 0, "pixels=" as *u8); t = pt_catn(txt, t, npix) 294 t = pt_cat(txt, t, " moved=" as *u8); t = pt_catn(txt, t, stats[3]) 295 t = pt_cat(txt, t, " mean_de00_micro=" as *u8); t = pt_catn(txt, t, mean) 296 t = pt_cat(txt, t, " max_de00_micro=" as *u8); t = pt_catn(txt, t, stats[1]) 297 t = pt_cat(txt, t, " above_max_tol=" as *u8); t = pt_catn(txt, t, stats[2]) 298 t = pt_cat(txt, t, " mean_tol_micro=" as *u8); t = pt_catn(txt, t, mean_tol) 299 t = pt_cat(txt, t, " max_tol_micro=" as *u8); t = pt_catn(txt, t, max_tol) 300 t = pt_cat(txt, t, " twin_png=" as *u8); t = pt_catn(txt, t, w1) 301 t = pt_cat(txt, t, " heat_png=" as *u8); t = pt_catn(txt, t, w2) 302 if control == 1 { t = pt_cat(txt, t, " substrate=CONTROL-image-profile-is-the-substrate-not-a-measured-one" as *u8) } else { t = pt_cat(txt, t, " substrate=PROFILE-exact-matrix-gamut" as *u8) } 303 t = pt_cat(txt, t, "\nverdict=" as *u8); t = pt_cat(txt, t, verdict); t = pt_cat(txt, t, "\n" as *u8) 304 pt_write_text(p3, txt, t) 305 sys_write(1, txt, t) 306 return rc 307} 308 309// ---- wash ledger ---- 310func pt_field(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 { 311 var p: i64 = s 312 var f: i64 = 0 313 while f < idx { 314 var go: i64 = 1 315 while go == 1 { if p >= e { go = 0 } else { if (buf[p] as i64) == PT_TAB { p = p + 1; go = 0 } else { p = p + 1 } } } 316 f = f + 1 317 } 318 if p >= e { box[0] = 0; box[1] = 0; return 0 } 319 var q: i64 = p 320 var g2: i64 = 1 321 while g2 == 1 { if q >= e { g2 = 0 } else { if (buf[q] as i64) == PT_TAB { g2 = 0 } else { q = q + 1 } } } 322 box[0] = p 323 box[1] = q - p 324 return 1 325} 326func pt_span_eq(buf: *u8, off: i64, len: i64, s: *u8) -> i64 { 327 var i: i64 = 0 328 while i < len { if s[i] == (0 as u8) { return 0 } if buf[off + i] != s[i] { return 0 } i = i + 1 } 329 if s[len] != (0 as u8) { return 0 } 330 return 1 331} 332func pt_span_same(buf: *u8, a: i64, an: i64, b: i64, bn: i64) -> i64 { 333 if an != bn { return 0 } 334 var i: i64 = 0 335 while i < an { if buf[a + i] != buf[b + i] { return 0 } i = i + 1 } 336 return 1 337} 338func pt_ledger_load(buf: *u8, n: i64, rows: *i64, bad: *i64) -> i64 { 339 let box: *i64 = sys_mmap(PT_SLOT * 2) as *i64 340 var cnt: i64 = 0 341 var p: i64 = 0 342 while p < n { 343 var e: i64 = p 344 while e < n { if (buf[e] as i64) == PT_NL { break } e = e + 1 } 345 if e > p { if (buf[p] as i64) != PT_HASH { 346 if cnt < PT_LEDGER_MAX_ROWS { 347 var ok: i64 = 1 348 if pt_field(buf, p, e, PT_LF_B, box) == 0 { ok = 0 } 349 if ok == 1 { 350 let base: i64 = cnt * PT_ROW_STRIDE 351 pt_field(buf, p, e, PT_LF_SWATCH, box); rows[base] = box[0]; rows[base + 1] = box[1] 352 pt_field(buf, p, e, PT_LF_SKU, box); rows[base + 2] = box[0]; rows[base + 3] = box[1] 353 pt_field(buf, p, e, PT_LF_CYCLE, box); rows[base + 4] = fq_parse_micro(buf, box[0], box[1]) / FQ_MICRO 354 pt_field(buf, p, e, PT_LF_L, box); rows[base + 5] = fq_from_micro(fq_parse_micro(buf, box[0], box[1])) 355 pt_field(buf, p, e, PT_LF_A, box); rows[base + 6] = fq_from_micro(fq_parse_micro(buf, box[0], box[1])) 356 pt_field(buf, p, e, PT_LF_B, box); rows[base + 7] = fq_from_micro(fq_parse_micro(buf, box[0], box[1])) 357 cnt = cnt + 1 358 } else { bad[0] = bad[0] + 1 } 359 } else { bad[0] = bad[0] + 1 } 360 } } 361 p = e + 1 362 } 363 return cnt 364} 365// pt_wash_ledger: verdict for one sku (or every sku when sku is 0). out: swatches complete ok failed worst rows_used. 366func pt_wash_ledger(cs: *i64, buf: *u8, rows: *i64, nrows: i64, sku: *u8, tol_micro: i64, out: *i64) -> i64 { 367 var swatches: i64 = 0 368 var complete: i64 = 0 369 var okn: i64 = 0 370 var failn: i64 = 0 371 var worst: i64 = 0 372 var used: i64 = 0 373 var i: i64 = 0 374 while i < nrows { 375 let bi: i64 = i * PT_ROW_STRIDE 376 var mine: i64 = 1 377 if (sku as i64) != 0 { mine = pt_span_eq(buf, rows[bi + 2], rows[bi + 3], sku) } 378 if mine == 1 { if rows[bi + 4] == 0 { 379 var first: i64 = 1 380 var j: i64 = 0 381 while j < i { let bj: i64 = j * PT_ROW_STRIDE; if rows[bj + 4] == 0 { if pt_span_same(buf, rows[bi], rows[bi + 1], rows[bj], rows[bj + 1]) == 1 { if pt_span_same(buf, rows[bi + 2], rows[bi + 3], rows[bj + 2], rows[bj + 3]) == 1 { first = 0 } } } j = j + 1 } 382 if first == 1 { 383 swatches = swatches + 1 384 used = used + 1 385 var cycles: i64 = 0 386 var smax: i64 = 0 387 var k: i64 = 0 388 while k < nrows { 389 let bk: i64 = k * PT_ROW_STRIDE 390 if rows[bk + 4] > 0 { if pt_span_same(buf, rows[bi], rows[bi + 1], rows[bk], rows[bk + 1]) == 1 { if pt_span_same(buf, rows[bi + 2], rows[bi + 3], rows[bk + 2], rows[bk + 3]) == 1 { 391 let d: i64 = fq_to_micro(cs_de2000(cs, rows[bi + 5], rows[bi + 6], rows[bi + 7], rows[bk + 5], rows[bk + 6], rows[bk + 7])) 392 cycles = cycles + 1 393 used = used + 1 394 if d > smax { smax = d } 395 pt_w(" swatch=" as *u8); sys_write(1, (buf as i64 + rows[bi]) as *u8, rows[bi + 1]); pt_w(" sku=" as *u8); sys_write(1, (buf as i64 + rows[bi + 2]) as *u8, rows[bi + 3]) 396 pt_w(" cycle=" as *u8); pt_wn(rows[bk + 4]); pt_w(" de00_micro=" as *u8); pt_wn(d); pt_w("\n" as *u8) 397 } } } 398 k = k + 1 399 } 400 if cycles + 1 >= PT_MIN_CYCLES { 401 complete = complete + 1 402 if smax <= tol_micro { okn = okn + 1 } else { failn = failn + 1 } 403 } 404 if smax > worst { worst = smax } 405 } 406 } } 407 i = i + 1 408 } 409 out[0] = swatches 410 out[1] = complete 411 out[2] = okn 412 out[3] = failn 413 out[4] = worst 414 out[5] = used 415 if swatches == 0 { return PT_EXIT_UNOBSERVABLE } 416 if complete < swatches { return PT_EXIT_UNOBSERVABLE } 417 if failn > 0 { return PT_EXIT_REFUSED } 418 return PT_EXIT_OK 419} 420// pt_bench_gate: the swatch bench over every sku; REFUSES partial coverage as UNOBSERVABLE 421func pt_bench_gate(cs: *i64, buf: *u8, rows: *i64, nrows: i64, tol_micro: i64, out: *i64) -> i64 { 422 return pt_wash_ledger(cs, buf, rows, nrows, 0 as *u8, tol_micro, out) 423} 424func pt_print_wash(out: *i64, rc: i64) -> i64 { 425 pt_w(" swatches=" as *u8); pt_wn(out[0]); pt_w(" complete=" as *u8); pt_wn(out[1]); pt_w(" ok=" as *u8); pt_wn(out[2]) 426 pt_w(" failed=" as *u8); pt_wn(out[3]); pt_w(" worst_de00_micro=" as *u8); pt_wn(out[4]); pt_w(" rows_used=" as *u8); pt_wn(out[5]); pt_w("\n" as *u8) 427 if rc == PT_EXIT_OK { pt_w("verdict=WASH-OK\n" as *u8) } 428 if rc == PT_EXIT_REFUSED { pt_w("verdict=WASH-FAIL\n" as *u8) } 429 if rc == PT_EXIT_UNOBSERVABLE { if out[0] == 0 { pt_w("verdict=UNMEASURED (no cycle-0 swatch rows)\n" as *u8) } else { pt_w("verdict=UNMEASURED-PARTIAL-COVERAGE (a swatch lacks a later cycle)\n" as *u8) } } 430 return 0 431} 432func pt_run_wash(cs: *i64, ledger: *u8, sku: *u8, conf: *u8, missing: *i64, bench: i64) -> i64 { 433 let tol: i64 = pt_conf(conf, "wash_tol_de00_micro" as *u8, missing) 434 if missing[0] > 0 { pt_w("verdict=UNOBSERVABLE conf incomplete\n" as *u8); return PT_EXIT_UNOBSERVABLE } 435 let lp: *i64 = sys_mmap(PT_SLOT * 2) as *i64 436 let buf: *u8 = sys_read_file(ledger, lp) 437 if (buf as i64) == 0 { pt_w("verdict=UNMEASURED ledger unreadable: " as *u8); pt_w(ledger); pt_w("\n" as *u8); return PT_EXIT_UNOBSERVABLE } 438 let rows: *i64 = sys_mmap(PT_LEDGER_MAX_ROWS * PT_ROW_STRIDE * PT_SLOT) as *i64 439 let bad: *i64 = sys_mmap(PT_SLOT) as *i64 440 bad[0] = 0 441 let n: i64 = pt_ledger_load(buf, lp[0], rows, bad) 442 pt_w("=== NX-PHOTOTWIN " as *u8) 443 if bench == 1 { pt_w("bench" as *u8) } else { pt_w("wash sku=" as *u8); pt_w(sku) } 444 pt_w(" ledger=" as *u8); pt_w(ledger); pt_w(" rows=" as *u8); pt_wn(n); pt_w(" malformed=" as *u8); pt_wn(bad[0]); pt_w(" tol_micro=" as *u8); pt_wn(tol); pt_w("\n" as *u8) 445 let out: *i64 = sys_mmap(PT_SLOT * PT_OUT_SLOTS) as *i64 446 var rc: i64 = 0 447 if bench == 1 { rc = pt_bench_gate(cs, buf, rows, n, tol, out) } else { rc = pt_wash_ledger(cs, buf, rows, n, sku, tol, out) } 448 pt_print_wash(out, rc) 449 return rc 450} 451 452func pt_usage() -> i64 { 453 pt_w("usage: nx_phototwin report <image> <image.icc> <substrate.icc> [conf] | proof <image> <image.icc> <substrate.icc> <outdir> [conf] | wash <ledger.tsv> <sku> [conf] | bench <ledger.tsv> [conf]\n" as *u8) 454 return PT_EXIT_USAGE 455} 456 457func main(argc: i64, argv: *i64) -> i64 { 458 if argc < 3 { return pt_usage() } 459 let verb: *u8 = argv[1] as *u8 460 let cs: *i64 = cs_ctx() 461 let missing: *i64 = sys_mmap(PT_SLOT) as *i64 462 missing[0] = 0 463 var conf: *u8 = PT_CONF_DEFAULT 464 if pt_streq(verb, "wash" as *u8) == 1 { if argc < 4 { return pt_usage() } if argc >= 5 { conf = argv[4] as *u8 } return pt_run_wash(cs, argv[2] as *u8, argv[3] as *u8, conf, missing, 0) } 465 if pt_streq(verb, "bench" as *u8) == 1 { if argc >= 4 { conf = argv[3] as *u8 } return pt_run_wash(cs, argv[2] as *u8, 0 as *u8, conf, missing, 1) } 466 var is_proof: i64 = 0 467 if pt_streq(verb, "proof" as *u8) == 1 { is_proof = 1 } 468 if is_proof == 0 { if pt_streq(verb, "report" as *u8) == 0 { return pt_usage() } } 469 if argc < 5 { return pt_usage() } 470 if is_proof == 1 { if argc < 6 { return pt_usage() } if argc >= 7 { conf = argv[6] as *u8 } } else { if argc >= 6 { conf = argv[5] as *u8 } } 471 let image: *u8 = argv[2] as *u8 472 let imgprof: *u8 = argv[3] as *u8 473 let subprof: *u8 = argv[4] as *u8 474 pt_w("=== NX-PHOTOTWIN " as *u8); pt_w(verb); pt_w(" image=" as *u8); pt_w(image); pt_w(" image_profile=" as *u8); pt_w(imgprof); pt_w(" substrate_profile=" as *u8); pt_w(subprof); pt_w("\n" as *u8) 475 let hs: i64 = pt_conf(conf, "gbd_hue_sectors" as *u8, missing) 476 let es: i64 = pt_conf(conf, "gbd_elev_sectors" as *u8, missing) 477 if missing[0] > 0 { pt_w("verdict=UNOBSERVABLE conf incomplete\n" as *u8); return PT_EXIT_UNOBSERVABLE } 478 let img: *i64 = cs_icc_load(cs, imgprof) 479 if img[CS_P_OK] != 1 { pt_w("verdict=UNOBSERVABLE image profile unreadable or not matrix/TRC (tags_parsed=" as *u8); pt_wn(img[CS_P_HAVE]); pt_w(")\n" as *u8); return PT_EXIT_UNOBSERVABLE } 480 let sub: *i64 = cs_icc_load(cs, subprof) 481 if sub[CS_P_OK] != 1 { pt_w("verdict=UNOBSERVABLE substrate profile unreadable or not matrix/TRC (tags_parsed=" as *u8); pt_wn(sub[CS_P_HAVE]); pt_w(")\n" as *u8); return PT_EXIT_UNOBSERVABLE } 482 let wh: *i64 = sys_mmap(PT_SLOT * 2) as *i64 483 wh[0] = 0 484 wh[1] = 0 485 let rgb: *u8 = nx_img_to_rgb(image, wh) 486 if (rgb as i64) == 0 { pt_w("verdict=UNOBSERVABLE image undecodable (baseline JPEG, PNG, GIF, BMP, TIFF, TGA, PCX, PNM only)\n" as *u8); return PT_EXIT_UNOBSERVABLE } 487 let w: i64 = wh[0] 488 let h: i64 = wh[1] 489 if w <= 0 { return PT_EXIT_UNOBSERVABLE } 490 if h <= 0 { return PT_EXIT_UNOBSERVABLE } 491 pt_w(" decoded w=" as *u8); pt_wn(w); pt_w(" h=" as *u8); pt_wn(h); pt_w(" image_profile_b64=" as *u8); pt_wn(img[CS_P_B64]); pt_w(" substrate_profile_b64=" as *u8); pt_wn(sub[CS_P_B64]); pt_w("\n" as *u8) 492 let sect: *i64 = cs_gbd_alloc(hs, es) 493 let memo: *i64 = pt_memo_alloc() 494 let out: *i64 = sys_mmap(PT_SLOT * (PT_R_SECT0 + hs)) as *i64 495 var z: i64 = 0 496 while z < PT_R_SECT0 + hs { out[z] = 0; z = z + 1 } 497 if pt_gamut_report(cs, img, sub, sect, rgb, w, h, memo, out) < 0 { pt_w("verdict=UNOBSERVABLE memo full\n" as *u8); return PT_EXIT_UNOBSERVABLE } 498 pt_print_report(out, hs) 499 var control: i64 = 0 500 if pt_streq(imgprof, subprof) == 1 { control = 1 } 501 if is_proof == 0 { 502 pt_w("verdict=REPORT out_permil=" as *u8) 503 if out[PT_R_PIXELS] > 0 { pt_wn((out[PT_R_OUTSIDE] * PT_PERMIL) / out[PT_R_PIXELS]) } else { pt_wn(0) } 504 if control == 1 { pt_w(" substrate=CONTROL" as *u8) } 505 pt_w("\n" as *u8) 506 return PT_EXIT_OK 507 } 508 return pt_soft_proof(cs, img, sub, rgb, w, h, memo, argv[5] as *u8, conf, missing, control) 509}