code wiki / (root) / nx_skinsample_lib.nx

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}