Files
hcie-rust-v3.05/hcie-fx/src/drop_shadow.rs
T

105 lines
3.5 KiB
Rust
Raw Normal View History

2026-07-09 02:59:53 +03:00
/// Drop Shadow effect generator.
///
/// Renders a drop shadow by offsetting the layer alpha, applying spread,
/// blurring, and filling with the shadow color. Supports knock-out mode.
use crate::helpers::{apply_spread, box_blur_f32};
/// Generate a drop shadow buffer.
///
/// **Purpose:**
/// Renders a drop shadow effect on the transparent background by shifting the layer alpha,
/// applying spread scaling, blurring, and coloring.
///
/// **Logic & Workflow:**
/// 1. Offset layer alpha by the specified angle and distance.
/// 2. Apply spread scaling to the offset alpha (multiplied by `spread_scale = 0.00` based on Krita tuning).
/// 3. Apply box blur for Gaussian approximation using the tuned parameters (`blur_factor = 1.5`, `passes = 1`).
/// 4. Populate the final RGBA buffer with the shadow color and the computed alpha values.
/// 5. Optionally knock out the shadow under the original opaque layer pixels.
///
/// **Arguments:**
/// * `alpha` — `&[u8]`: Layer alpha channel.
/// * `w` — `u32`: Canvas width.
/// * `h` — `u32`: Canvas height.
/// * `angle` — `f32`: Shadow angle in degrees (PSD convention: 0=down, 90=left).
/// * `distance` — `f32`: Shadow offset distance in pixels.
/// * `spread` — `f32`: Spread percentage (0.0100.0).
/// * `size` — `f32`: Blur radius.
/// * `color` — `[u8; 4]`: Shadow RGBA color.
/// * `knock_out` — `bool`: If true, shadow is hidden behind the layer.
///
/// **Returns:**
/// `Vec<u8>`: RGBA buffer with the rendered drop shadow.
///
/// **Side Effects / Dependencies:**
/// Relies on `box_blur_f32` and `apply_spread` helpers from the `helpers` module.
pub(crate) fn generate_shadow(
alpha: &[u8],
w: u32,
h: u32,
angle: f32,
distance: f32,
spread: f32,
size: f32,
color: [u8; 4],
knock_out: bool,
) -> 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![0u8; px];
for y in 0..h as i32 {
for x in 0..w as i32 {
let a = alpha[(y as u32 * w + x as u32) as usize];
if a == 0 { continue; }
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] = offset_alpha[di].max(a);
}
}
}
// MAE-optimized against Krita ground truth (768 samples, 350 combos, 800x800)
// Best: blur_factor=5.0, passes=5, spread_scale=1.00
2026-07-09 02:59:53 +03:00
let radius = size.max(0.0).round() as i32;
if radius > 0 {
let gaussian_r = (radius as f32 / 5.0).round().max(1.0) as i32;
let mut f32_buf: Vec<f32> = offset_alpha.iter().map(|&a| a as f32).collect();
for _ in 0..5 {
f32_buf = box_blur_f32(&f32_buf, w, h, gaussian_r);
}
offset_alpha = f32_buf.iter().map(|&a| (a as u8).min(255)).collect();
}
2026-07-13 20:33:39 +03:00
// Actually use the spread parameter (0.0 to 100.0) from the user AFTER the blur!
apply_spread(&mut offset_alpha, spread);
2026-07-09 02:59:53 +03:00
for i in 0..px {
let a = offset_alpha[i];
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;
}
}
if knock_out {
for i in 0..px {
if alpha[i] > 0 {
buf[i * 4 + 3] = 0;
}
}
}
buf
}