use crate::alpha::{self, AlphaCompression, AlphaFilter};
use crate::lossy::prelude::*;
const FILTERS: [AlphaFilter; 4] = [
AlphaFilter::None,
AlphaFilter::Horizontal,
AlphaFilter::Vertical,
AlphaFilter::Gradient,
];
#[must_use]
pub(crate) fn compress_alpha(plane: &[u8], width: u32, height: u32) -> Vec<u8> {
let (w, h) = (width as usize, height as usize);
let mut best: Option<Vec<u8>> = None;
for &filter in &FILTERS {
let filtered = alpha::filter_plane(filter, plane, w, h);
let lossless = crate::lossless::encode_alpha(&filtered, width, height);
consider(
&mut best,
assemble(AlphaCompression::Lossless, filter, &lossless),
);
consider(
&mut best,
assemble(AlphaCompression::None, filter, &filtered),
);
}
best.unwrap_or_else(|| assemble(AlphaCompression::None, AlphaFilter::None, plane))
}
fn assemble(compression: AlphaCompression, filter: AlphaFilter, data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(1 + data.len());
out.push(alpha::build_header(compression, filter, 0));
out.extend_from_slice(data);
out
}
fn consider(best: &mut Option<Vec<u8>>, candidate: Vec<u8>) {
if best.as_ref().is_none_or(|b| candidate.len() < b.len()) {
*best = Some(candidate);
}
}
#[cfg(test)]
mod tests {
use crate::alpha::{AlphaCompression, parse_header, unfilter};
use super::compress_alpha;
fn byte(v: u32) -> u8 {
u8::try_from(v & 0xff).unwrap_or(0)
}
fn decompress(alph: &[u8], width: u32, height: u32) -> Vec<u8> {
let (w, h) = (width as usize, height as usize);
let (header, data) = parse_header(alph).unwrap();
let mut plane = match header.compression {
AlphaCompression::None => data[..w * h].to_vec(),
AlphaCompression::Lossless => {
crate::lossless::decode_alpha(data, width, height).unwrap()
},
};
unfilter(header.filter, &mut plane, w, h);
plane
}
#[test]
fn compress_alpha_round_trips_byte_exact() {
let cases: [(u32, u32, Vec<u8>); 4] = [
(4, 4, vec![0xC0u8; 16]),
(8, 3, (0..24u32).map(|v| byte(v * 10)).collect()),
(
5,
5,
(0..25u32)
.map(|v| byte(v.wrapping_mul(53) ^ 0x1f))
.collect(),
),
(
6,
4,
(0..24u32)
.map(|i| if i % 6 < 3 { 0 } else { 255 })
.collect(),
),
];
for (w, h, plane) in cases {
let alph = compress_alpha(&plane, w, h);
assert_eq!(decompress(&alph, w, h), plane, "{w}x{h} alpha round-trip");
}
}
#[test]
fn compress_alpha_keeps_the_smallest_candidate() {
let plane = vec![0x42u8; 32];
let chosen = compress_alpha(&plane, 8, 4);
let raw_baseline = 1 + plane.len();
assert!(
chosen.len() <= raw_baseline,
"chosen {} must not exceed raw baseline {raw_baseline}",
chosen.len()
);
}
#[test]
fn compress_alpha_incompressible_plane_keeps_the_first_smallest_raw() {
use crate::alpha::{AlphaFilter, build_header};
let plane: Vec<u8> = (0..64u32)
.map(|i| {
let mut z = i.wrapping_add(1).wrapping_mul(0x9E37_79B1);
z ^= z >> 15;
z = z.wrapping_mul(0x85EB_CA77);
z ^= z >> 13;
z = z.wrapping_mul(0xC2B2_AE3D);
z ^= z >> 16;
byte(z)
})
.collect();
let mut expected = vec![build_header(AlphaCompression::None, AlphaFilter::None, 0)];
expected.extend_from_slice(&plane);
assert_eq!(compress_alpha(&plane, 8, 8), expected);
}
#[test]
fn compress_alpha_flat_plane_chooses_lossless() {
let plane = vec![0xA7u8; 32];
let chosen = compress_alpha(&plane, 8, 4);
let (header, _) = parse_header(&chosen).unwrap();
assert_eq!(header.compression, AlphaCompression::Lossless);
assert!(
chosen.len() < 1 + plane.len(),
"lossless flat-plane payload {} must beat the {}-byte raw baseline",
chosen.len(),
1 + plane.len()
);
}
#[test]
fn compress_alpha_is_deterministic() {
let plane: Vec<u8> = (0..48u32).map(|v| byte(v.wrapping_mul(29))).collect();
assert_eq!(compress_alpha(&plane, 8, 6), compress_alpha(&plane, 8, 6));
}
}