code wiki / (root) / nx_lighting_consistency.nx

nx_lighting_consistency.nx source

↩ module page · 171 lines · 6736 B

1// nx_lighting_consistency.nx -- subject lighting vs environment lighting match. 2// 3// Image #58 failure: subject (woman) appears lit by warm interior light 4// but the environment (rainy window behind) is cool daylight. Body 5// reads as "pasted in" — a compositing seam. Real cinematography keeps 6// subject + environment under the SAME light temperature + direction. 7// 8// Math: 9// Compute color temperature proxy for subject (skin region) and 10// environment (background region) via R:B ratio and mean luminance. 11// Compute light direction proxy via brightness gradient across each. 12// Score the MATCH between the two readings. 13// 14// Inputs: rgb image + skin_mask + environment bbox (background region). 15// 16// Output flat-array: 17// result[0] = subject_R_to_B_q10 warm/cool proxy 18// result[1] = env_R_to_B_q10 19// result[2] = temp_match_q10 1024 = perfect match 20// result[3] = subject_brightness mean Y in skin 21// result[4] = env_brightness 22// result[5] = direction_match_q10 1024 = same light direction 23// result[6] = composite_q10 24// result[7] = verdict 25// 0=COMPOSITING_SEAM / 1=INCONSISTENT / 2=ACCEPTABLE / 3=COHERENT 26 27// nx_safety_envelope: 28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 29// sil_target: SIL1 30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34import "nx_image.nx" 35const NX_MAGIC_1024: i64 = 1024 36const NX_MAGIC_2048: i64 = 2048 37 38const NX_LIT_RES_SUBJ_RB: i64 = 0 39const NX_LIT_RES_ENV_RB: i64 = 1 40const NX_LIT_RES_TEMP_MATCH: i64 = 2 41const NX_LIT_RES_SUBJ_Y: i64 = 3 42const NX_LIT_RES_ENV_Y: i64 = 4 43const NX_LIT_RES_DIR_MATCH: i64 = 5 44const NX_LIT_RES_COMPOSITE: i64 = 6 45const NX_LIT_RES_VERDICT: i64 = 7 46const NX_LIT_RES_FIELDS: i64 = 8 47 48const NX_LIT_V_COMPOSITING_SEAM: i64 = 0 49const NX_LIT_V_INCONSISTENT: i64 = 1 50const NX_LIT_V_ACCEPTABLE: i64 = 2 51const NX_LIT_V_COHERENT: i64 = 3 52 53// Mean R:B ratio in Q10 + mean Y over masked region. 54// Writes out[0] = R/B q10, out[1] = mean Y. 55func nx_lit_mean_temp_and_y(rgb: *Image, mask: *Image, 56 x0: i64, y0: i64, x1: i64, y1: i64, 57 use_mask: i64, out: *i64) -> i64 { 58 var sum_r: i64 = 0 59 var sum_b: i64 = 0 60 var sum_y: i64 = 0 61 var n: i64 = 0 62 var y: i64 = y0 63 while y <= y1 { 64 if y >= 0 { if y < rgb.height { 65 var x: i64 = x0 66 while x <= x1 { 67 if x >= 0 { if x < rgb.width { 68 var include: i64 = 1 69 if use_mask != 0 { 70 if nx_image_get(mask, x, y, 0) == 0 { include = 0 } 71 } 72 if include != 0 { 73 let r: i64 = nx_image_get(rgb, x, y, 0) 74 let g: i64 = nx_image_get(rgb, x, y, 1) 75 let b: i64 = nx_image_get(rgb, x, y, 2) 76 sum_r = sum_r + r 77 sum_b = sum_b + b 78 sum_y = sum_y + (77 * r + 150 * g + 29 * b) / 256 79 n = n + 1 80 } 81 } } 82 x = x + 1 83 } 84 } } 85 y = y + 1 86 } 87 if n == 0 { 88 out[0] = NX_MAGIC_1024 89 out[1] = 0 90 return 0 91 } 92 let mean_r: i64 = sum_r / n 93 let mean_b: i64 = sum_b / n 94 if mean_b == 0 { 95 out[0] = NX_MAGIC_2048 96 } else { 97 out[0] = (mean_r * NX_MAGIC_1024) / mean_b 98 } 99 out[1] = sum_y / n 100 return n 101} 102 103// Light direction proxy: mean Y of top half - mean Y of bottom half. 104// Positive = lit from above; negative = lit from below. 105func nx_lit_direction_y(rgb: *Image, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 { 106 let mid: i64 = (y0 + y1) / 2 107 let mv_top: *i64 = (sys_mmap(2 * 8 + 16)) as *i64 108 let mv_bot: *i64 = (sys_mmap(2 * 8 + 16)) as *i64 109 let m: *Image = (0 as *Image) 110 nx_lit_mean_temp_and_y(rgb, m, x0, y0, x1, mid, 0, mv_top) 111 nx_lit_mean_temp_and_y(rgb, m, x0, mid + 1, x1, y1, 0, mv_bot) 112 return mv_top[1] - mv_bot[1] 113} 114 115// Score lighting consistency between subject and environment. 116// bbox_pack (8 entries): 117// [0..3] subject x0,y0,x1,y1 (mask used; pixels OUTSIDE skin ignored) 118// [4..7] env x0,y0,x1,y1 (no mask; all pixels in band considered) 119func nx_lighting_consistency(rgb: *Image, skin_mask: *Image, 120 bbox_pack: *i64, result: *i64) -> i64 { 121 var i: i64 = 0 122 while i < NX_LIT_RES_FIELDS { result[i] = 0; i = i + 1 } 123 124 let subj: *i64 = (sys_mmap(2 * 8 + 16)) as *i64 125 let env: *i64 = (sys_mmap(2 * 8 + 16)) as *i64 126 nx_lit_mean_temp_and_y(rgb, skin_mask, 127 bbox_pack[0], bbox_pack[1], bbox_pack[2], bbox_pack[3], 128 1, subj) 129 let no_mask: *Image = (0 as *Image) 130 nx_lit_mean_temp_and_y(rgb, no_mask, 131 bbox_pack[4], bbox_pack[5], bbox_pack[6], bbox_pack[7], 132 0, env) 133 134 let subj_rb: i64 = subj[0] 135 let env_rb: i64 = env[0] 136 let subj_y: i64 = subj[1] 137 let env_y: i64 = env[1] 138 result[NX_LIT_RES_SUBJ_RB] = subj_rb 139 result[NX_LIT_RES_ENV_RB] = env_rb 140 result[NX_LIT_RES_SUBJ_Y] = subj_y 141 result[NX_LIT_RES_ENV_Y] = env_y 142 143 // Temp match: 1024 - |subj_rb - env_rb| / 8 (saturating). 144 var rb_diff: i64 = subj_rb - env_rb 145 if rb_diff < 0 { rb_diff = -rb_diff } 146 let temp_pen: i64 = rb_diff / 4 // 256 q10 diff = 64 penalty 147 var temp_match: i64 = NX_MAGIC_1024 - temp_pen 148 if temp_match < 0 { temp_match = 0 } 149 result[NX_LIT_RES_TEMP_MATCH] = temp_match 150 151 // Direction match: light direction Y-gradient consistency. 152 let subj_dir: i64 = nx_lit_direction_y(rgb, bbox_pack[0], bbox_pack[1], 153 bbox_pack[2], bbox_pack[3]) 154 let env_dir: i64 = nx_lit_direction_y(rgb, bbox_pack[4], bbox_pack[5], 155 bbox_pack[6], bbox_pack[7]) 156 var dir_diff: i64 = subj_dir - env_dir 157 if dir_diff < 0 { dir_diff = -dir_diff } 158 var dir_match: i64 = NX_MAGIC_1024 - dir_diff * 8 159 if dir_match < 0 { dir_match = 0 } 160 result[NX_LIT_RES_DIR_MATCH] = dir_match 161 162 let composite: i64 = (temp_match * 60 + dir_match * 40) / 100 163 result[NX_LIT_RES_COMPOSITE] = composite 164 165 var verdict: i64 = NX_LIT_V_COMPOSITING_SEAM 166 if composite >= 410 { verdict = NX_LIT_V_INCONSISTENT } 167 if composite >= 614 { verdict = NX_LIT_V_ACCEPTABLE } 168 if composite >= 819 { verdict = NX_LIT_V_COHERENT } 169 result[NX_LIT_RES_VERDICT] = verdict 170 return 0 171}