use crate::rasterizer::AlphaBitmap;
pub fn dilate_alpha(src: &AlphaBitmap, radius_px: f32) -> AlphaBitmap {
if src.is_empty() || radius_px <= 0.0 {
return src.clone();
}
let r = radius_px.ceil() as u32;
let r_sq = (radius_px * radius_px) as i32;
let r_i = r as i32;
let half_widths: Vec<i32> = (-r_i..=r_i)
.map(|dy| {
let rem = r_sq - dy * dy;
if rem < 0 {
-1 } else {
(rem as f32).sqrt().floor() as i32
}
})
.collect();
let new_w = src.width + 2 * r;
let new_h = src.height + 2 * r;
let mut out = AlphaBitmap::new(new_w, new_h);
let sw = src.width as i32;
let sh = src.height as i32;
let nw = new_w as i32;
let nh = new_h as i32;
for oy in 0..nh {
for ox in 0..nw {
let mut best: u8 = 0;
let cx = ox - r_i;
let cy = oy - r_i;
for dy in -r_i..=r_i {
let hw = half_widths[(dy + r_i) as usize];
if hw < 0 {
continue;
}
let sy = cy + dy;
if sy < 0 || sy >= sh {
continue;
}
let row_off = (sy as u32 * src.width) as usize;
let row = &src.data[row_off..row_off + src.width as usize];
let x_lo = (cx - hw).max(0);
let x_hi = (cx + hw).min(sw - 1);
if x_hi < x_lo {
continue;
}
for sx in x_lo..=x_hi {
let v = row[sx as usize];
if v > best {
best = v;
if best == 255 {
break;
}
}
}
if best == 255 {
break;
}
}
out.data[(oy as u32 * new_w + ox as u32) as usize] = best;
}
}
out
}
pub fn dilate_offset(radius_px: f32) -> i32 {
if radius_px <= 0.0 {
0
} else {
radius_px.ceil() as i32
}
}
#[cfg(test)]
mod tests {
use super::*;
fn solid_pixel() -> AlphaBitmap {
let mut bm = AlphaBitmap::new(1, 1);
bm.data[0] = 255;
bm
}
#[test]
fn zero_radius_returns_original() {
let src = solid_pixel();
let out = dilate_alpha(&src, 0.0);
assert_eq!(out.width, 1);
assert_eq!(out.height, 1);
assert_eq!(out.data, vec![255]);
}
#[test]
fn radius_1_pixel_grows_to_3x3_disc() {
let src = solid_pixel();
let out = dilate_alpha(&src, 1.0);
assert_eq!(out.width, 3);
assert_eq!(out.height, 3);
assert_eq!(out.get(0, 0), 0, "top-left corner");
assert_eq!(out.get(2, 0), 0, "top-right corner");
assert_eq!(out.get(0, 2), 0, "bottom-left corner");
assert_eq!(out.get(2, 2), 0, "bottom-right corner");
assert_eq!(out.get(1, 1), 255, "centre");
assert_eq!(out.get(0, 1), 255, "left");
assert_eq!(out.get(2, 1), 255, "right");
assert_eq!(out.get(1, 0), 255, "top");
assert_eq!(out.get(1, 2), 255, "bottom");
}
#[test]
fn dilation_preserves_max_value() {
let mut src = AlphaBitmap::new(3, 3);
src.data[4] = 200; let out = dilate_alpha(&src, 1.0);
assert_eq!(out.get(2, 2), 200);
assert_eq!(out.get(1, 2), 200);
assert_eq!(out.get(3, 2), 200);
}
#[test]
fn empty_bitmap_is_noop() {
let src = AlphaBitmap::default();
let out = dilate_alpha(&src, 2.0);
assert!(out.is_empty());
}
#[test]
fn dilate_offset_is_ceil_of_radius() {
assert_eq!(dilate_offset(0.0), 0);
assert_eq!(dilate_offset(1.0), 1);
assert_eq!(dilate_offset(1.4), 2);
assert_eq!(dilate_offset(2.0), 2);
}
}