use crate::imgproc::preprocess::bilinear_resample;
const DHASH_WIDTH: usize = 9;
const DHASH_HEIGHT: usize = 8;
const DHASH_SIZE: usize = DHASH_WIDTH * DHASH_HEIGHT;
pub fn compute_dhash(pixels: &[f32; 32 * 32]) -> u64 {
let mut small = [0.0f32; DHASH_SIZE];
bilinear_resample(pixels, 32, 32, &mut small, DHASH_WIDTH, DHASH_HEIGHT);
compute_hash_from_gradient(&small)
}
#[inline]
pub fn compute_dhash_from_64x64(block: &[f32; 64 * 64]) -> u64 {
let mut downsampled = [0.0f32; 32 * 32];
bilinear_resample(block, 64, 64, &mut downsampled, 32, 32);
compute_dhash(&downsampled)
}
#[inline(always)]
fn compute_hash_from_gradient(pixels: &[f32; DHASH_SIZE]) -> u64 {
let mut hash = 0u64;
let mut bit_pos = 63u32;
for y in 0..DHASH_HEIGHT {
let row_start = y * DHASH_WIDTH;
for x in 0..(DHASH_WIDTH - 1) {
let left = pixels[row_start + x];
let right = pixels[row_start + x + 1];
if left > right {
hash |= 1u64 << bit_pos;
}
bit_pos = bit_pos.saturating_sub(1);
}
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dhash_determinism() {
let img: [f32; 32 * 32] = std::array::from_fn(|i| ((i % 256) as f32) / 255.0);
let h1 = compute_dhash(&img);
let h2 = compute_dhash(&img);
assert_eq!(h1, h2);
}
#[test]
fn test_dhash_different_images() {
let img1: [f32; 32 * 32] = std::array::from_fn(|i| {
let x = i % 32;
let y = i / 32;
((x * x + y * y) % 256) as f32 / 255.0
});
let img2: [f32; 32 * 32] = std::array::from_fn(|i| {
let x = i % 32;
let y = i / 32;
((x + y) * 7 % 256) as f32 / 255.0
});
let h1 = compute_dhash(&img1);
let h2 = compute_dhash(&img2);
assert_ne!(h1, h2);
}
#[test]
fn test_dhash_from_64x64() {
let block: [f32; 64 * 64] = std::array::from_fn(|i| {
let x = i % 64;
let y = i / 64;
((x.wrapping_mul(y)) % 256) as f32 / 255.0
});
let hash = compute_dhash_from_64x64(&block);
let _ = hash;
}
#[test]
fn test_dhash_uniform_image() {
let img: [f32; 32 * 32] = [0.5; 32 * 32];
let hash = compute_dhash(&img);
assert_eq!(hash, 0);
}
#[test]
fn test_dhash_all_zeros() {
let img: [f32; 32 * 32] = [0.0; 32 * 32];
let hash = compute_dhash(&img);
assert_eq!(hash, 0);
}
#[test]
fn test_dhash_all_ones() {
let img: [f32; 32 * 32] = [1.0; 32 * 32];
let hash = compute_dhash(&img);
assert_eq!(hash, 0);
}
#[test]
fn test_dhash_gradient_horizontal() {
let mut img = [0.0f32; 32 * 32];
for y in 0..32 {
for x in 0..32 {
img[y * 32 + x] = x as f32 / 31.0;
}
}
let hash = compute_dhash(&img);
assert_eq!(hash, 0);
}
#[test]
fn test_dhash_gradient_vertical() {
let mut img = [0.0f32; 32 * 32];
for y in 0..32 {
for x in 0..32 {
img[y * 32 + x] = y as f32 / 31.0;
}
}
let hash = compute_dhash(&img);
let _ = hash;
}
#[test]
fn test_dhash_checkerboard_pattern() {
let mut img = [0.0f32; 32 * 32];
for y in 0..32 {
for x in 0..32 {
img[y * 32 + x] = if (x + y) % 2 == 0 { 0.0 } else { 1.0 };
}
}
let hash = compute_dhash(&img);
assert_ne!(hash, 0);
}
#[test]
fn test_dhash_bit_ordering() {
let mut img1 = [0.0f32; 32 * 32];
let mut img2 = [0.0f32; 32 * 32];
for i in 0..img1.len() {
img1[i] = (i % 128) as f32 / 255.0;
img2[i] = (i % 128 + 1) as f32 / 255.0;
}
let h1 = compute_dhash(&img1);
let h2 = compute_dhash(&img2);
let distance = (h1 ^ h2).count_ones();
assert!(
distance < 32,
"Similar images should have low Hamming distance, got {}",
distance
);
}
#[test]
fn test_dhash_from_64x64_deterministic() {
let block: [f32; 64 * 64] = std::array::from_fn(|i| (i % 256) as f32 / 255.0);
let h1 = compute_dhash_from_64x64(&block);
let h2 = compute_dhash_from_64x64(&block);
assert_eq!(h1, h2);
}
#[test]
fn test_dhash_from_64x64_uniform() {
let block: [f32; 64 * 64] = [0.5; 64 * 64];
let hash = compute_dhash_from_64x64(&block);
assert_eq!(hash, 0);
}
#[test]
fn test_compute_hash_from_gradient_uniform() {
let pixels = [0.5f32; DHASH_SIZE];
let hash = compute_hash_from_gradient(&pixels);
assert_eq!(hash, 0);
}
#[test]
fn test_compute_hash_from_gradient_ascending() {
let mut pixels = [0.0f32; DHASH_SIZE];
for (i, item) in pixels.iter_mut().enumerate().take(DHASH_SIZE) {
*item = i as f32 / DHASH_SIZE as f32;
}
let hash = compute_hash_from_gradient(&pixels);
assert_eq!(hash, 0);
}
#[test]
fn test_compute_hash_from_gradient_descending() {
let mut pixels = [1.0f32; DHASH_SIZE];
for (i, item) in pixels.iter_mut().enumerate().take(DHASH_SIZE) {
*item = 1.0 - (i as f32 / DHASH_SIZE as f32);
}
let hash = compute_hash_from_gradient(&pixels);
assert_ne!(hash, 0);
}
}