/// Inner Shadow effect generator. /// /// Renders an inner shadow by offsetting the inverted layer alpha, /// blurring, and masking with the original alpha. MAE-optimized /// parameters for Photoshop-compatible output. use crate::helpers::{apply_spread, box_blur_f32}; /// Generate an inner shadow buffer. /// /// **Algorithm:** /// 1. Invert layer alpha (255 - alpha) /// 2. Offset inverted alpha by (distance, angle) /// 3. Box blur 2-pass (blur_factor=10.0) /// 4. Mask with original alpha (mask_strength=0.50) /// 5. Fill with shadow color /// /// **MAE-optimized parameters** (from grid search on 1536 PSD samples): /// - blur_factor = 10.0 /// - passes = 2 /// - choke_scale = 0.0 (choke disabled) /// - mask_strength = 0.50 /// /// **Arguments:** /// * `alpha` — Layer alpha channel /// * `w`, `h` — Canvas dimensions /// * `angle` — Shadow angle in degrees /// * `distance` — Shadow offset distance in pixels /// * `choke` — Choke amount (currently unused, kept for API compatibility) /// * `size` — Blur radius /// * `color` — Shadow RGBA color /// /// **Returns:** /// RGBA buffer with the rendered inner shadow. pub(crate) fn generate_inner_shadow( alpha: &[u8], w: u32, h: u32, angle: f32, distance: f32, choke: f32, size: f32, color: [u8; 4], ) -> Vec { let px = (w * h) as usize; let mut buf = vec![0u8; px * 4]; let rad = angle.to_radians(); let dx_raw = (-rad.cos() * distance).round() as i32; let dy_raw = (rad.sin() * distance).round() as i32; let mut offset_alpha = vec![255u8; px]; for y in 0..h as i32 { for x in 0..w as i32 { let orig_i = (y as u32 * w + x as u32) as usize; let val = 255 - alpha[orig_i]; let dx = x + dx_raw; let dy = y + dy_raw; if dx >= 0 && dx < w as i32 && dy >= 0 && dy < h as i32 { let di = (dy as u32 * w + dx as u32) as usize; offset_alpha[di] = val; } } } apply_spread(&mut offset_alpha, choke); let radius = size.max(0.0).round() as i32; if radius > 0 { let gaussian_r = (radius as f32 / 10.0).round().max(1.0) as i32; let mut f32_buf: Vec = offset_alpha.iter().map(|&a| a as f32).collect(); f32_buf = box_blur_f32(&f32_buf, w, h, gaussian_r); f32_buf = box_blur_f32(&f32_buf, w, h, gaussian_r); offset_alpha = f32_buf.iter().map(|&a| (a as u8).min(255)).collect(); } for i in 0..px { let a = ((offset_alpha[i] as f32 * 0.50) as u32 * alpha[i] as u32 / 255) as u8; if a > 0 { buf[i * 4] = color[0]; buf[i * 4 + 1] = color[1]; buf[i * 4 + 2] = color[2]; buf[i * 4 + 3] = a; } } buf }