mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-10 20:29:33 -06:00
Merge pull request #2057 from aleksei-suholita/perf/glass-dispersion-narrow-window
M8: glass takes one sample where its colours split under 1/256 px
This commit is contained in:
+2
-1
File diff suppressed because one or more lines are too long
@@ -381,15 +381,27 @@ pub(crate) fn bend_at(x: f32, depth: f32, bevel: f32, refraction: f32) -> f32 {
|
||||
core + (rim - core) * u
|
||||
}
|
||||
|
||||
/// Below this split (pixels: the move's length × `k`) the dispersion window takes one sample: its
|
||||
/// steps lie within 1/256 px of the moved point, so one sample is within one level (of 255) of the
|
||||
/// window even across the hardest edge, from black to white or opaque to clear in a pixel.
|
||||
const ONE_SAMPLE_SPLIT_PX: f32 = 1.0 / 256.0;
|
||||
|
||||
/// The backdrop seen at pixel coordinates (`x`, `y`) moved by (`dx`, `dy`), its channels split by
|
||||
/// `k` (dispersion, 0–1): red moves up to `k` further, blue up to `k` less, each smeared along the
|
||||
/// move by a triangular window of [`DISPERSION_STEPS`]. One sample when `k` is too small to show.
|
||||
/// move by a triangular window of [`DISPERSION_STEPS`]. One sample when the split is too small to
|
||||
/// show: `k` under 0.001, or the channels under [`ONE_SAMPLE_SPLIT_PX`] apart (where the curve
|
||||
/// flattens out and the move is short).
|
||||
fn dispersed(frosted: &Pixmap, x: f32, y: f32, dx: f32, dy: f32, k: f32) -> [f32; 4] {
|
||||
let at = |s: f32| sample(frosted, x + dx * s, y + dy * s);
|
||||
if k < 0.001 {
|
||||
let s = at(1.0);
|
||||
if k < 0.001 || (dx * dx + dy * dy).sqrt() * k < ONE_SAMPLE_SPLIT_PX {
|
||||
let s = sample(frosted, x + dx, y + dy);
|
||||
return [s[0].min(s[3]), s[1].min(s[3]), s[2].min(s[3]), s[3]];
|
||||
}
|
||||
dispersion_window(frosted, x, y, dx, dy, k)
|
||||
}
|
||||
|
||||
/// [`dispersed`]'s triangular window, every step sampled.
|
||||
fn dispersion_window(frosted: &Pixmap, x: f32, y: f32, dx: f32, dy: f32, k: f32) -> [f32; 4] {
|
||||
let at = |s: f32| sample(frosted, x + dx * s, y + dy * s);
|
||||
let last = (DISPERSION_STEPS - 1) as f32;
|
||||
let (mut acc, mut sum) = ([0.0f32; 4], 0.0f32);
|
||||
for i in 1..DISPERSION_STEPS - 1 {
|
||||
@@ -1053,6 +1065,40 @@ mod tests {
|
||||
assert_eq!(spread[2], 0.0);
|
||||
}
|
||||
|
||||
/// Where the channels split under [`ONE_SAMPLE_SPLIT_PX`], one sample stands in for the window
|
||||
/// within one level, even over the hardest backdrops: 1 px columns of black and white, and of
|
||||
/// white and nothing.
|
||||
#[test]
|
||||
fn a_split_too_narrow_to_show_takes_one_sample() {
|
||||
for clear in [false, true] {
|
||||
let mut p = Pixmap::new(64, 4).unwrap();
|
||||
for (i, px) in p.data_mut().chunks_exact_mut(4).enumerate() {
|
||||
let on = i % 2 == 1;
|
||||
let v = if on { 255 } else { 0 };
|
||||
px.copy_from_slice(&[v, v, v, if clear { v } else { 255 }]);
|
||||
}
|
||||
let mut worst = 0.0f32;
|
||||
for step in 0..400 {
|
||||
let x = 20.0 + step as f32 / 100.0;
|
||||
// Along the columns' rows and across them, and out past the backdrop's last row.
|
||||
for (dx, dy) in [(2.0f32, 0.0f32), (-2.0, 0.0), (1.2, 1.6), (0.0, 2.0), (40.0, 0.0)] {
|
||||
let k = ONE_SAMPLE_SPLIT_PX * 0.999 / (dx * dx + dy * dy).sqrt();
|
||||
let one = dispersed(&p, x, 2.0, dx, dy, k);
|
||||
assert_eq!(one, dispersed(&p, x, 2.0, dx, dy, 0.0), "one sample at {x}");
|
||||
let window = dispersion_window(&p, x, 2.0, dx, dy, k);
|
||||
worst = one.iter().zip(window).fold(worst, |m, (a, b)| m.max((a - b).abs()));
|
||||
}
|
||||
}
|
||||
assert!(worst <= 1.0, "clear {clear}: {worst}");
|
||||
}
|
||||
// A wider split still takes the window.
|
||||
let mut p = Pixmap::new(64, 4).unwrap();
|
||||
p.data_mut().chunks_exact_mut(4).step_by(2).for_each(|px| px.copy_from_slice(&[255; 4]));
|
||||
let k = ONE_SAMPLE_SPLIT_PX * 2.0 / 2.0;
|
||||
assert_eq!(dispersed(&p, 20.3, 2.0, 2.0, 0.0, k), dispersion_window(&p, 20.3, 2.0, 2.0, 0.0, k));
|
||||
assert_ne!(dispersed(&p, 20.3, 2.0, 2.0, 0.0, k), dispersed(&p, 20.3, 2.0, 2.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
/// A `w` × `h` coverage with the rectangles `rects` (x, y, width, height) filled.
|
||||
fn coverage(w: u32, h: u32, rects: &[(f32, f32, f32, f32)]) -> Pixmap {
|
||||
let mut p = Pixmap::new(w, h).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user