use image::{self, FilterType, GenericImageView};
use std::sync::mpsc;
use std::thread;
pub struct SimilarChecker {
threshold: usize,
compressed_w: usize,
compressed_h: usize,
}
impl Default for SimilarChecker {
fn default() -> Self {
SimilarChecker {
threshold: 10,
compressed_w: 8,
compressed_h: 8,
}
}
}
impl SimilarChecker {
pub fn new() -> Self {
SimilarChecker {
threshold: 10,
compressed_w: 8,
compressed_h: 8,
}
}
pub fn compression_size(self, width: usize, height: usize) -> Self {
SimilarChecker {
compressed_w: width,
compressed_h: height,
..self
}
}
pub fn threshold(self, threshold: usize) -> Self {
SimilarChecker { threshold, ..self }
}
pub fn is_similar(&self, img1: image::DynamicImage, img2: image::DynamicImage) -> bool {
let w = self.compressed_w;
let h = self.compressed_h;
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx);
thread::spawn(move || {
let hash1 = get_hash(process(img1, w, h));
tx1.send(hash1).unwrap();
});
thread::spawn(move || {
let hash2 = get_hash(process(img2, w, h));
tx.send(hash2).unwrap();
});
let mut v: Vec<usize> = Vec::new();
for received in rx {
v.push(received)
}
let distance = get_distance(v[0], v[1], w*h);
distance < self.threshold
}
}
fn process(
img: image::DynamicImage,
compressed_w: usize,
compressed_h: usize,
) -> image::DynamicImage {
img.resize_exact(
compressed_w as u32,
compressed_h as u32,
FilterType::Lanczos3,
)
.grayscale()
}
pub fn get_hash(img: image::DynamicImage) -> usize {
let mut sum_pixels: usize = 0;
let mut pixels: Vec<usize> = Vec::new();
for (_x, _y, pixel) in img.pixels() {
let red = pixel[0];
sum_pixels += red as usize;
pixels.push(red as usize)
}
let (width, height) = img.dimensions();
let ave = (sum_pixels as f64) / (f64::from(width) * f64::from(height));
let mut hash: usize = 0;
let mut one: usize = 1;
for pixel in pixels {
if pixel as f64 > ave {
hash |= one;
}
one <<= 1
}
hash
}
pub fn get_distance(hash1: usize, hash2: usize, pix_num: usize) -> usize {
let mut d = 0;
for i in 0..pix_num {
let k = 1 << i;
if (hash1 & k) != (hash2 & k) {
d += 1
}
}
d
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_distance_1() {
assert_eq!(get_distance(2247878505465, 2488321179641, 64), 6)
}
#[test]
fn get_distance_2() {
assert_eq!(get_distance(17431013446337445887, 17431022259610337215, 64), 3)
}
}