nx_skintex_probe.nx source
↩ module page · 89 lines · 3836 B
1// nx_skintex_probe.nx -- decode a real skin texture and report what is actually in it.
2//
3// The arousal skin model computes reflectance from melanin + haemoglobin and was generating flat
4// colour with hash-noise micro-variation standing in for pores. A real Std_Skin_*_Diffuse map IS the
5// melanin/albedo term, measured off an actual character asset instead of invented -- so the
6// physiology can MODULATE a real albedo rather than replace it.
7//
8// This probe exists before any wiring: prove the decode, learn the resolution, and measure the tone
9// distribution. Building the shader path on an unverified decode would be building on a guess.
10//
11// usage: nx_skintex_probe <file.jpeg>
12// license_tier: ORIGINAL expect_exit: 0
13// ⚠NOT nx_jpeg_ascii: that module's decode_rgb only probes the SOF for dimensions (it backs the
14// JPEG->ASCII-art path). It returned rc=0 with correct 2048x2048 dims and ONE non-black pixel out of
15// 16384 sampled -- a success code over an empty buffer. nx_jpeg_decoder is the real baseline DCT
16// decoder (DQT/DHT/SOS/Huffman). Picking the first symbol that matched the name cost a build cycle.
17import "nx_jpeg_decoder.nx"
18
19func sp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
20func sp_putn(v: i64) -> i64 {
21 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
22 var m: i64 = v
23 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
24 let d: *u8 = sys_mmap(24); var k: i64 = 0
25 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
26 let o: *u8 = sys_mmap(24); var w: i64 = 0
27 while w < k { o[w] = d[k-1-w]; w = w + 1 }
28 sys_write(1, o, k)
29 sys_munmap(d, 24); sys_munmap(o, 24)
30 return 0
31}
32
33func main(argc: i64, argv: *i64) -> i64 {
34 if argc < 2 { sp_puts("REFUSED reason=need_jpeg_path\n" as *u8); return 2 }
35 let path: *u8 = argv[1] as *u8
36 let jl: *i64 = sys_mmap(16) as *i64
37 jl[0] = 0
38 let jbuf: *u8 = sys_read_file(path, jl)
39 let jn: i64 = jl[0]
40 if (jbuf as i64) == 0 { sp_puts("REFUSED reason=unreadable\n" as *u8); return 3 }
41 if jn <= 0 { sp_puts("REFUSED reason=empty\n" as *u8); return 3 }
42
43 let res: *NxJpgResult = nx_jpeg_decode(jbuf, jn)
44 let rc: i64 = res.error_code
45 let w: i64 = res.width
46 let h: i64 = res.height
47 let rgb: *u8 = res.rgb
48 sp_puts("bytes=" as *u8); sp_putn(jn)
49 sp_puts(" rc=" as *u8); sp_putn(rc)
50 sp_puts(" w=" as *u8); sp_putn(w)
51 sp_puts(" h=" as *u8); sp_putn(h)
52 sp_puts(" rgb_size=" as *u8); sp_putn(res.rgb_size)
53 if rc != 0 { sp_puts(" DECODE_FAILED\n" as *u8); return 4 }
54 if w <= 0 { sp_puts(" NO_PIXELS\n" as *u8); return 4 }
55 if (rgb as i64) == 0 { sp_puts(" NULL_RGB\n" as *u8); return 4 }
56
57 // mean tone over a coarse grid, plus the R:G ratio the Beer-Lambert model reasons in
58 var sr: i64 = 0
59 var sg: i64 = 0
60 var sb: i64 = 0
61 var n: i64 = 0
62 var y: i64 = 0
63 while y < h {
64 var x: i64 = 0
65 while x < w {
66 let o: i64 = (y * w + x) * 3
67 let r: i64 = rgb[o] as i64
68 let g: i64 = rgb[o+1] as i64
69 let b: i64 = rgb[o+2] as i64
70 // ignore the pure-black gutters an atlas leaves between UV islands
71 if r + g + b > 30 { sr = sr + r; sg = sg + g; sb = sb + b; n = n + 1 }
72 x = x + 16
73 }
74 y = y + 16
75 }
76 if n <= 0 { sp_puts(" ALL_BACKGROUND\n" as *u8); return 5 }
77 let mr: i64 = sr / n
78 let mg: i64 = sg / n
79 let mb: i64 = sb / n
80 sp_puts(" samples=" as *u8); sp_putn(n)
81 sp_puts(" mean_r=" as *u8); sp_putn(mr)
82 sp_puts(" mean_g=" as *u8); sp_putn(mg)
83 sp_puts(" mean_b=" as *u8); sp_putn(mb)
84 var rg: i64 = 0
85 if mg > 0 { rg = (mr * 1000) / mg }
86 sp_puts(" rg_permil=" as *u8); sp_putn(rg)
87 sp_puts("\n" as *u8)
88 return 0
89}