Files
hcie-rust-v3.05/hcie-fx/src/helpers.rs
T
2026-07-09 02:59:53 +03:00

262 lines
8.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![allow(dead_code)]
/// Shared helper functions for layer effect rendering.
///
/// Provides alpha extraction, compositing, spread/choke mapping,
/// smoothstep math, and box blur algorithms used across all effect modules.
/// Extract the alpha channel from an RGBA pixel buffer.
///
/// **Arguments:**
/// * `pixels` — Flat RGBA buffer (4 bytes per pixel)
/// * `px_count` — Total number of pixels
///
/// **Returns:**
/// A `Vec<u8>` containing one alpha byte per pixel.
pub(crate) fn extract_alpha(pixels: &[u8], px_count: usize) -> Vec<u8> {
let mut alpha = vec![0u8; px_count];
for i in 0..px_count {
alpha[i] = pixels[i * 4 + 3];
}
alpha
}
/// Composite an effect buffer onto a destination buffer using standard blend math.
///
/// Skips fully transparent source pixels. Used for "behind" effects (drop shadow,
/// outer glow) and standard inside effects (bevel, satin, inner glow, inner shadow).
///
/// **Arguments:**
/// * `dst` — Destination RGBA buffer (modified in-place)
/// * `effect_buf` — Effect RGBA buffer to composite
/// * `blend_mode` — Blend mode for compositing
/// * `opacity` — Effect opacity (0.01.0)
/// * `px_count` — Total number of pixels
pub(crate) fn composite_effect(
dst: &mut [u8],
effect_buf: &[u8],
blend_mode: hcie_blend::BlendMode,
opacity: f32,
px_count: usize,
) {
for i in 0..px_count {
let dst_px = [dst[i * 4], dst[i * 4 + 1], dst[i * 4 + 2], dst[i * 4 + 3]];
let src_px = [effect_buf[i * 4], effect_buf[i * 4 + 1], effect_buf[i * 4 + 2], effect_buf[i * 4 + 3]];
if src_px[3] == 0 { continue; }
let out = hcie_blend::blend_pixels(dst_px, src_px, blend_mode, opacity);
dst[i * 4..i * 4 + 4].copy_from_slice(&out);
}
}
/// Composite an inside effect buffer, preserving the original layer alpha.
///
/// For Normal blend: lerps between dst and src color based on effect alpha.
/// For other blend modes: uses standard blend_pixels.
/// Preserves the original alpha channel from the destination.
///
/// **Arguments:**
/// * `dst` — Destination RGBA buffer (modified in-place)
/// * `effect_buf` — Effect RGBA buffer to composite
/// * `blend_mode` — Blend mode for compositing
/// * `opacity` — Effect opacity (0.01.0)
/// * `px_count` — Total number of pixels
pub(crate) fn composite_inside_effect(
dst: &mut [u8],
effect_buf: &[u8],
blend_mode: hcie_blend::BlendMode,
opacity: f32,
px_count: usize,
) {
for i in 0..px_count {
let dst_px = [dst[i * 4], dst[i * 4 + 1], dst[i * 4 + 2], dst[i * 4 + 3]];
let src_px = [effect_buf[i * 4], effect_buf[i * 4 + 1], effect_buf[i * 4 + 2], effect_buf[i * 4 + 3]];
if src_px[3] == 0 || dst_px[3] == 0 { continue; }
let eff_sa = (src_px[3] as f32 / 255.0) * opacity;
if blend_mode == hcie_blend::BlendMode::Normal {
if eff_sa >= 1.0 {
dst[i * 4] = src_px[0];
dst[i * 4 + 1] = src_px[1];
dst[i * 4 + 2] = src_px[2];
} else {
let dst_r = dst_px[0] as f32;
let dst_g = dst_px[1] as f32;
let dst_b = dst_px[2] as f32;
let src_r = src_px[0] as f32;
let src_g = src_px[1] as f32;
let src_b = src_px[2] as f32;
dst[i * 4] = (dst_r * (1.0 - eff_sa) + src_r * eff_sa).round() as u8;
dst[i * 4 + 1] = (dst_g * (1.0 - eff_sa) + src_g * eff_sa).round() as u8;
dst[i * 4 + 2] = (dst_b * (1.0 - eff_sa) + src_b * eff_sa).round() as u8;
}
} else {
let out = hcie_blend::blend_pixels(dst_px, src_px, blend_mode, opacity);
dst[i * 4..i * 4 + 4].copy_from_slice(&out);
}
}
}
/// Apply smooth contrast scaling for spread/choke mapping.
///
/// Boosts alpha values based on spread percentage. Used by drop shadow
/// before blur to create harder or softer shadow edges.
///
/// **Arguments:**
/// * `alpha` — Alpha buffer to modify in-place
/// * `spread` — Spread percentage (0.0100.0)
pub(crate) fn apply_spread(alpha: &mut [u8], spread: f32) {
if spread <= 0.0 {
return;
}
if spread >= 100.0 {
for a in alpha.iter_mut() {
if *a > 0 { *a = 255; }
}
return;
}
let factor = 1.0 / (1.0 - spread / 100.0);
for a in alpha.iter_mut() {
*a = ((*a as f32 * factor).round().min(255.0)) as u8;
}
}
/// Hermite S-curve smoothstep function for soft slope shading.
pub(crate) fn smoothstep(t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
/// O(W*H) sliding-window box blur for f32 height fields.
///
/// Used as a building block for Gaussian blur approximation (multiple passes).
/// This is the public version used by `tuned.rs` and all effect modules.
///
/// **Arguments:**
/// * `data` — Input f32 buffer
/// * `w` — Image width
/// * `h` — Image height
/// * `radius` — Blur radius
///
/// **Returns:**
/// Blurred f32 buffer of the same size.
pub fn box_blur_f32(data: &[f32], w: u32, h: u32, radius: i32) -> Vec<f32> {
let n = (w * h) as usize;
let iw = w as usize;
let ih = h as usize;
let r = radius as usize;
let mut tmp = vec![0.0f32; n];
let mut out = vec![0.0f32; n];
for y in 0..ih {
let mut sum: f32 = 0.0;
let mut count: usize = 0;
let row = y * iw;
for nx in 0..=(r.min(iw - 1)) {
sum += data[row + nx];
count += 1;
}
tmp[row] = sum / count as f32;
for x in 1..iw {
let add_x = x + r;
if add_x < iw {
sum += data[row + add_x];
count += 1;
}
let sub_x = x as isize - r as isize - 1;
if sub_x >= 0 {
sum -= data[row + sub_x as usize];
count -= 1;
}
tmp[row + x] = sum / count as f32;
}
}
for x in 0..iw {
let mut sum: f32 = 0.0;
let mut count: usize = 0;
for ny in 0..=(r.min(ih - 1)) {
sum += tmp[ny * iw + x];
count += 1;
}
out[x] = sum / count as f32;
for y in 1..ih {
let add_y = y + r;
if add_y < ih {
sum += tmp[add_y * iw + x];
count += 1;
}
let sub_y = y as isize - r as isize - 1;
if sub_y >= 0 {
sum -= tmp[sub_y as usize * iw + x];
count -= 1;
}
out[y * iw + x] = sum / count as f32;
}
}
out
}
/// O(W*H) sliding-window box blur for u8 alpha masks.
pub(crate) fn box_blur(data: &[u8], w: u32, h: u32, radius: i32) -> Vec<u8> {
let n = (w * h) as usize;
let iw = w as usize;
let ih = h as usize;
let r = radius as usize;
let mut tmp = vec![0u8; n];
let mut out = vec![0u8; n];
for y in 0..ih {
let mut sum: u32 = 0;
let mut count: u32 = 0;
let row = y * iw;
for nx in 0..=(r.min(iw - 1)) {
sum += data[row + nx] as u32;
count += 1;
}
tmp[row] = (sum / count) as u8;
for x in 1..iw {
let add_x = x + r;
if add_x < iw {
sum += data[row + add_x] as u32;
count += 1;
}
let sub_x = x as isize - r as isize - 1;
if sub_x >= 0 {
sum -= data[row + sub_x as usize] as u32;
count -= 1;
}
tmp[row + x] = (sum / count) as u8;
}
}
for x in 0..iw {
let mut sum: u32 = 0;
let mut count: u32 = 0;
for ny in 0..=(r.min(ih - 1)) {
sum += tmp[ny * iw + x] as u32;
count += 1;
}
out[x] = (sum / count) as u8;
for y in 1..ih {
let add_y = y + r;
if add_y < ih {
sum += tmp[add_y * iw + x] as u32;
count += 1;
}
let sub_y = y as isize - r as isize - 1;
if sub_y >= 0 {
sum -= tmp[sub_y as usize * iw + x] as u32;
count -= 1;
}
out[y * iw + x] = (sum / count) as u8;
}
}
out
}