code wiki / (root) / nx_subsurface_warmth.nx

nx_subsurface_warmth.nx source

↩ module page · 155 lines · 5325 B

1// nx_subsurface_warmth.nx -- detect subsurface scatter (SSS) by R/G ratio in shadows. 2// 3// Real skin transmits red wavelength deeper than green/blue. In shadow 4// regions (low overall luminance), real skin shows ELEVATED red-to-green 5// ratio relative to the lit side. Mannequin renders / SDXL plastic 6// renders show flat R:G ratio across light and shadow. 7// 8// Math: 9// 1. Pick skin-mask pixels with luminance below median (shadow side). 10// 2. Compute mean R, mean G across those pixels. 11// 3. Mean R / Mean G in Q10. SSS-positive when > 1100 (≈1.075). 12// 4. Score Q10: how far above the SSS threshold. 13// 14// Output flat-array (result[]): 15// result[0] = shadow_pixel_count 16// result[1] = mean_R_q10 (mean R*4 to keep precision) 17// result[2] = mean_G_q10 18// result[3] = R_to_G_q10 (ratio in Q10) 19// result[4] = sss_score_q10 (0=no SSS, 1024=strong SSS) 20// result[5] = verdict (0=MANNEQUIN, 1=SLIGHT, 2=NATURAL) 21 22// nx_safety_envelope: 23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 24// sil_target: SIL1 25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 26// verdict: NOT_YET_EVALUATED 27 28import "nx_syscalls.nx" 29import "nx_image.nx" 30 31const NX_SSS_RES_COUNT: i64 = 0 32const NX_SSS_RES_MEAN_R: i64 = 1 33const NX_SSS_RES_MEAN_G: i64 = 2 34const NX_SSS_RES_RATIO: i64 = 3 35const NX_SSS_RES_SCORE: i64 = 4 36const NX_SSS_RES_VERDICT: i64 = 5 37const NX_SSS_RES_FIELDS: i64 = 6 38 39const NX_SSS_VERDICT_MANNEQUIN: i64 = 0 // ratio ≈ 1.0 (flat) 40const NX_SSS_VERDICT_SLIGHT: i64 = 1 // 1.05..1.075 41const NX_SSS_VERDICT_NATURAL: i64 = 2 // > 1.075 42 43const NX_SSS_THRESH_NATURAL_Q10: i64 = 1100 // 1.075 in Q10 44const NX_SSS_THRESH_SLIGHT_Q10: i64 = 1075 // 1.05 in Q10 45 46// Compute luminance threshold via simple percentile estimate over mask. 47// Returns the median Y over skin-mask pixels (or 128 fallback). 48func nx_sss_luminance_median(rgb: *Image, mask: *Image) -> i64 { 49 let h: i64 = mask.height 50 let w: i64 = mask.width 51 // 32-bin luminance histogram over skin pixels. 52 let hist: *i64 = (sys_mmap(32 * 8 + 16)) as *i64 53 var b: i64 = 0 54 while b < 32 { hist[b] = 0; b = b + 1 } 55 var n: i64 = 0 56 var y: i64 = 0 57 while y < h { 58 var x: i64 = 0 59 while x < w { 60 let m: i64 = nx_image_get(mask, x, y, 0) 61 if m > 0 { 62 let r: i64 = nx_image_get(rgb, x, y, 0) 63 let g: i64 = nx_image_get(rgb, x, y, 1) 64 let bch: i64 = nx_image_get(rgb, x, y, 2) 65 let yval: i64 = (77 * r + 150 * g + 29 * bch) / 256 66 var bin: i64 = yval / 8 67 if bin > 31 { bin = 31 } 68 hist[bin] = hist[bin] + 1 69 n = n + 1 70 } 71 x = x + 1 72 } 73 y = y + 1 74 } 75 if n == 0 { return 128 } 76 let half: i64 = n / 2 77 var acc: i64 = 0 78 var i: i64 = 0 79 while i < 32 { 80 acc = acc + hist[i] 81 if acc >= half { return i * 8 + 4 } 82 i = i + 1 83 } 84 return 128 85} 86 87// Run SSS analysis. Caller provides RGB image + skin mask + result array. 88func nx_subsurface_warmth(rgb: *Image, mask: *Image, result: *i64) -> i64 { 89 // Init result. 90 var i: i64 = 0 91 while i < NX_SSS_RES_FIELDS { result[i] = 0; i = i + 1 } 92 93 let median_y: i64 = nx_sss_luminance_median(rgb, mask) 94 let shadow_thresh: i64 = median_y 95 96 var sum_r: i64 = 0 97 var sum_g: i64 = 0 98 var count: i64 = 0 99 let w: i64 = rgb.width 100 let h: i64 = rgb.height 101 var y: i64 = 0 102 while y < h { 103 var x: i64 = 0 104 while x < w { 105 let m: i64 = nx_image_get(mask, x, y, 0) 106 if m > 0 { 107 let r: i64 = nx_image_get(rgb, x, y, 0) 108 let g: i64 = nx_image_get(rgb, x, y, 1) 109 let bch: i64 = nx_image_get(rgb, x, y, 2) 110 let yval: i64 = (77 * r + 150 * g + 29 * bch) / 256 111 if yval < shadow_thresh { 112 sum_r = sum_r + r 113 sum_g = sum_g + g 114 count = count + 1 115 } 116 } 117 x = x + 1 118 } 119 y = y + 1 120 } 121 122 result[NX_SSS_RES_COUNT] = count 123 if count == 0 { return 0 } 124 125 let mean_r: i64 = sum_r / count 126 let mean_g: i64 = sum_g / count 127 result[NX_SSS_RES_MEAN_R] = mean_r 128 result[NX_SSS_RES_MEAN_G] = mean_g 129 if mean_g == 0 { return 0 } 130 131 let ratio_q10: i64 = (mean_r * 1024) / mean_g 132 result[NX_SSS_RES_RATIO] = ratio_q10 133 134 // Score: linear from threshold_slight up to threshold_natural+spread. 135 var score: i64 = 0 136 let upper: i64 = NX_SSS_THRESH_NATURAL_Q10 + 100 137 if ratio_q10 > NX_SSS_THRESH_SLIGHT_Q10 { 138 if ratio_q10 >= upper { 139 score = 1024 140 } else { 141 score = ((ratio_q10 - NX_SSS_THRESH_SLIGHT_Q10) * 1024) / 142 (upper - NX_SSS_THRESH_SLIGHT_Q10) 143 } 144 } 145 result[NX_SSS_RES_SCORE] = score 146 147 var verdict: i64 = NX_SSS_VERDICT_MANNEQUIN 148 if ratio_q10 >= NX_SSS_THRESH_NATURAL_Q10 { 149 verdict = NX_SSS_VERDICT_NATURAL 150 } else { 151 if ratio_q10 >= NX_SSS_THRESH_SLIGHT_Q10 { verdict = NX_SSS_VERDICT_SLIGHT } 152 } 153 result[NX_SSS_RES_VERDICT] = verdict 154 return 0 155}