use anyhow::Result;
use image::{DynamicImage, GenericImageView};
use dwt::wavelet::Haar;
use dwt::{Operation, Transform};
use rustdct::DctPlanner;
#[derive(Eq, PartialEq, Hash, Clone)]
pub struct ImageHash {
hash: u64,
}
impl ImageHash {
#[inline]
pub fn ahash(image: &DynamicImage) -> Result<Self> {
let mut sum = 0u64;
let mut pixels = [0u8; 64];
for (i, (_, _, pixel)) in image.pixels().enumerate().take(64) {
pixels[i] = pixel[0]; sum += pixels[i] as u64;
}
let avg = sum / 64;
let mut hash = 0u64;
for (i, &pixel) in pixels.iter().enumerate() {
if pixel as u64 > avg {
hash |= 1 << (63 - i); }
}
Ok(Self { hash })
}
#[inline]
pub fn mhash(image: &DynamicImage) -> Result<Self> {
let mut pixels = [0u8; 64];
for (i, pixel) in image.pixels().map(|p| p.2[0]).take(64).enumerate() {
pixels[i] = pixel;
}
let mut pixels_copy = pixels;
let mid = 32;
let (low, median, _high) = pixels_copy.select_nth_unstable(mid);
let median = (*median as u64 + low[mid - 1] as u64) / 2;
let mut hash = 0u64;
for (i, &pixel) in pixels.iter().enumerate() {
if pixel as u64 > median {
hash |= 1 << (63 - i); }
}
Ok(Self { hash })
}
#[inline]
pub fn dhash(image: &DynamicImage) -> Result<Self> {
let mut hash = 0u64;
for y in 0..8 {
let mut current = image.get_pixel(0, y)[0];
for x in 1..9 {
let next = image.get_pixel(x, y)[0];
hash = (hash << 1) | (next > current) as u64;
current = next;
}
}
Ok(Self { hash })
}
#[inline]
pub fn phash(image: &DynamicImage) -> Result<Self> {
const IMG_SIZE: usize = 32;
const HASH_SIZE: usize = 8;
let mut pixels: Vec<f32> = image.pixels().map(|p| p.2[0] as f32).collect();
let mut planner = DctPlanner::new();
let dct = planner.plan_dct2(IMG_SIZE);
for row in pixels.chunks_exact_mut(IMG_SIZE) {
dct.process_dct2(row);
}
for col in 0..IMG_SIZE {
let mut col_values: [f32; IMG_SIZE] = [0.0; IMG_SIZE];
for row in 0..IMG_SIZE {
col_values[row] = pixels[row * IMG_SIZE + col];
}
dct.process_dct2(&mut col_values);
for row in 0..IMG_SIZE {
pixels[row * IMG_SIZE + col] = col_values[row];
}
}
let mut dct_lowfreq = [0f32; HASH_SIZE * HASH_SIZE];
for y in 0..HASH_SIZE {
for x in 0..HASH_SIZE {
dct_lowfreq[y * HASH_SIZE + x] = pixels[y * IMG_SIZE + x];
}
}
let mut ac_coeffs = dct_lowfreq[1..].to_vec();
let mid = ac_coeffs.len() / 2;
ac_coeffs.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap());
let median = ac_coeffs[mid];
let mut hash = 0u64;
for (i, &val) in dct_lowfreq.iter().enumerate() {
if val > median {
hash |= 1 << (63 - i);
}
}
Ok(Self { hash })
}
#[inline]
pub fn whash(image: &DynamicImage) -> Result<Self> {
const HASH_SIZE: u32 = 8;
let ll_max_level: usize = 3;
let total_pixels = (HASH_SIZE * HASH_SIZE) as usize;
let mut pixels = Vec::with_capacity(total_pixels);
for y in 0..HASH_SIZE {
for x in 0..HASH_SIZE {
let pixel = image.get_pixel(x, y);
pixels.push(pixel[0] as f32 / 255.0);
}
}
pixels.transform(Operation::Forward, &Haar::new(), ll_max_level);
pixels[0] = 0.0;
pixels.transform(Operation::Inverse, &Haar::new(), ll_max_level);
let mid: usize = 32;
let mut flat = pixels.clone();
flat.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap());
let median = (flat[mid - 1] + flat[mid]) / 2.0;
let mut hash = 0u64;
for (i, &val) in pixels.iter().enumerate() {
if val > median {
hash |= 1 << (63 - i);
}
}
Ok(Self { hash })
}
#[inline]
pub fn get_hash(&self) -> u64 {
self.hash
}
}