nx_color_meter.nx source
↩ module page · 75 lines · 3462 B
1// nx_color_meter.nx -- Calculates CMYK toner coverage from an RGB bitmap and estimates remaining toner based on page yield.
2const NX_MAGIC_1000000: i64 = 1000000
3const NX_MAGIC_50000: i64 = 50000
4const NX_MAGIC_10000: i64 = 10000
5// nx_color_meter.nx -- per-color (CMYK) toner coverage measurement = the COLOR analog of the black-toner
6// real-coverage exceed. The operator's ask: "color toner monitoring, not just black." We own the bitmap, so
7// we measure REAL per-channel toner consumption with GCR under-color removal (black pulled into the K channel,
8// exactly as a color laser separates), giving honest C/M/Y/K consumption per page -- the per-color truth the
9// printer's lying per-color gauge can't be trusted for (HP "fake empty" = $5M settlement; per the sovereign
10// researcher's Ink/Toner-cartridge sources). PURE integer, NO float, NO syscalls -> never-brick #26.
11// Grounded: ISO/IEC 19798 (color toner page yield) + ISO/IEC 19752 (mono), 5%-coverage standard page.
12// genealogy_id: project-printer-management-ipp-sclass-2026-06-20 ; license_tier: ORIGINAL
13
14const NX_CMY_C: i64 = 0
15const NX_CMY_M: i64 = 1
16const NX_CMY_Y: i64 = 2
17const NX_CMY_K: i64 = 3
18
19// Per-channel coverage in ppm (1e6 = 100% of that colorant over the page) from an RGB bitmap (3 bytes/pixel).
20// out4 receives [C, M, Y, K]. CMY demand = 255 - channel; GCR: K = min(C,M,Y) -> black toner, C/M/Y reduced
21// by K (so pure gray/black consumes K toner, not a muddy CMY mix -- how a real color laser prints).
22func nx_color_coverage(rgb: *u8, npix: i64, out4: *i64) -> i64 {
23 if npix <= 0 { out4[0] = 0; out4[1] = 0; out4[2] = 0; out4[3] = 0; return 0 }
24 var sc: i64 = 0
25 var sm: i64 = 0
26 var sy: i64 = 0
27 var sk: i64 = 0
28 var i: i64 = 0
29 while i < npix {
30 let base: i64 = i * 3
31 let r: i64 = rgb[base] as i64
32 let g: i64 = rgb[base + 1] as i64
33 let bb: i64 = rgb[base + 2] as i64
34 let c: i64 = 255 - r
35 let m: i64 = 255 - g
36 let y: i64 = 255 - bb
37 var k: i64 = c
38 if m < k { k = m }
39 if y < k { k = y }
40 sc = sc + (c - k)
41 sm = sm + (m - k)
42 sy = sy + (y - k)
43 sk = sk + k
44 i = i + 1
45 }
46 let denom: i64 = 255 * npix
47 out4[0] = (sc * NX_MAGIC_1000000) / denom
48 out4[1] = (sm * NX_MAGIC_1000000) / denom
49 out4[2] = (sy * NX_MAGIC_1000000) / denom
50 out4[3] = (sk * NX_MAGIC_1000000) / denom
51 return 0
52}
53
54// Per-color remaining toner in basis points (10000 = 100%) given that color's ISO-19798 page yield and the
55// cumulative coverage consumed (ppm summed over pages). capacity = yield_pages * 50000 ppm (the 5% ISO page).
56// -1 if yield unknown. Mirrors the mono nx_toner_ledger, per channel.
57func nx_color_remaining_bp(yield_pages: i64, consumed_ppm: i64) -> i64 {
58 if yield_pages <= 0 { return 0 - 1 }
59 let cap: i64 = yield_pages * NX_MAGIC_50000
60 if cap <= 0 { return 0 - 1 }
61 if consumed_ppm >= cap { return 0 }
62 return ((cap - consumed_ppm) * NX_MAGIC_10000) / cap
63}
64
65// sealed per-color status from a remaining-bp value: OK / LOW (<10%) / EMPTY (0) / UNKNOWN (-1).
66const NX_CSUP_OK: i64 = 1
67const NX_CSUP_LOW: i64 = 2
68const NX_CSUP_EMPTY: i64 = 3
69const NX_CSUP_UNKNOWN: i64 = 4
70func nx_color_status(remaining_bp: i64) -> i64 {
71 if remaining_bp < 0 { return NX_CSUP_UNKNOWN }
72 if remaining_bp == 0 { return NX_CSUP_EMPTY }
73 if remaining_bp < 1000 { return NX_CSUP_LOW }
74 return NX_CSUP_OK
75}