nx_skinsample_lib.nx source
↩ module page · 226 lines · 10539 B
1// nx_skinsample_lib.nx -- THE FIRST PHOTO RULER FOR THE TWIN CARD: skin colour MEASURED from a mirrored photograph
2// (aesthetictwin AT23 rulers skin_L skin_a skin_b ita, 2026-09-16). Decodes the JPEG through the estate's own baseline and
3// progressive decoder (nx_jpeg_ascii.nx composes nx_jpeg_decode and nx_jpeg_progressive), keeps the pixels a declared skin
4// chroma rule admits, averages them in LINEAR light (never in gamma bytes), converts the mean to CIE L*a*b* with the transform
5// constants nx_skin_ita already carries (one colour organ, never two), and takes the individual typology angle through the
6// estate's fixed-point CORDIC arctangent (nx_fixq30_lib). Every number a card receives from this ruler is MEASURED: method
7// measured, source the declared reference key, and the receipt prints the mask count beside the mean so a reader can see how
8// much of the image the value rests on.
9// WHAT IS CHOSEN, said plainly: the skin mask is the YCbCr chroma rule Cb in [77,127] and Cr in [133,173] (a published
10// hand-tuned rule, not a model of this subject) with a luma gate that drops clipped shadows and highlights. It admits skin under
11// most daylight and studio light and also admits skin-coloured backgrounds, sand and wood; the count and the coverage are
12// therefore PRINTED and journaled with the value, and a region chart (a face or torso window) narrows the mask when the card
13// names one. What is DERIVED: the sRGB transfer, the D65 matrix, the Lab transform and the ITA definition (Chardon 1991).
14// LIB, no main. license_tier: ORIGINAL No hw writes (Rule 26).
15import "nx_syscalls.nx"
16import "nx_jpeg_ascii.nx"
17import "nx_skin_ita.nx"
18import "nx_fixq30_lib.nx"
19import "nx_twincard_lib.nx"
20
21const SKS_E5: i64 = 100000
22const SKS_E4: i64 = 10000
23const SKS_E3: i64 = 1000
24const SKS_PCT: i64 = 100
25const SKS_TEN: i64 = 10
26// linear sRGB -> XYZ (IEC 61966-2-1, D65) x 1e4
27const SKS_M_XR: i64 = 4124
28const SKS_M_XG: i64 = 3576
29const SKS_M_XB: i64 = 1805
30const SKS_M_YR: i64 = 2126
31const SKS_M_YG: i64 = 7152
32const SKS_M_YB: i64 = 722
33const SKS_M_ZR: i64 = 193
34const SKS_M_ZG: i64 = 1192
35const SKS_M_ZB: i64 = 9505
36// the Lab f() knee in the 1e5 domain: (6/29)^3 = 0.008856
37const SKS_F_KNEE: i64 = 886
38// f(t) = t / (3 (6/29)^2) + 4/29 below the knee: the slope constant nx_skin_ita carries at 1e4 (SI_F_LINK) and the
39// offset it carries at 1e4 (SI_F_OFF), both lifted to the 1e5 f-domain here
40const SKS_F_OFF_E5: i64 = 13790
41// a* = 500 (fx - fy), b* = 200 (fy - fz): with f at 1e5 and a*, b* at 1e3 the factors are 5 and 2
42const SKS_A_MUL: i64 = 5
43const SKS_B_MUL: i64 = 2
44// the skin mask: YCbCr (JFIF) chroma windows, a CHOSEN published rule, plus a luma gate against clipped pixels
45const SKS_YCC_CB_R: i64 = 0 - 16874
46const SKS_YCC_CB_G: i64 = 0 - 33126
47const SKS_YCC_CB_B: i64 = 50000
48const SKS_YCC_CR_R: i64 = 50000
49const SKS_YCC_CR_G: i64 = 0 - 41869
50const SKS_YCC_CR_B: i64 = 0 - 8131
51const SKS_YCC_Y_R: i64 = 29900
52const SKS_YCC_Y_G: i64 = 58700
53const SKS_YCC_Y_B: i64 = 11400
54const SKS_YCC_MID: i64 = 128
55const SKS_CB_LO: i64 = 77
56const SKS_CB_HI: i64 = 127
57const SKS_CR_LO: i64 = 133
58const SKS_CR_HI: i64 = 173
59const SKS_Y_LO: i64 = 30
60const SKS_Y_HI: i64 = 235
61const SKS_BYTE: i64 = 255
62// the ITA pivot L* = 50 at 1e3
63const SKS_L_PIVOT_E3: i64 = 50000
64// result slots
65const SKS_R_L: i64 = 0
66const SKS_R_A: i64 = 1
67const SKS_R_B: i64 = 2
68const SKS_R_COUNT: i64 = 3
69const SKS_R_TOTAL: i64 = 4
70const SKS_R_ITA: i64 = 5
71const SKS_R_N: i64 = 6
72const SKS_BPP: i64 = 3
73const SKS_OK: i64 = 0
74const SKS_E_UNMEASURED: i64 = 0 - 1
75const SKS_E_DECODE: i64 = 0 - 2
76const SKS_E_READ: i64 = 0 - 3
77const SKS_E_EXISTS: i64 = 0 - 4
78const SKS_SIDE_C: *u8 = "C"
79const SKS_METHOD: *u8 = "measured"
80
81// the ruler's generation stamp
82func sks_ruler_version() -> i64 { return 1 }
83
84// f() of the Lab transform on a ratio at 1e5 (t = X/Xn etc.), result at 1e5
85func sks_f_e5(t_e5: i64) -> i64 {
86 if t_e5 > SKS_F_KNEE { return si_cbrt_e5(t_e5) }
87 return t_e5 * SKS_E4 / SI_F_LINK + SKS_F_OFF_E5
88}
89// linear light (each channel at 1e5) -> L*, a*, b* at 1e3 in out3
90func sks_lab_of_linear(lr: i64, lg: i64, lb: i64, out3: *i64) -> i64 {
91 let x: i64 = (SKS_M_XR * lr + SKS_M_XG * lg + SKS_M_XB * lb) / SKS_E4
92 let y: i64 = (SKS_M_YR * lr + SKS_M_YG * lg + SKS_M_YB * lb) / SKS_E4
93 let z: i64 = (SKS_M_ZR * lr + SKS_M_ZG * lg + SKS_M_ZB * lb) / SKS_E4
94 let fx: i64 = sks_f_e5(x * SKS_E5 / SI_XN)
95 let fy: i64 = sks_f_e5(y)
96 let fz: i64 = sks_f_e5(z * SKS_E5 / SI_ZN)
97 out3[0] = SI_L_DIV * fy / SKS_PCT - SI_L_ADD
98 out3[1] = SKS_A_MUL * (fx - fy)
99 out3[2] = SKS_B_MUL * (fy - fz)
100 return 0
101}
102// one sRGB byte triple -> L*, a*, b* at 1e3
103func sks_lab_of_rgb(r: i64, g: i64, b: i64, out3: *i64) -> i64 {
104 return sks_lab_of_linear(si_linear_of_byte(r), si_linear_of_byte(g), si_linear_of_byte(b), out3)
105}
106// does the chroma rule admit this pixel as skin?
107func sks_is_skin(r: i64, g: i64, b: i64) -> i64 {
108 let y: i64 = (SKS_YCC_Y_R * r + SKS_YCC_Y_G * g + SKS_YCC_Y_B * b) / SKS_E5
109 if y < SKS_Y_LO { return 0 }
110 if y > SKS_Y_HI { return 0 }
111 let cb: i64 = SKS_YCC_MID + (SKS_YCC_CB_R * r + SKS_YCC_CB_G * g + SKS_YCC_CB_B * b) / SKS_E5
112 let cr: i64 = SKS_YCC_MID + (SKS_YCC_CR_R * r + SKS_YCC_CR_G * g + SKS_YCC_CR_B * b) / SKS_E5
113 if cb < SKS_CB_LO { return 0 }
114 if cb > SKS_CB_HI { return 0 }
115 if cr < SKS_CR_LO { return 0 }
116 if cr > SKS_CR_HI { return 0 }
117 return 1
118}
119// the individual typology angle (Chardon 1991): atan((L* - 50) / b*) in degrees x10, through the estate's CORDIC
120func sks_ita_deg10(l_e3: i64, b_e3: i64) -> i64 {
121 let ctx: *i64 = fq_ctx()
122 let yq: i64 = (l_e3 - SKS_L_PIVOT_E3) * FQ_ONE / SKS_E3
123 let xq: i64 = b_e3 * FQ_ONE / SKS_E3
124 let rad: i64 = fq_atan2(ctx, yq, xq)
125 let deg: i64 = fq_rad2deg(ctx, rad)
126 if deg >= 0 { return fq_to_int(deg * SKS_TEN + FQ_HALF) }
127 return 0 - fq_to_int((0 - deg) * SKS_TEN + FQ_HALF)
128}
129// MEASURE a packed RGB image: mean linear light over the skin mask -> Lab and ITA. out[SKS_R_*]. Returns the mask count;
130// a count of 0 leaves the colour slots at 0 and the caller MUST report UNMEASURED rather than a colour
131func sks_measure_rgb(rgb: *u8, w: i64, h: i64, out: *i64) -> i64 {
132 var k: i64 = 0
133 while k < SKS_R_N { out[k] = 0; k = k + 1 }
134 let n: i64 = w * h
135 out[SKS_R_TOTAL] = n
136 var sr: i64 = 0
137 var sg: i64 = 0
138 var sb: i64 = 0
139 var c: i64 = 0
140 var i: i64 = 0
141 while i < n {
142 let o: i64 = i * SKS_BPP
143 let r: i64 = (rgb[o] as i64) & SKS_BYTE
144 let g: i64 = (rgb[o + 1] as i64) & SKS_BYTE
145 let b: i64 = (rgb[o + 2] as i64) & SKS_BYTE
146 if sks_is_skin(r, g, b) == 1 {
147 sr = sr + si_linear_of_byte(r)
148 sg = sg + si_linear_of_byte(g)
149 sb = sb + si_linear_of_byte(b)
150 c = c + 1
151 }
152 i = i + 1
153 }
154 out[SKS_R_COUNT] = c
155 if c == 0 { return 0 }
156 let lab: *i64 = sys_mmap(SKS_R_N * 8) as *i64
157 sks_lab_of_linear(sr / c, sg / c, sb / c, lab)
158 out[SKS_R_L] = lab[0]
159 out[SKS_R_A] = lab[1]
160 out[SKS_R_B] = lab[2]
161 out[SKS_R_ITA] = sks_ita_deg10(lab[0], lab[1 + 1])
162 return c
163}
164// DECODE a JPEG file through the sovereign decoder and measure it. Returns SKS_OK, SKS_E_READ, SKS_E_DECODE or SKS_E_UNMEASURED.
165func sks_measure_file(path: *u8, out: *i64) -> i64 {
166 let fl: *i64 = sys_mmap(16) as *i64
167 fl[0] = 0
168 let jpeg: *u8 = sys_read_file(path, fl)
169 if (jpeg as i64) == 0 { return SKS_E_READ }
170 if fl[0] <= 0 { return SKS_E_READ }
171 let orgb: *i64 = sys_mmap(8) as *i64
172 let ow: *i64 = sys_mmap(8) as *i64
173 let oh: *i64 = sys_mmap(8) as *i64
174 let rc: i64 = nx_jpeg_decode_rgb(jpeg, fl[0], orgb, ow, oh)
175 if rc != NX_JPEG_ASCII_OK { return SKS_E_DECODE }
176 let c: i64 = sks_measure_rgb(orgb[0] as *u8, ow[0], oh[0], out)
177 sys_munmap(orgb[0] as *u8, ow[0] * oh[0] * SKS_BPP + 16)
178 if c == 0 { return SKS_E_UNMEASURED }
179 return SKS_OK
180}
181// one measured card row: axis|skin|<id>|C|<value>|measured|<refkey>|<when>
182func sks_row(out: *u8, id: *u8, value: i64, refkey: *u8, when: *u8) -> i64 {
183 var o: i64 = ri_cat(out, 0, "axis|skin|" as *u8)
184 o = ri_cat(out, o, id)
185 out[o] = RI_PIPE as u8; o = o + 1
186 o = ri_cat(out, o, SKS_SIDE_C)
187 out[o] = RI_PIPE as u8; o = o + 1
188 o = ri_catn(out, o, value)
189 out[o] = RI_PIPE as u8; o = o + 1
190 o = ri_cat(out, o, SKS_METHOD)
191 out[o] = RI_PIPE as u8; o = o + 1
192 o = ri_cat(out, o, refkey)
193 out[o] = RI_PIPE as u8; o = o + 1
194 o = ri_cat(out, o, when)
195 out[o] = RI_NL as u8; o = o + 1
196 out[o] = 0 as u8
197 return o
198}
199// WRITE the four measured skin rows onto a card unless a row for that axis and side already exists (a card is never
200// clobbered: retire the observed row first). Returns the number of rows appended, SKS_E_EXISTS when any existed, SKS_E_READ
201// when the card is unreadable. lab10 values are the 1e3 values over 100; ita is already deg10.
202func sks_card_write(slug: *u8, refkey: *u8, when: *u8, res: *i64) -> i64 {
203 let path: *u8 = sys_mmap(RI_PATHB)
204 tc_card_path(slug, path)
205 let cl: *i64 = sys_mmap(16) as *i64
206 let card: *u8 = ri_load(path, cl)
207 if cl[0] <= 0 { return SKS_E_READ }
208 let key: *u8 = sys_mmap(RI_ROWB)
209 let pos: *i64 = sys_mmap(TC_POS_N * RI_I64) as *i64
210 var exists: i64 = 0
211 tc_key3(key, "axis" as *u8, "skin" as *u8, "skin_L" as *u8, SKS_SIDE_C); if tc_row_find(card, cl[0], key, pos) == 1 { exists = 1 }
212 tc_key3(key, "axis" as *u8, "skin" as *u8, "skin_a" as *u8, SKS_SIDE_C); if tc_row_find(card, cl[0], key, pos) == 1 { exists = 1 }
213 tc_key3(key, "axis" as *u8, "skin" as *u8, "skin_b" as *u8, SKS_SIDE_C); if tc_row_find(card, cl[0], key, pos) == 1 { exists = 1 }
214 tc_key3(key, "axis" as *u8, "skin" as *u8, "ita" as *u8, SKS_SIDE_C); if tc_row_find(card, cl[0], key, pos) == 1 { exists = 1 }
215 if exists == 1 { return SKS_E_EXISTS }
216 let row: *u8 = sys_mmap(RI_ROWB)
217 let fd: i64 = sys_openat_append(path, RI_MODE644)
218 if fd < 0 { return SKS_E_READ }
219 var n: i64 = 0
220 var o: i64 = sks_row(row, "skin_L" as *u8, res[SKS_R_L] / SKS_PCT, refkey, when); sys_write(fd, row, o); n = n + 1
221 o = sks_row(row, "skin_a" as *u8, res[SKS_R_A] / SKS_PCT, refkey, when); sys_write(fd, row, o); n = n + 1
222 o = sks_row(row, "skin_b" as *u8, res[SKS_R_B] / SKS_PCT, refkey, when); sys_write(fd, row, o); n = n + 1
223 o = sks_row(row, "ita" as *u8, res[SKS_R_ITA], refkey, when); sys_write(fd, row, o); n = n + 1
224 sys_close(fd)
225 return n
226}