90 lines
2.7 KiB
Rust
90 lines
2.7 KiB
Rust
/// 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<u8> {
|
|
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<f32> = 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
|
|
}
|