code wiki / (root) / nx_garment_twin.nx

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}