use crate::Result;
#[derive(Clone, Debug)]
pub struct Template {
pub rgb: Vec<u8>,
pub width: usize,
pub height: usize,
pub mask: Option<Vec<bool>>,
}
impl Template {
pub fn load(path: impl AsRef<std::path::Path>) -> Result<Self> {
let img = image::open(path)?;
if img.color().has_alpha() {
let rgba = img.to_rgba8();
let (width, height) = (rgba.width() as usize, rgba.height() as usize);
let mut rgb = Vec::with_capacity(width * height * 3);
let mut mask = Vec::with_capacity(width * height);
let mut any_transparent = false;
for px in rgba.as_raw().as_chunks::<4>().0 {
rgb.extend_from_slice(&[px[0], px[1], px[2]]);
let visible = px[3] != 0;
if !visible {
any_transparent = true;
}
mask.push(visible);
}
Ok(Template {
rgb,
width,
height,
mask: if any_transparent { Some(mask) } else { None },
})
} else {
let rgb_img = img.to_rgb8();
let (width, height) = (rgb_img.width() as usize, rgb_img.height() as usize);
Ok(Template {
rgb: rgb_img.into_raw(),
width,
height,
mask: None,
})
}
}
pub fn from_rgb(rgb: Vec<u8>, width: usize, height: usize) -> Self {
assert_eq!(rgb.len(), width * height * 3, "template 字节数与尺寸不符");
Template {
rgb,
width,
height,
mask: None,
}
}
pub fn from_rgba(rgba: Vec<u8>, width: usize, height: usize) -> Self {
assert_eq!(rgba.len(), width * height * 4, "rgba 字节数与尺寸不符");
let mut rgb = Vec::with_capacity(width * height * 3);
let mut mask = Vec::with_capacity(width * height);
let mut any_transparent = false;
for px in rgba.as_chunks::<4>().0 {
rgb.extend_from_slice(&[px[0], px[1], px[2]]);
let visible = px[3] != 0;
if !visible {
any_transparent = true;
}
mask.push(visible);
}
Template {
rgb,
width,
height,
mask: if any_transparent { Some(mask) } else { None },
}
}
pub fn with_mask(mut self, mask: Vec<bool>) -> Self {
assert_eq!(
mask.len(),
self.width * self.height,
"mask 长度与像素数不符"
);
self.mask = Some(mask);
self
}
pub fn content_key(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
self.width.hash(&mut h);
self.height.hash(&mut h);
self.rgb.hash(&mut h);
self.mask.hash(&mut h);
h.finish()
}
pub fn to_gray(&self) -> Vec<u8> {
let mut gray = Vec::with_capacity(self.width * self.height);
for px in self.rgb.as_chunks::<3>().0 {
let r = px[0] as u32;
let g = px[1] as u32;
let b = px[2] as u32;
gray.push(((r * 299 + g * 587 + b * 114) / 1000) as u8);
}
gray
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_rgb_no_mask() {
let t = Template::from_rgb(vec![128u8; 4 * 4 * 3], 4, 4);
assert!(t.mask.is_none());
}
#[test]
fn from_rgba_detects_transparent() {
let (w, h) = (4usize, 4usize);
let mut rgba = vec![200u8; w * h * 4];
rgba[(w + 1) * 4 + 3] = 0; let t = Template::from_rgba(rgba, w, h);
let mask = t.mask.as_ref().expect("应检测到透明像素");
assert!(!mask[w + 1], "(1,1) 应被标记为跳过");
assert!(mask[0], "(0,0) 应参与比较");
}
#[test]
fn with_mask_overrides() {
let t =
Template::from_rgb(vec![0u8; 2 * 2 * 3], 2, 2).with_mask(vec![true, false, true, true]);
assert!(!t.mask.as_ref().unwrap()[1]);
}
#[test]
fn content_key_changes_with_mask() {
let a = Template::from_rgb(vec![10u8; 2 * 2 * 3], 2, 2);
let b = Template::from_rgb(vec![10u8; 2 * 2 * 3], 2, 2)
.with_mask(vec![true, false, true, true]);
assert_ne!(a.content_key(), b.content_key());
}
}