shadow.rs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use super::shape::sexpr_add;
  19. use crate::expr::{Op, SExprCode};
  20. /// Gaussian CDF `0.5 * (1 + erf(dist * sqrt(2) / blur))`: coverage of the
  21. /// box interior at `dist` px from an edge.
  22. fn edge_cdf(dist: f32, blur: f32) -> f32 {
  23. if blur <= 0. {
  24. return if dist >= 0. { 1. } else { 0. };
  25. }
  26. let z = dist * std::f32::consts::SQRT_2 / blur;
  27. // This magic is Abramowitz & Stegun 7.1.26, max abs error 1.5e-7
  28. let t = 1. / (1. + 0.327_591_1 * z.abs());
  29. let approx = 1. -
  30. t * (0.254_829_592 +
  31. t * (-0.284_496_736 +
  32. t * (1.421_413_741 + t * (-1.453_152_027 + t * 1.061_405_429)))) *
  33. (-z * z).exp();
  34. let erf = if z < 0. { -approx } else { approx };
  35. 0.5 * (1. + erf)
  36. }
  37. /// Knot offsets from one edge, one ring every `blur / 4` px. The fade is
  38. /// gone by ~2 * blur, so 8 rings cover it.
  39. pub(crate) fn ring_knots(blur: f32) -> Vec<f32> {
  40. let n = 8;
  41. let spacing = blur / 4.;
  42. (0..=n).map(|k| k as f32 * spacing).collect()
  43. }
  44. /// Inset shadow alpha at a knot.
  45. pub(crate) fn inset_alpha(dx: f32, dy: f32, blur: f32, base: f32) -> f32 {
  46. let a = base * (1. - edge_cdf(dx, blur) * edge_cdf(dy, blur));
  47. if a < 1e-4 * base {
  48. 0.
  49. } else {
  50. a
  51. }
  52. }
  53. /// Symbolic position for every knot on one axis. Returns `(position, off)`
  54. /// so callers know each knot's distance from its edge.
  55. pub(crate) fn knot_sexprs(offs: &[f32], e1: &SExprCode, e2: &SExprCode) -> Vec<(SExprCode, f32)> {
  56. let center = sexpr_add(
  57. e1.clone(),
  58. Op::Mul((
  59. Box::new(Op::Sub((Box::new(e2[0].clone()), Box::new(e1[0].clone())))),
  60. Box::new(Op::ConstFloat32(0.5)),
  61. )),
  62. )
  63. .unwrap();
  64. let n = offs.len();
  65. let near = (0..n).map(|k| {
  66. let raw = sexpr_add(e1.clone(), Op::ConstFloat32(offs[k])).unwrap();
  67. let head = raw.into_iter().next().unwrap();
  68. (vec![Op::Min((Box::new(head), Box::new(center[0].clone())))], offs[k])
  69. });
  70. let far = (0..n).rev().map(|k| {
  71. let raw = sexpr_add(e2.clone(), Op::ConstFloat32(-offs[k])).unwrap();
  72. let head = raw.into_iter().next().unwrap();
  73. (vec![Op::Max((Box::new(head), Box::new(center[0].clone())))], offs[k])
  74. });
  75. near.chain(far).collect()
  76. }