code wiki / (root) / nx_shadow_integration.nx

nx_shadow_integration.nx source

↩ module page · 112 lines · 4526 B

1// nx_shadow_integration.nx -- Best Compositing (shadow half). 2// 3// Real subjects in real scenes cast shadows onto environment objects. 4// AI renders often drop the shadow → subject reads "pasted on". 5// Detector measures: in the region around the subject's silhouette 6// edge, is there a darkening band ON the environment side that 7// matches what a subject-cast shadow would look like? 8// 9// Math: 10// 1. Define a band along the subject's outer edge on the BG side. 11// 2. Compare mean luminance INSIDE that band vs background baseline 12// luminance further away. 13// 3. Real shadow: band is 10-25% darker than baseline. 14// 4. No shadow: band is similar to baseline. 15// 16// Inputs: gray image + subject_mask + env_baseline_bbox. 17// 18// Output flat-array: 19// result[0] band_mean_Y 20// result[1] baseline_mean_Y 21// result[2] shadow_depth_pct (baseline - band) * 100 / baseline 22// result[3] verdict 23 24// nx_safety_envelope: 25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 26// sil_target: SIL1 27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 28// verdict: NOT_YET_EVALUATED 29 30import "nx_syscalls.nx" 31import "nx_image.nx" 32 33const NX_SHADOW_RES_BAND_Y: i64 = 0 34const NX_SHADOW_RES_BASELINE_Y: i64 = 1 35const NX_SHADOW_RES_DEPTH_PCT: i64 = 2 36const NX_SHADOW_RES_VERDICT: i64 = 3 37const NX_SHADOW_RES_FIELDS: i64 = 4 38 39const NX_SHADOW_V_MISSING: i64 = 0 // < 5% depth 40const NX_SHADOW_V_WEAK: i64 = 1 // 5-10% 41const NX_SHADOW_V_NATURAL: i64 = 2 // 10-25% 42const NX_SHADOW_V_OVERDARK: i64 = 3 // > 25% 43 44// Mean luminance over bbox where mask differs from match_value. 45// pass_through_value 0: count pixels where mask == 0 (env) 46// pass_through_value 255: count pixels where mask == 255 (subject) 47func nx_shadow_mean_y_masked(rgb: *Image, mask: *Image, 48 x0: i64, y0: i64, x1: i64, y1: i64, 49 match_value: i64) -> i64 { 50 var sum: i64 = 0 51 var n: i64 = 0 52 var y: i64 = y0 53 while y <= y1 { 54 if y >= 0 { if y < rgb.height { 55 var x: i64 = x0 56 while x <= x1 { 57 if x >= 0 { if x < rgb.width { 58 let m: i64 = nx_image_get(mask, x, y, 0) 59 var include: i64 = 0 60 if match_value == 0 { if m == 0 { include = 1 } } 61 if match_value == 255 { if m == 255 { include = 1 } } 62 if include != 0 { 63 let r: i64 = nx_image_get(rgb, x, y, 0) 64 let g: i64 = nx_image_get(rgb, x, y, 1) 65 let b: i64 = nx_image_get(rgb, x, y, 2) 66 sum = sum + (77 * r + 150 * g + 29 * b) / 256 67 n = n + 1 68 } 69 } } 70 x = x + 1 71 } 72 } } 73 y = y + 1 74 } 75 if n == 0 { return -1 } 76 return sum / n 77} 78 79// Score shadow integration. 80// bbox_pack: [band_x0, band_y0, band_x1, band_y1, 81// baseline_x0, baseline_y0, baseline_x1, baseline_y1] 82// The band is a thin region adjacent to the subject's silhouette. 83// The baseline is further out, same env material. 84func nx_shadow_integration(rgb: *Image, subject_mask: *Image, 85 bbox_pack: *i64, result: *i64) -> i64 { 86 var i: i64 = 0 87 while i < NX_SHADOW_RES_FIELDS { result[i] = 0; i = i + 1 } 88 89 // Band: env pixels (mask == 0) within shadow band. 90 let band_y: i64 = nx_shadow_mean_y_masked(rgb, subject_mask, 91 bbox_pack[0], bbox_pack[1], 92 bbox_pack[2], bbox_pack[3], 0) 93 let base_y: i64 = nx_shadow_mean_y_masked(rgb, subject_mask, 94 bbox_pack[4], bbox_pack[5], 95 bbox_pack[6], bbox_pack[7], 0) 96 if band_y < 0 { return 1 } 97 if base_y < 0 { return 2 } 98 if base_y == 0 { return 3 } 99 100 result[NX_SHADOW_RES_BAND_Y] = band_y 101 result[NX_SHADOW_RES_BASELINE_Y] = base_y 102 103 let depth_pct: i64 = ((base_y - band_y) * 100) / base_y 104 result[NX_SHADOW_RES_DEPTH_PCT] = depth_pct 105 106 var verdict: i64 = NX_SHADOW_V_MISSING 107 if depth_pct >= 5 { verdict = NX_SHADOW_V_WEAK } 108 if depth_pct >= 10 { verdict = NX_SHADOW_V_NATURAL } 109 if depth_pct > 25 { verdict = NX_SHADOW_V_OVERDARK } 110 result[NX_SHADOW_RES_VERDICT] = verdict 111 return 0 112}