Skip to main content

wonfy_tools/util/dhash/
mod.rs

1// Code effectively same as https://github.com/9elt/fast-dhash but without multithreading for wasm support
2
3#[derive(Debug, Clone, Copy)]
4pub struct DHash {
5    pub hash: u64,
6}
7
8impl DHash {
9    pub fn new(bytes: &[u8], width: u32, height: u32, channel_count: u8) -> Self {
10        let width = width as usize;
11        let height = height as usize;
12        let channel_count = channel_count as usize;
13
14        // NOTE: Very important, prevents possible segfault
15        if width * height * channel_count != bytes.len() {
16            panic!(
17                "Invalid image dimensions, expected {} got {}",
18                bytes.len(),
19                width * height * channel_count
20            );
21        }
22
23        let cell_width = width / 9;
24        let cell_height = height / 8;
25
26        let grid = if channel_count >= 3 {
27            grid_from_rgb(bytes, width, cell_width, cell_height, channel_count)
28        } else {
29            grid_from_grayscale(bytes, width, cell_width, cell_height, channel_count)
30        };
31
32        let mut bits = [false; 64];
33
34        for y in 0..8 {
35            for x in 0..8 {
36                bits[y * 8 + x] = grid[y][x] > grid[y][x + 1];
37            }
38        }
39
40        let mut hash: u64 = 0;
41
42        for (i, &bit) in bits.iter().enumerate() {
43            if bit {
44                hash += 1 << i;
45            }
46        }
47
48        Self { hash }
49    }
50
51    pub fn hamming_distance(&self, other: &Self) -> u32 {
52        (self.hash ^ other.hash).count_ones()
53    }
54}
55
56impl PartialEq for DHash {
57    fn eq(&self, other: &Self) -> bool {
58        self.hamming_distance(other) < 11
59    }
60}
61
62fn grid_from_rgb(
63    bytes: &[u8],
64    width: usize,
65    cell_width: usize,
66    cell_height: usize,
67    channel_count: usize,
68) -> [[f64; 9]; 8] {
69    let mut grid = [[0f64; 9]; 8];
70
71    for y in 0..8 {
72        let mut row = [0f64; 9];
73
74        for (x, cell) in row.iter_mut().enumerate() {
75            let from = x * cell_width;
76            let to = from + cell_width;
77
78            let mut rs = 0f64;
79            let mut gs = 0f64;
80            let mut bs = 0f64;
81
82            for image_x in from..to {
83                let from = y * cell_height;
84                let to = from + cell_height;
85
86                for image_y in from..to {
87                    let i = (image_y * width + image_x) * channel_count;
88
89                    unsafe {
90                        rs += *bytes.get_unchecked(i) as f64;
91                        gs += *bytes.get_unchecked(i + 1) as f64;
92                        bs += *bytes.get_unchecked(i + 2) as f64;
93                    }
94                }
95            }
96
97            *cell += rs * 0.299 + gs * 0.587 + bs * 0.114;
98        }
99
100        grid[y] = row
101    }
102
103    grid
104}
105
106fn grid_from_grayscale(
107    bytes: &[u8],
108    width: usize,
109    cell_width: usize,
110    cell_height: usize,
111    channel_count: usize,
112) -> [[f64; 9]; 8] {
113    let mut grid = [[0f64; 9]; 8];
114
115    for y in 0..8 {
116        let mut row = [0f64; 9];
117
118        for (x, cell) in row.iter_mut().enumerate() {
119            let from = x * cell_width;
120            let to = from + cell_width;
121
122            let mut luma = 0f64;
123
124            for image_x in from..to {
125                let from = y * cell_height;
126                let to = from + cell_height;
127
128                for image_y in from..to {
129                    let i = (image_y * width + image_x) * channel_count;
130
131                    unsafe {
132                        luma += *bytes.get_unchecked(i) as f64;
133                    }
134                }
135            }
136
137            *cell += luma;
138        }
139
140        grid[y] = row;
141    }
142
143    grid
144}