use zenpixels::{AlphaMode, PixelFormat, PixelSlice};
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct AlphaStats {
pub present: bool,
pub used_fraction: f32,
pub bimodal_score: f32,
}
fn alpha_layout(fmt: PixelFormat) -> Option<(usize, usize, usize)> {
match fmt {
PixelFormat::Rgba8 => Some((3, 1, 4)),
PixelFormat::Bgra8 => Some((3, 1, 4)),
PixelFormat::Rgba16 => Some((6, 2, 8)),
PixelFormat::RgbaF32 => Some((12, 4, 16)),
PixelFormat::GrayA8 => Some((1, 1, 2)),
PixelFormat::GrayA16 => Some((2, 2, 4)),
PixelFormat::GrayAF32 => Some((4, 4, 8)),
_ => None,
}
}
#[inline(always)]
fn alpha_byte_at(pixel: &[u8], a_off: usize, ch_bytes: usize) -> u8 {
match ch_bytes {
1 => pixel[a_off],
2 => pixel[a_off + 1], 4 => {
let v = f32::from_le_bytes([
pixel[a_off],
pixel[a_off + 1],
pixel[a_off + 2],
pixel[a_off + 3],
]);
(v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
_ => 255,
}
}
pub(crate) fn scan_alpha(slice: &PixelSlice<'_>, pixel_budget: usize) -> AlphaStats {
let desc = slice.descriptor();
let alpha_mode = desc.alpha;
if !matches!(alpha_mode, Some(AlphaMode::Straight)) {
return AlphaStats::default();
}
let (a_off, ch_bytes, bpp) = match alpha_layout(desc.format) {
Some(t) => t,
None => return AlphaStats::default(),
};
let width = slice.width() as usize;
let height = slice.rows() as usize;
if width == 0 || height == 0 {
return AlphaStats::default();
}
let pixels_per_row = width.max(1);
let target_rows = (pixel_budget / pixels_per_row).max(1).min(height);
let row_step = (height / target_rows).max(1);
let mut histogram = [0u32; 256];
let mut total: u32 = 0;
let mut y = 0usize;
while y < height {
let row = slice.row(y as u32);
let mut x = 0usize;
while x < width {
let off = x * bpp;
if off + a_off + ch_bytes > row.len() {
break;
}
let pixel = &row[off..off + bpp];
let a = alpha_byte_at(pixel, a_off, ch_bytes);
histogram[a as usize] += 1;
total += 1;
x += 1;
}
y += row_step;
}
if total == 0 {
return AlphaStats {
present: true,
..AlphaStats::default()
};
}
let total_f = total as f32;
let low_quarter: u32 = histogram[..64].iter().sum();
let high_quarter: u32 = histogram[192..].iter().sum();
let fully_opaque: u32 = histogram[255];
let low_frac = low_quarter as f32 / total_f;
let high_frac = high_quarter as f32 / total_f;
let used_fraction = 1.0 - (fully_opaque as f32 / total_f);
let bimodal_score = low_frac.min(high_frac);
AlphaStats {
present: true,
used_fraction,
bimodal_score,
}
}