use crate::core::{Pix, PixelDepth};
use crate::transform::{TransformError, TransformResult};
pub fn reduce_rank_binary_2(pix: &Pix, level: u8) -> TransformResult<Pix> {
if pix.depth() != PixelDepth::Bit1 {
return Err(TransformError::InvalidParameters(
"reduce_rank_binary_2 requires a 1-bpp image".to_string(),
));
}
if !(1..=4).contains(&level) {
return Err(TransformError::InvalidParameters(format!(
"level must be 1–4, got {level}"
)));
}
let src_w = pix.width();
let src_h = pix.height();
let dst_w = src_w / 2;
let dst_h = src_h / 2;
let mut dst = Pix::new(dst_w, dst_h, PixelDepth::Bit1)
.map_err(TransformError::Core)?
.to_mut();
for oy in 0..dst_h {
for ox in 0..dst_w {
let sx = ox * 2;
let sy = oy * 2;
let p00 = pix.get_pixel(sx, sy).unwrap_or(0);
let p10 = pix.get_pixel(sx + 1, sy).unwrap_or(0);
let p01 = pix.get_pixel(sx, sy + 1).unwrap_or(0);
let p11 = pix.get_pixel(sx + 1, sy + 1).unwrap_or(0);
let count = p00 + p10 + p01 + p11;
if count >= level as u32 {
dst.set_pixel_unchecked(ox, oy, 1);
}
}
}
Ok(dst.into())
}
pub fn reduce_rank_binary_cascade(pix: &Pix, levels: &[u8]) -> TransformResult<Pix> {
if levels.is_empty() {
return Ok(pix.clone());
}
let mut current = reduce_rank_binary_2(pix, levels[0])?;
for &lvl in &levels[1..] {
current = reduce_rank_binary_2(¤t, lvl)?;
}
Ok(current)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::PixelDepth;
fn make_1bpp(width: u32, height: u32, pixels: &[u8]) -> Pix {
assert_eq!(pixels.len(), (width * height) as usize);
let mut pix = Pix::new(width, height, PixelDepth::Bit1).unwrap().to_mut();
for y in 0..height {
for x in 0..width {
let v = pixels[(y * width + x) as usize];
pix.set_pixel(x, y, v as u32).unwrap();
}
}
pix.into()
}
#[test]
fn test_reduce_rank_binary_2_output_size_even() {
let src = Pix::new(8, 8, PixelDepth::Bit1).unwrap();
let dst = reduce_rank_binary_2(&src, 1).unwrap();
assert_eq!(dst.width(), 4);
assert_eq!(dst.height(), 4);
}
#[test]
fn test_reduce_rank_binary_2_output_size_odd() {
let src = Pix::new(9, 7, PixelDepth::Bit1).unwrap();
let dst = reduce_rank_binary_2(&src, 1).unwrap();
assert_eq!(dst.width(), 4);
assert_eq!(dst.height(), 3);
}
#[test]
fn test_reduce_rank_binary_2_level1_all_off() {
#[rustfmt::skip]
let pixels: Vec<u8> = vec![
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
];
let src = make_1bpp(4, 4, &pixels);
let dst = reduce_rank_binary_2(&src, 1).unwrap();
assert_eq!(dst.get_pixel(0, 0), Some(0));
assert_eq!(dst.get_pixel(1, 0), Some(0));
assert_eq!(dst.get_pixel(0, 1), Some(0));
assert_eq!(dst.get_pixel(1, 1), Some(0));
}
#[test]
fn test_reduce_rank_binary_2_level1_one_on_per_block() {
#[rustfmt::skip]
let pixels: Vec<u8> = vec![
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1,
0, 0, 0, 0,
];
let src = make_1bpp(4, 4, &pixels);
let dst = reduce_rank_binary_2(&src, 1).unwrap();
assert_eq!(dst.get_pixel(0, 0), Some(1)); assert_eq!(dst.get_pixel(1, 0), Some(0)); assert_eq!(dst.get_pixel(0, 1), Some(0)); assert_eq!(dst.get_pixel(1, 1), Some(1)); }
#[test]
fn test_reduce_rank_binary_2_level4_partial() {
#[rustfmt::skip]
let pixels: Vec<u8> = vec![
1, 1, 1, 1,
1, 0, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
];
let src = make_1bpp(4, 4, &pixels);
let dst = reduce_rank_binary_2(&src, 4).unwrap();
assert_eq!(dst.get_pixel(0, 0), Some(0)); assert_eq!(dst.get_pixel(1, 0), Some(1)); assert_eq!(dst.get_pixel(0, 1), Some(1)); assert_eq!(dst.get_pixel(1, 1), Some(1)); }
#[test]
fn test_reduce_rank_binary_2_level2() {
#[rustfmt::skip]
let pixels: Vec<u8> = vec![
1, 1, 0, 0,
0, 0, 0, 0,
];
let src = make_1bpp(4, 2, &pixels);
let dst2 = reduce_rank_binary_2(&src, 2).unwrap();
let dst3 = reduce_rank_binary_2(&src, 3).unwrap();
assert_eq!(dst2.get_pixel(0, 0), Some(1)); assert_eq!(dst2.get_pixel(1, 0), Some(0)); assert_eq!(dst3.get_pixel(0, 0), Some(0)); }
#[test]
fn test_reduce_rank_binary_2_invalid_depth() {
let src = Pix::new(8, 8, PixelDepth::Bit8).unwrap();
assert!(reduce_rank_binary_2(&src, 1).is_err());
}
#[test]
fn test_reduce_rank_binary_2_invalid_level() {
let src = Pix::new(8, 8, PixelDepth::Bit1).unwrap();
assert!(reduce_rank_binary_2(&src, 0).is_err());
assert!(reduce_rank_binary_2(&src, 5).is_err());
}
#[test]
fn test_cascade_r11_size() {
let src = Pix::new(16, 16, PixelDepth::Bit1).unwrap();
let dst = reduce_rank_binary_cascade(&src, &[1, 1]).unwrap();
assert_eq!(dst.width(), 4);
assert_eq!(dst.height(), 4);
}
#[test]
fn test_cascade_r1143_size() {
let src = Pix::new(64, 64, PixelDepth::Bit1).unwrap();
let dst = reduce_rank_binary_cascade(&src, &[1, 1, 4, 3]).unwrap();
assert_eq!(dst.width(), 4);
assert_eq!(dst.height(), 4);
}
#[test]
fn test_cascade_empty_levels() {
let mut src = Pix::new(8, 8, PixelDepth::Bit1).unwrap().to_mut();
src.set_pixel(2, 3, 1).unwrap();
let src: Pix = src.into();
let dst = reduce_rank_binary_cascade(&src, &[]).unwrap();
assert_eq!(dst.width(), 8);
assert_eq!(dst.height(), 8);
assert_eq!(dst.get_pixel(2, 3), Some(1));
}
}