/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use super::shape::sexpr_add; use crate::expr::{Op, SExprCode}; /// Gaussian CDF `0.5 * (1 + erf(dist * sqrt(2) / blur))`: coverage of the /// box interior at `dist` px from an edge. fn edge_cdf(dist: f32, blur: f32) -> f32 { if blur <= 0. { return if dist >= 0. { 1. } else { 0. }; } let z = dist * std::f32::consts::SQRT_2 / blur; // This magic is Abramowitz & Stegun 7.1.26, max abs error 1.5e-7 let t = 1. / (1. + 0.327_591_1 * z.abs()); let approx = 1. - t * (0.254_829_592 + t * (-0.284_496_736 + t * (1.421_413_741 + t * (-1.453_152_027 + t * 1.061_405_429)))) * (-z * z).exp(); let erf = if z < 0. { -approx } else { approx }; 0.5 * (1. + erf) } /// Knot offsets from one edge, one ring every `blur / 4` px. The fade is /// gone by ~2 * blur, so 8 rings cover it. pub(crate) fn ring_knots(blur: f32) -> Vec { let n = 8; let spacing = blur / 4.; (0..=n).map(|k| k as f32 * spacing).collect() } /// Inset shadow alpha at a knot. pub(crate) fn inset_alpha(dx: f32, dy: f32, blur: f32, base: f32) -> f32 { let a = base * (1. - edge_cdf(dx, blur) * edge_cdf(dy, blur)); if a < 1e-4 * base { 0. } else { a } } /// Symbolic position for every knot on one axis. Returns `(position, off)` /// so callers know each knot's distance from its edge. pub(crate) fn knot_sexprs(offs: &[f32], e1: &SExprCode, e2: &SExprCode) -> Vec<(SExprCode, f32)> { let center = sexpr_add( e1.clone(), Op::Mul(( Box::new(Op::Sub((Box::new(e2[0].clone()), Box::new(e1[0].clone())))), Box::new(Op::ConstFloat32(0.5)), )), ) .unwrap(); let n = offs.len(); let near = (0..n).map(|k| { let raw = sexpr_add(e1.clone(), Op::ConstFloat32(offs[k])).unwrap(); let head = raw.into_iter().next().unwrap(); (vec![Op::Min((Box::new(head), Box::new(center[0].clone())))], offs[k]) }); let far = (0..n).rev().map(|k| { let raw = sexpr_add(e2.clone(), Op::ConstFloat32(-offs[k])).unwrap(); let head = raw.into_iter().next().unwrap(); (vec![Op::Max((Box::new(head), Box::new(center[0].clone())))], offs[k]) }); near.chain(far).collect() }