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}