#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ImageMaskShape {
#[default]
None,
Circle,
RoundedSquare(f32),
}
#[derive(Debug, Clone, Copy)]
enum MaskShape {
Circle,
RoundedSquare(f32),
Square,
}
const SAMPLES_PER_AXIS: u32 = 4;
pub fn center_crop_square(pixels: &[u8], width: u32, height: u32) -> (Vec<u8>, u32) {
let side = width.min(height);
if width == side && height == side {
return (pixels.to_vec(), side);
}
debug_assert_eq!(
pixels.len(),
(width * height * 4) as usize,
"pixel buffer length must be width * height * 4"
);
let x_off = ((width - side) / 2) as usize;
let y_off = ((height - side) / 2) as usize;
let stride = (width * 4) as usize;
let row_bytes = (side * 4) as usize;
let mut out = Vec::with_capacity((side as usize) * row_bytes);
for j in 0..side as usize {
let row_start = (y_off + j) * stride + x_off * 4;
out.extend_from_slice(&pixels[row_start..row_start + row_bytes]);
}
(out, side)
}
pub fn apply_alpha_mask(pixels: &mut [u8], width: u32, height: u32, shape: ImageMaskShape) {
debug_assert_eq!(pixels.len(), (width * height * 4) as usize);
let internal = match shape {
ImageMaskShape::None => return,
ImageMaskShape::Circle => MaskShape::Circle,
ImageMaskShape::RoundedSquare(ratio) => {
let r = ratio.clamp(0.0, 0.5) * (width.min(height) as f32);
if r <= 0.0 {
MaskShape::Square
} else {
MaskShape::RoundedSquare(r)
}
}
};
let radius = match internal {
MaskShape::Square => return,
MaskShape::Circle => (width.min(height) as f32) / 2.0,
MaskShape::RoundedSquare(r) => r,
};
apply_rounded(pixels, width, height, radius);
}
fn apply_rounded(pixels: &mut [u8], width: u32, height: u32, radius: f32) {
if width == 0 || height == 0 {
return;
}
let w = width as f32;
let h = height as f32;
let r = radius.clamp(0.0, (w.min(h)) / 2.0);
if r <= 0.0 {
return;
}
for j in 0..height {
for i in 0..width {
let coverage = pixel_coverage(i as f32, j as f32, w, h, r);
let idx = ((j * width + i) * 4 + 3) as usize;
let original = pixels[idx] as f32;
let masked = (original * coverage + 0.5).clamp(0.0, 255.0) as u8;
pixels[idx] = masked;
}
}
}
fn pixel_coverage(px: f32, py: f32, w: f32, h: f32, r: f32) -> f32 {
let mut hits: u32 = 0;
let total = SAMPLES_PER_AXIS * SAMPLES_PER_AXIS;
for sy in 0..SAMPLES_PER_AXIS {
for sx in 0..SAMPLES_PER_AXIS {
let sub_x = px + (sx as f32 + 0.5) / SAMPLES_PER_AXIS as f32;
let sub_y = py + (sy as f32 + 0.5) / SAMPLES_PER_AXIS as f32;
if inside_rounded_rect(sub_x, sub_y, w, h, r) {
hits += 1;
}
}
}
hits as f32 / total as f32
}
#[inline]
fn inside_rounded_rect(x: f32, y: f32, w: f32, h: f32, r: f32) -> bool {
if x < 0.0 || y < 0.0 || x > w || y > h {
return false;
}
let cx = x.clamp(r, w - r);
let cy = y.clamp(r, h - r);
let dx = x - cx;
let dy = y - cy;
dx * dx + dy * dy <= r * r
}
#[cfg(test)]
mod tests {
use super::*;
fn solid(width: u32, height: u32) -> Vec<u8> {
let mut v = Vec::with_capacity((width * height * 4) as usize);
for _ in 0..(width * height) {
v.extend_from_slice(&[10, 20, 30, 200]);
}
v
}
fn alpha_at(pixels: &[u8], width: u32, x: u32, y: u32) -> u8 {
pixels[((y * width + x) * 4 + 3) as usize]
}
#[test]
fn mask_circle_zeros_corners() {
let mut pixels = solid(32, 32);
apply_alpha_mask(&mut pixels, 32, 32, ImageMaskShape::Circle);
assert_eq!(alpha_at(&pixels, 32, 0, 0), 0);
assert_eq!(alpha_at(&pixels, 32, 31, 0), 0);
assert_eq!(alpha_at(&pixels, 32, 0, 31), 0);
assert_eq!(alpha_at(&pixels, 32, 31, 31), 0);
}
#[test]
fn mask_circle_full_center() {
let mut pixels = solid(32, 32);
apply_alpha_mask(&mut pixels, 32, 32, ImageMaskShape::Circle);
assert_eq!(alpha_at(&pixels, 32, 16, 16), 200);
}
#[test]
fn mask_circle_aa_at_boundary() {
let mut pixels = solid(32, 32);
apply_alpha_mask(&mut pixels, 32, 32, ImageMaskShape::Circle);
let edge = alpha_at(&pixels, 32, 5, 4);
assert!(
edge > 0 && edge < 200,
"expected partial coverage at the curve boundary, got {edge}"
);
}
#[test]
fn mask_rounded_square_radius_zero_is_passthrough() {
let mut pixels = solid(16, 16);
apply_alpha_mask(&mut pixels, 16, 16, ImageMaskShape::RoundedSquare(0.0));
for j in 0..16 {
for i in 0..16 {
assert_eq!(alpha_at(&pixels, 16, i, j), 200);
}
}
}
#[test]
fn mask_rounded_square_full_radius_equals_circle() {
let mut a = solid(24, 24);
let mut b = solid(24, 24);
apply_alpha_mask(&mut a, 24, 24, ImageMaskShape::Circle);
apply_alpha_mask(&mut b, 24, 24, ImageMaskShape::RoundedSquare(0.5));
for (av, bv) in a.iter().zip(b.iter()) {
assert!(
av.abs_diff(*bv) <= 1,
"circle and full-radius rounded-square should match within 1 alpha LSB"
);
}
}
#[test]
fn mask_preserves_rgb() {
let mut pixels = solid(16, 16);
apply_alpha_mask(&mut pixels, 16, 16, ImageMaskShape::Circle);
for i in (0..pixels.len()).step_by(4) {
assert_eq!(pixels[i], 10);
assert_eq!(pixels[i + 1], 20);
assert_eq!(pixels[i + 2], 30);
}
}
#[test]
fn mask_none_is_noop() {
let mut pixels = solid(8, 8);
apply_alpha_mask(&mut pixels, 8, 8, ImageMaskShape::None);
for j in 0..8 {
for i in 0..8 {
assert_eq!(alpha_at(&pixels, 8, i, j), 200);
}
}
}
#[test]
fn mask_handles_size_one_image() {
let mut pixels = vec![10, 20, 30, 200];
apply_alpha_mask(&mut pixels, 1, 1, ImageMaskShape::Circle);
assert!(pixels[3] > 0, "1×1 alpha must remain non-zero");
assert!(pixels[3] <= 200, "1×1 alpha cannot exceed source");
assert_eq!(&pixels[..3], &[10, 20, 30]);
}
#[test]
fn mask_circle_alpha_multiplied_with_source() {
let mut pixels = Vec::with_capacity(32 * 32 * 4);
for _ in 0..(32 * 32) {
pixels.extend_from_slice(&[10, 20, 30, 100]);
}
apply_alpha_mask(&mut pixels, 32, 32, ImageMaskShape::Circle);
assert_eq!(alpha_at(&pixels, 32, 16, 16), 100);
assert_eq!(alpha_at(&pixels, 32, 0, 0), 0);
}
#[test]
fn center_crop_square_is_identity_when_already_square() {
let p = solid(16, 16);
let (out, side) = center_crop_square(&p, 16, 16);
assert_eq!(side, 16);
assert_eq!(out, p);
}
#[test]
fn center_crop_square_landscape() {
let mut pixels = Vec::new();
for y in 0..4 {
for x in 0..8 {
pixels.extend_from_slice(&[x as u8, y as u8, 0, 255]);
}
}
let (out, side) = center_crop_square(&pixels, 8, 4);
assert_eq!(side, 4);
assert_eq!(out.len(), 4 * 4 * 4);
assert_eq!(out[0], 2);
let last = out.len() - 4;
assert_eq!(out[last], 5);
}
#[test]
fn center_crop_square_portrait() {
let mut pixels = Vec::new();
for y in 0..8 {
for x in 0..4 {
pixels.extend_from_slice(&[x as u8, y as u8, 0, 255]);
}
}
let (out, side) = center_crop_square(&pixels, 4, 8);
assert_eq!(side, 4);
assert_eq!(out[1], 2); }
}