code wiki / (root) / nx_lash_transition.nx

nx_lash_transition.nx source

↩ module page · 108 lines · 3966 B

1// nx_lash_transition.nx -- eyelid-to-lash edge gradient softness. 2// 3// Mannequin face marker: lashes meet eyelid as a hard sharp line 4// (rendered as a discrete edge). Real lashes fade softly into the 5// eyelid skin tone over 2-3 pixels of micro-blur. 6// 7// Math: 8// Within the eyelid band (3-5 px ABOVE the iris top), measure 9// per-column vertical luminance gradient |Y(y) - Y(y-1)| and 10// accumulate. High mean gradient = sharp line = MANNEQUIN. 11// Low mean gradient = soft fade = NATURAL. 12// 13// Output flat-array: 14// result[0] = pixel_count 15// result[1] = mean_gradient 16// result[2] = max_gradient 17// result[3] = hard_transitions (count of gradients > HARD_THRESH) 18// result[4] = verdict (0=MANNEQUIN, 1=AMBIGUOUS, 2=NATURAL) 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "nx_syscalls.nx" 27import "nx_image.nx" 28 29const NX_LASH_RES_PIXELS: i64 = 0 30const NX_LASH_RES_MEAN_GRAD: i64 = 1 31const NX_LASH_RES_MAX_GRAD: i64 = 2 32const NX_LASH_RES_HARD_TRANS: i64 = 3 33const NX_LASH_RES_VERDICT: i64 = 4 34const NX_LASH_RES_FIELDS: i64 = 5 35 36const NX_LASH_VERDICT_MANNEQUIN: i64 = 0 37const NX_LASH_VERDICT_AMBIGUOUS: i64 = 1 38const NX_LASH_VERDICT_NATURAL: i64 = 2 39 40// Hard-edge threshold: gradient > 60 = mannequin-like sharp line. 41const NX_LASH_HARD_THRESH: i64 = 60 42const NX_LASH_MEAN_NATURAL_MAX: i64 = 25 // mean gradient ≤ 25 = soft NATURAL 43const NX_LASH_MEAN_MANNEQUIN_MIN: i64 = 45 // mean gradient ≥ 45 = sharp MANNEQUIN 44 45// Compute luminance at (x, y). 46func nx_lash_lum(rgb: *Image, x: i64, y: i64) -> i64 { 47 if x < 0 { return 0 } 48 if y < 0 { return 0 } 49 if x >= rgb.width { return 0 } 50 if y >= rgb.height { return 0 } 51 let r: i64 = nx_image_get(rgb, x, y, 0) 52 let g: i64 = nx_image_get(rgb, x, y, 1) 53 let b: i64 = nx_image_get(rgb, x, y, 2) 54 return (77 * r + 150 * g + 29 * b) / 256 55} 56 57// Score lash-eyelid transition in the band above iris top. 58// bbox layout (4 entries): iris_x0, iris_y0, iris_x1, iris_y1 59// band_height: 4 = scan 4 pixels above iris top 60func nx_lash_transition(rgb: *Image, bbox: *i64, band_height: i64, 61 result: *i64) -> i64 { 62 var i: i64 = 0 63 while i < NX_LASH_RES_FIELDS { result[i] = 0; i = i + 1 } 64 let ix0: i64 = bbox[0] 65 let iy0: i64 = bbox[1] 66 let ix1: i64 = bbox[2] 67 if ix1 <= ix0 { return 1 } 68 if band_height <= 0 { return 2 } 69 70 let band_y0: i64 = iy0 - band_height 71 let band_y1: i64 = iy0 72 if band_y0 < 1 { return 3 } // need y-1 to exist 73 74 var sum_grad: i64 = 0 75 var max_grad: i64 = 0 76 var pixels: i64 = 0 77 var hard: i64 = 0 78 79 var y: i64 = band_y0 80 while y < band_y1 { 81 var x: i64 = ix0 82 while x <= ix1 { 83 let y_curr: i64 = nx_lash_lum(rgb, x, y) 84 let y_above: i64 = nx_lash_lum(rgb, x, y - 1) 85 var grad: i64 = y_curr - y_above 86 if grad < 0 { grad = -grad } 87 sum_grad = sum_grad + grad 88 if grad > max_grad { max_grad = grad } 89 if grad > NX_LASH_HARD_THRESH { hard = hard + 1 } 90 pixels = pixels + 1 91 x = x + 1 92 } 93 y = y + 1 94 } 95 96 result[NX_LASH_RES_PIXELS] = pixels 97 if pixels == 0 { return 0 } 98 let mean_grad: i64 = sum_grad / pixels 99 result[NX_LASH_RES_MEAN_GRAD] = mean_grad 100 result[NX_LASH_RES_MAX_GRAD] = max_grad 101 result[NX_LASH_RES_HARD_TRANS] = hard 102 103 var verdict: i64 = NX_LASH_VERDICT_AMBIGUOUS 104 if mean_grad <= NX_LASH_MEAN_NATURAL_MAX { verdict = NX_LASH_VERDICT_NATURAL } 105 else { if mean_grad >= NX_LASH_MEAN_NATURAL_MAX { if mean_grad >= NX_LASH_MEAN_MANNEQUIN_MIN { verdict = NX_LASH_VERDICT_MANNEQUIN } } } 106 result[NX_LASH_RES_VERDICT] = verdict 107 return 0 108}