nx_garment_twin.nx source
↩ module page · 186 lines · 9862 B
1// nx_garment_twin.nx -- THE GARMENT PREVIEW: a print placed on a lit, procedurally knitted cotton front (2026-08-24,
2// /compare/phototwin PT5: gt_print_place). license_tier: ORIGINAL No hw writes (Rule 26).
3//
4// nx_garment_twin place <print.img> <out.png> [conf] -> the flat-front preview PNG + a stats line + verdict=PLACED
5// EXIT: 0 PLACED | 2 usage | 3 UNOBSERVABLE (undecodable print, unreadable conf, unwritable output)
6//
7// WHAT IT IS, HONESTLY. A FLAT-FRONT preview: the print is multiplied onto the fabric albedo (ink is subtractive: it can
8// only darken cotton, never brighten it) and the whole front is shaded by a procedural jersey-knit relief (wale and
9// course pitch, relief depth, ambient floor -- all conf rows) under an upper-left light, so the print reads as ink on
10// a knitted surface rather than a flat composite. It is NOT a mesh drape: a body-on-mesh render is the next rung
11// (gt_drape_mesh), declared, not implied. Every number is in knowledge/garment_twin.conf, and the fabric albedo rows
12// are DECLARED until the bench swatch (PT7) measures the real blank -- the conf says so.
13import "nx_syscalls.nx"
14import "nx_colorsci_lib.nx"
15import "nx_img_to_rgb.nx"
16import "nx_png_write.nx"
17
18const GT_EXIT_OK: i64 = 0
19const GT_EXIT_USAGE: i64 = 2
20const GT_EXIT_UNOBSERVABLE: i64 = 3
21const GT_CONF_DEFAULT: *u8 = "knowledge/garment_twin.conf"
22const GT_CHANNELS: i64 = 3
23const GT_BYTE_MAX: i64 = 255
24const GT_PERMIL: i64 = 1000
25const GT_SLOT: i64 = 8
26const GT_LINE_CAP: i64 = 1024
27const GT_LUMA_R_MILLI: i64 = 2126 // Rec. 709 luma weights, milli-units (the photometric mean used for the stats line)
28const GT_LUMA_G_MILLI: i64 = 7152
29const GT_LUMA_B_MILLI: i64 = 722
30const GT_LUMA_DEN: i64 = 10000
31
32func gt_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
33func gt_wn(v: i64) -> i64 {
34 var m: i64 = v
35 if m < 0 { gt_w("-" as *u8); m = 0 - m }
36 let t: *u8 = sys_mmap(32)
37 var k: i64 = 0
38 if m == 0 { t[0] = 48 as u8; k = 1 }
39 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
40 let o: *u8 = sys_mmap(32)
41 var i: i64 = 0
42 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
43 sys_write(1, o, k)
44 return 0
45}
46func gt_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 }
47func gt_conf(path: *u8, key: *u8, missing: *i64) -> i64 {
48 let v: i64 = cs_conf_int(path, key)
49 if v == CS_CONF_MISS { gt_w(" CONF-MISSING key=" as *u8); gt_w(key); gt_w(" in " as *u8); gt_w(path); gt_w("\n" as *u8); missing[0] = missing[0] + 1; return 0 }
50 return v
51}
52func gt_clamp8(v: i64) -> i64 { if v < 0 { return 0 } if v > GT_BYTE_MAX { return GT_BYTE_MAX } return v }
53
54// the knit shade table: one Lambert factor (permil) per cell of a wale x course tile, from a sinusoidal relief lit from
55// the upper left. Relief height h = sin(2 pi x / wale) * sin(2 pi y / course); the slope toward the light is the
56// shading signal; relief_permil scales it, ambient_permil floors it.
57func gt_shade_table(fq: *i64, wale: i64, course: i64, relief_permil: i64, ambient_permil: i64) -> *i64 {
58 let tab: *i64 = sys_mmap(GT_SLOT * wale * course) as *i64
59 let sc: *i64 = sys_mmap(GT_SLOT * 2) as *i64
60 let two_pi: i64 = fq[FQ_C_PI] * 2
61 var y: i64 = 0
62 while y < course {
63 var x: i64 = 0
64 while x < wale {
65 let ax: i64 = fq_div(two_pi * x, fq_from_int(wale))
66 let ay: i64 = fq_div(two_pi * y, fq_from_int(course))
67 fq_sincos(fq, ax, sc)
68 let cx: i64 = sc[1]
69 fq_sincos(fq, ay, sc)
70 let cy: i64 = sc[1]
71 // slope toward the upper-left light: the sum of the two cosines (each in [-1, 1])
72 let slope: i64 = (cx + cy) / 2
73 var shade: i64 = ambient_permil + ((GT_PERMIL - ambient_permil) * (GT_PERMIL + fq_to_int(fq_mul(slope, fq_from_int(relief_permil))))) / GT_PERMIL
74 if shade > GT_PERMIL { shade = GT_PERMIL }
75 if shade < 0 { shade = 0 }
76 tab[y * wale + x] = shade
77 x = x + 1
78 }
79 y = y + 1
80 }
81 return tab
82}
83
84// gt_print_place: compose the print (pw x ph RGB) onto a W x H canvas at the chest box, ink-multiplied over the fabric
85// albedo, shaded by the knit table. out: [0] box_x [1] box_y [2] box_w [3] box_h [4] fabric mean luma milli [5] print-region mean luma milli
86func gt_print_place(canvas: *u8, W: i64, H: i64, print: *u8, pw: i64, ph: i64, fab_r: i64, fab_g: i64, fab_b: i64, shade: *i64, wale: i64, course: i64, bx: i64, by: i64, bw: i64, out: *i64) -> i64 {
87 var bh: i64 = 0
88 if pw > 0 { bh = (bw * ph) / pw }
89 var fab_luma: i64 = 0
90 var fab_n: i64 = 0
91 var prn_luma: i64 = 0
92 var prn_n: i64 = 0
93 var y: i64 = 0
94 while y < H {
95 var x: i64 = 0
96 while x < W {
97 var r: i64 = fab_r
98 var g: i64 = fab_g
99 var b: i64 = fab_b
100 var in_box: i64 = 0
101 if x >= bx { if x < bx + bw { if y >= by { if y < by + bh { in_box = 1 } } } }
102 if in_box == 1 {
103 let sx: i64 = ((x - bx) * pw) / bw
104 let sy: i64 = ((y - by) * ph) / bh
105 let so: i64 = (sy * pw + sx) * GT_CHANNELS
106 // ink is subtractive: the print multiplies the fabric albedo (white cotton keeps the print, never lightens it)
107 r = (fab_r * (print[so] as i64)) / GT_BYTE_MAX
108 g = (fab_g * (print[so + 1] as i64)) / GT_BYTE_MAX
109 b = (fab_b * (print[so + 2] as i64)) / GT_BYTE_MAX
110 }
111 let sh: i64 = shade[(y % course) * wale + (x % wale)]
112 r = gt_clamp8((r * sh) / GT_PERMIL)
113 g = gt_clamp8((g * sh) / GT_PERMIL)
114 b = gt_clamp8((b * sh) / GT_PERMIL)
115 let o: i64 = (y * W + x) * GT_CHANNELS
116 canvas[o] = r as u8
117 canvas[o + 1] = g as u8
118 canvas[o + 2] = b as u8
119 let luma: i64 = (r * GT_LUMA_R_MILLI + g * GT_LUMA_G_MILLI + b * GT_LUMA_B_MILLI) / GT_LUMA_DEN
120 if in_box == 1 { prn_luma = prn_luma + luma; prn_n = prn_n + 1 } else { fab_luma = fab_luma + luma; fab_n = fab_n + 1 }
121 x = x + 1
122 }
123 y = y + 1
124 }
125 out[0] = bx
126 out[1] = by
127 out[2] = bw
128 out[3] = bh
129 if fab_n > 0 { out[4] = (fab_luma * GT_PERMIL) / (fab_n * GT_BYTE_MAX) } else { out[4] = 0 }
130 if prn_n > 0 { out[5] = (prn_luma * GT_PERMIL) / (prn_n * GT_BYTE_MAX) } else { out[5] = 0 }
131 return 0
132}
133
134func gt_usage() -> i64 { gt_w("usage: nx_garment_twin place <print.img> <out.png> [conf]\n" as *u8); return GT_EXIT_USAGE }
135
136func main(argc: i64, argv: *i64) -> i64 {
137 if argc < 4 { return gt_usage() }
138 if gt_streq(argv[1] as *u8, "place" as *u8) == 0 { return gt_usage() }
139 let print_path: *u8 = argv[2] as *u8
140 let out_path: *u8 = argv[3] as *u8
141 var conf: *u8 = GT_CONF_DEFAULT
142 if argc >= 5 { conf = argv[4] as *u8 }
143 let missing: *i64 = sys_mmap(GT_SLOT) as *i64
144 missing[0] = 0
145 let W: i64 = gt_conf(conf, "canvas_w" as *u8, missing)
146 let H: i64 = gt_conf(conf, "canvas_h" as *u8, missing)
147 let fab_r: i64 = gt_conf(conf, "fabric_r" as *u8, missing)
148 let fab_g: i64 = gt_conf(conf, "fabric_g" as *u8, missing)
149 let fab_b: i64 = gt_conf(conf, "fabric_b" as *u8, missing)
150 let wale: i64 = gt_conf(conf, "wale_px" as *u8, missing)
151 let course: i64 = gt_conf(conf, "course_px" as *u8, missing)
152 let relief: i64 = gt_conf(conf, "relief_permil" as *u8, missing)
153 let ambient: i64 = gt_conf(conf, "ambient_permil" as *u8, missing)
154 let cx: i64 = gt_conf(conf, "chest_x_permil" as *u8, missing)
155 let cy: i64 = gt_conf(conf, "chest_y_permil" as *u8, missing)
156 let cw: i64 = gt_conf(conf, "chest_w_permil" as *u8, missing)
157 if missing[0] > 0 { gt_w("verdict=UNOBSERVABLE conf incomplete\n" as *u8); return GT_EXIT_UNOBSERVABLE }
158 if W <= 0 { return GT_EXIT_UNOBSERVABLE }
159 if H <= 0 { return GT_EXIT_UNOBSERVABLE }
160 if wale <= 0 { return GT_EXIT_UNOBSERVABLE }
161 if course <= 0 { return GT_EXIT_UNOBSERVABLE }
162 let wh: *i64 = sys_mmap(GT_SLOT * 2) as *i64
163 wh[0] = 0
164 wh[1] = 0
165 let print: *u8 = nx_img_to_rgb(print_path, wh)
166 if (print as i64) == 0 { gt_w("verdict=UNOBSERVABLE print undecodable\n" as *u8); return GT_EXIT_UNOBSERVABLE }
167 if wh[0] <= 0 { return GT_EXIT_UNOBSERVABLE }
168 if wh[1] <= 0 { return GT_EXIT_UNOBSERVABLE }
169 let fq: *i64 = fq_ctx()
170 let shade: *i64 = gt_shade_table(fq, wale, course, relief, ambient)
171 let canvas: *u8 = sys_mmap(W * H * GT_CHANNELS)
172 let out: *i64 = sys_mmap(GT_SLOT * 8) as *i64
173 let bx: i64 = (W * cx) / GT_PERMIL
174 let by: i64 = (H * cy) / GT_PERMIL
175 let bw: i64 = (W * cw) / GT_PERMIL
176 gt_print_place(canvas, W, H, print, wh[0], wh[1], fab_r, fab_g, fab_b, shade, wale, course, bx, by, bw, out)
177 let wr: i64 = nx_png_write_rgb(out_path, canvas, W, H)
178 if wr < 0 { gt_w("verdict=UNOBSERVABLE output unwritable\n" as *u8); return GT_EXIT_UNOBSERVABLE }
179 gt_w("=== NX-GARMENT-TWIN place print=" as *u8); gt_w(print_path); gt_w(" out=" as *u8); gt_w(out_path); gt_w("\n" as *u8)
180 gt_w(" canvas=" as *u8); gt_wn(W); gt_w("x" as *u8); gt_wn(H); gt_w(" print=" as *u8); gt_wn(wh[0]); gt_w("x" as *u8); gt_wn(wh[1])
181 gt_w(" box=" as *u8); gt_wn(out[0]); gt_w("," as *u8); gt_wn(out[1]); gt_w("," as *u8); gt_wn(out[2]); gt_w("," as *u8); gt_wn(out[3])
182 gt_w(" fabric_luma_permil=" as *u8); gt_wn(out[4]); gt_w(" print_luma_permil=" as *u8); gt_wn(out[5])
183 gt_w(" knit=" as *u8); gt_wn(wale); gt_w("x" as *u8); gt_wn(course); gt_w(" relief_permil=" as *u8); gt_wn(relief); gt_w(" surface=FLAT-FRONT-KNIT (mesh drape is the next rung)\n" as *u8)
184 gt_w("verdict=PLACED\n" as *u8)
185 return GT_EXIT_OK
186}