Skip to main content

simimgrs/
lib.rs

1//! This crate checks for similar image using average hash algorism.
2//! The average of the luminance values ​​is calculated, and the 64-bit
3//! hash value is calculated as “1” for greater than the average
4//! and “0” for less than the average.
5
6use image::{self, FilterType, GenericImageView};
7use std::sync::mpsc;
8use std::thread;
9
10/// SimilarChecker has settings for detecting similar image.
11// #[derive(Default)]
12pub struct SimilarChecker {
13    threshold: usize,
14    compressed_w: usize,
15    compressed_h: usize,
16}
17
18impl Default for SimilarChecker {
19    fn default() -> Self {
20        SimilarChecker {
21            threshold: 10,
22            compressed_w: 8,
23            compressed_h: 8,
24        }
25    }
26}
27
28impl SimilarChecker {
29    /// Inits SimilarChecker.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// let checker = simimgrs::SimilarChecker::new();
35    /// ```
36    pub fn new() -> Self {
37        // sets default setting.
38        SimilarChecker {
39            threshold: 10,
40            compressed_w: 8,
41            compressed_h: 8,
42        }
43    }
44
45    /// Sets compression_size parametor for SimilarChecker.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// let checker = simimgrs::SimilarChecker::new().compression_size(10, 10);
51    /// ```
52    pub fn compression_size(self, width: usize, height: usize) -> Self {
53        SimilarChecker {
54            compressed_w: width,
55            compressed_h: height,
56            ..self
57        }
58    }
59
60    /// Sets threshold parametor for SimilarChecker.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// let checker = simimgrs::SimilarChecker::new().threshold(10);
66    /// ```
67    pub fn threshold(self, threshold: usize) -> Self {
68        SimilarChecker { threshold, ..self }
69    }
70
71    /// Checks for similar image using average hash algorism.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// let img1 = image::open("testdata/aws_batch.png").unwrap();
77    /// let img2 = image::open("testdata/aws_rekognition.png").unwrap();
78
79    /// let checker = simimgrs::SimilarChecker::new();
80    /// assert!(!checker.is_similar(img1, img2))
81    /// ```
82    pub fn is_similar(&self, img1: image::DynamicImage, img2: image::DynamicImage) -> bool {
83        let w = self.compressed_w;
84        let h = self.compressed_h;
85
86        let (tx, rx) = mpsc::channel();
87        let tx1 = mpsc::Sender::clone(&tx);
88
89        thread::spawn(move || {
90            let hash1 = get_hash(process(img1, w, h));
91            tx1.send(hash1).unwrap();
92        });
93
94        thread::spawn(move || {
95            let hash2 = get_hash(process(img2, w, h));
96            tx.send(hash2).unwrap();
97        });
98
99        let mut v: Vec<usize> = Vec::new();
100        for received in rx {
101            v.push(received)
102        }
103
104        let distance = get_distance(v[0], v[1], w*h);
105        distance < self.threshold
106    }
107}
108
109fn process(
110    img: image::DynamicImage,
111    compressed_w: usize,
112    compressed_h: usize,
113) -> image::DynamicImage {
114    img.resize_exact(
115        compressed_w as u32,
116        compressed_h as u32,
117        FilterType::Lanczos3,
118    )
119    .grayscale()
120}
121
122/// get_hash calculate average hash.
123///
124/// # Examples
125///
126/// ```
127/// let img = image::open("testdata/aws_batch.png").unwrap();
128/// assert_eq!(simimgrs::get_hash(img), 18446744073709551615)
129/// ```
130pub fn get_hash(img: image::DynamicImage) -> usize {
131    let mut sum_pixels: usize = 0;
132    let mut pixels: Vec<usize> = Vec::new();
133
134    // TODO: supports other than gray image.
135    for (_x, _y, pixel) in img.pixels() {
136        let red = pixel[0];
137        sum_pixels += red as usize;
138        pixels.push(red as usize)
139    }
140
141    let (width, height) = img.dimensions();
142    let ave = (sum_pixels as f64) / (f64::from(width) * f64::from(height));
143
144    let mut hash: usize = 0;
145    let mut one: usize = 1;
146
147    for pixel in pixels {
148        if pixel as f64 > ave {
149            hash |= one;
150        }
151        one <<= 1
152    }
153    hash
154}
155
156/// get_distance gets distance between two average hash.
157///
158/// # Examples
159///
160/// ```
161/// assert_eq!(simimgrs::get_distance(1110, 1101, 64), 4)
162/// ```
163pub fn get_distance(hash1: usize, hash2: usize, pix_num: usize) -> usize {
164    let mut d = 0;
165    for i in 0..pix_num {
166        let k = 1 << i;
167        if (hash1 & k) != (hash2 & k) {
168            d += 1
169        }
170    }
171    d
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    #[test]
178    fn get_distance_1() {
179        assert_eq!(get_distance(2247878505465, 2488321179641, 64), 6)
180    }
181
182    #[test]
183    fn get_distance_2() {
184        assert_eq!(get_distance(17431013446337445887, 17431022259610337215, 64), 3)
185    }
186}