Glowing Edges: transparency beyond the right and bottom edges; rustfmt; as_chunks in tests

The edge filter read `y * w + x` for x = w (the next row's first pixel)
and y = h at the raster's right and bottom edges; it now reads
transparency there, as the module documents. A test checks a solid square
glows alike on every side. Formatted with rustfmt, and the test uses
as_chunks for clippy 1.99.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-10 09:50:05 +07:00
co-authored by Claude Opus 5.5
parent 11aaf816d0
commit 800baacbba
2 changed files with 48 additions and 10 deletions
+35 -9
View File
@@ -10,10 +10,15 @@ pub(super) fn glow(px: &mut [[u8; 4]], w: usize, h: usize, width: f64, brightnes
}
let src = px.to_vec();
let signal = |x: isize, y: isize| -> f32 {
if x < 0 || y < 0 { return 0.0; }
let Some(p) = src.get(y as usize * w + x as usize) else { return 0.0 };
// Beyond the raster's edges is transparency (not the next row's first pixel).
let (Ok(x), Ok(y)) = (usize::try_from(x), usize::try_from(y)) else { return 0.0 };
if x >= w || y >= h {
return 0.0;
}
let Some(p) = src.get(y * w + x) else { return 0.0 };
let alpha = f32::from(p[3]) / 255.0;
let straight = if alpha > 0.0 { [f32::from(p[0]) / 255.0 / alpha, f32::from(p[1]) / 255.0 / alpha, f32::from(p[2]) / 255.0 / alpha] } else { [0.0; 3] };
let straight =
if alpha > 0.0 { [f32::from(p[0]) / 255.0 / alpha, f32::from(p[1]) / 255.0 / alpha, f32::from(p[2]) / 255.0 / alpha] } else { [0.0; 3] };
(0.2126 * straight[0] + 0.7152 * straight[1] + 0.0722 * straight[2]) * alpha
};
let mut edges = vec![0.0f32; px.len()];
@@ -21,18 +26,39 @@ pub(super) fn glow(px: &mut [[u8; 4]], w: usize, h: usize, width: f64, brightnes
for x in 0..w {
let x = x as isize;
let y = y as isize;
let gx = -signal(x - 1, y - 1) + signal(x + 1, y - 1) - 2.0 * signal(x - 1, y) + 2.0 * signal(x + 1, y) - signal(x - 1, y + 1) + signal(x + 1, y + 1);
let gy = -signal(x - 1, y - 1) - 2.0 * signal(x, y - 1) - signal(x + 1, y - 1) + signal(x - 1, y + 1) + 2.0 * signal(x, y + 1) + signal(x + 1, y + 1);
if let Some(edge) = edges.get_mut(y as usize * w + x as usize) { *edge = gx.hypot(gy).clamp(0.0, 1.0); }
let gx = -signal(x - 1, y - 1) + signal(x + 1, y - 1) - 2.0 * signal(x - 1, y) + 2.0 * signal(x + 1, y) - signal(x - 1, y + 1)
+ signal(x + 1, y + 1);
let gy = -signal(x - 1, y - 1) - 2.0 * signal(x, y - 1) - signal(x + 1, y - 1)
+ signal(x - 1, y + 1)
+ 2.0 * signal(x, y + 1)
+ signal(x + 1, y + 1);
if let Some(edge) = edges.get_mut(y as usize * w + x as usize) {
*edge = gx.hypot(gy).clamp(0.0, 1.0);
}
}
}
blur_plane(&mut edges, w, h, smoothness.max(0.0));
if width > 0.5 { blur_plane(&mut edges, w, h, width * 0.5); }
if width > 0.5 {
blur_plane(&mut edges, w, h, width * 0.5);
}
for (out, (original, edge)) in px.iter_mut().zip(src.iter().zip(edges)) {
let alpha = f32::from(original[3]) / 255.0;
let glow = (edge * brightness as f32 / 6.0).clamp(0.0, 1.0);
let straight = if original[3] > 0 { [f32::from(original[0]) / 255.0 / alpha.max(1e-6), f32::from(original[1]) / 255.0 / alpha.max(1e-6), f32::from(original[2]) / 255.0 / alpha.max(1e-6)] } else { [1.0, 1.0, 1.0] };
let straight = if original[3] > 0 {
[
f32::from(original[0]) / 255.0 / alpha.max(1e-6),
f32::from(original[1]) / 255.0 / alpha.max(1e-6),
f32::from(original[2]) / 255.0 / alpha.max(1e-6),
]
} else {
[1.0, 1.0, 1.0]
};
let a = alpha.max(glow);
*out = [(straight[0] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8, (straight[1] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8, (straight[2] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8, (a * 255.0).round() as u8];
*out = [
(straight[0] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8,
(straight[1] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8,
(straight[2] * glow * a * 255.0).round().clamp(0.0, 255.0) as u8,
(a * 255.0).round() as u8,
];
}
}
+13 -1
View File
@@ -128,7 +128,19 @@ fn glowing_edges_draw_a_bright_outline_and_preserve_premultiplication() {
let mut out = src.clone();
fx("stylize.glowingEdges", json!({"edgeWidth": 1, "smoothness": 1, "edgeBrightness": 20})).apply(&mut out, w, h, &space(w, h));
assert!(at(&out, w, 3, 4)[0] > 0 || at(&out, w, 4, 4)[0] > 0);
assert!(out.chunks_exact(4).all(|p| p[0] <= p[3] && p[1] <= p[3] && p[2] <= p[3]));
assert!(out.as_chunks::<4>().0.iter().all(|p| p[0] <= p[3] && p[1] <= p[3] && p[2] <= p[3]));
}
/// Glowing Edges sees transparency beyond every side of the raster: a solid square glows alike at
/// its left and right edges (the right one doesn't read the next row's first pixel).
#[test]
fn glowing_edges_glow_alike_on_every_side() {
let (w, h) = (9, 9);
let mut out = image(w, h, |_, _| [180, 180, 180, 255]);
fx("stylize.glowingEdges", json!({"edgeWidth": 1, "smoothness": 1, "edgeBrightness": 20})).apply(&mut out, w, h, &space(w, h));
let (left, right, top, bottom) = (at(&out, w, 0, 4), at(&out, w, 8, 4), at(&out, w, 4, 0), at(&out, w, 4, 8));
assert!(left[0] > 0 && left == right && top == bottom, "{left:?} {right:?} {top:?} {bottom:?}");
assert!(at(&out, w, 4, 4)[0] < left[0], "the inside stays darker than the edges");
}
#[test]