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, the get_unchecked reads below rely on this bound
15        if width * height * channel_count != bytes.len() {
16            panic!(
17                "Invalid image dimensions, expected {} bytes got {}",
18                width * height * channel_count,
19                bytes.len()
20            );
21        }
22
23        if width < 9 || height < 8 {
24            panic!("Image must be at least 9x8 pixels to compute a dhash, got {width}x{height}");
25        }
26
27        let cell_width = width / 9;
28        let cell_height = height / 8;
29
30        let grid = if channel_count >= 3 {
31            grid_from_rgb(bytes, width, cell_width, cell_height, channel_count)
32        } else {
33            grid_from_grayscale(bytes, width, cell_width, cell_height, channel_count)
34        };
35
36        let mut bits = [false; 64];
37
38        for y in 0..8 {
39            for x in 0..8 {
40                bits[y * 8 + x] = grid[y][x] > grid[y][x + 1];
41            }
42        }
43
44        let mut hash: u64 = 0;
45
46        for (i, &bit) in bits.iter().enumerate() {
47            if bit {
48                hash += 1 << i;
49            }
50        }
51
52        Self { hash }
53    }
54
55    pub fn hamming_distance(&self, other: &Self) -> u32 {
56        (self.hash ^ other.hash).count_ones()
57    }
58}
59
60impl PartialEq for DHash {
61    fn eq(&self, other: &Self) -> bool {
62        self.hamming_distance(other) < 11
63    }
64}
65
66fn grid_from_rgb(
67    bytes: &[u8],
68    width: usize,
69    cell_width: usize,
70    cell_height: usize,
71    channel_count: usize,
72) -> [[f64; 9]; 8] {
73    let mut grid = [[0f64; 9]; 8];
74
75    for y in 0..8 {
76        let mut row = [0f64; 9];
77
78        for (x, cell) in row.iter_mut().enumerate() {
79            let from = x * cell_width;
80            let to = from + cell_width;
81
82            let mut rs = 0f64;
83            let mut gs = 0f64;
84            let mut bs = 0f64;
85
86            for image_x in from..to {
87                let from = y * cell_height;
88                let to = from + cell_height;
89
90                for image_y in from..to {
91                    let i = (image_y * width + image_x) * channel_count;
92
93                    unsafe {
94                        rs += *bytes.get_unchecked(i) as f64;
95                        gs += *bytes.get_unchecked(i + 1) as f64;
96                        bs += *bytes.get_unchecked(i + 2) as f64;
97                    }
98                }
99            }
100
101            *cell += rs * 0.299 + gs * 0.587 + bs * 0.114;
102        }
103
104        grid[y] = row
105    }
106
107    grid
108}
109
110fn grid_from_grayscale(
111    bytes: &[u8],
112    width: usize,
113    cell_width: usize,
114    cell_height: usize,
115    channel_count: usize,
116) -> [[f64; 9]; 8] {
117    let mut grid = [[0f64; 9]; 8];
118
119    for y in 0..8 {
120        let mut row = [0f64; 9];
121
122        for (x, cell) in row.iter_mut().enumerate() {
123            let from = x * cell_width;
124            let to = from + cell_width;
125
126            let mut luma = 0f64;
127
128            for image_x in from..to {
129                let from = y * cell_height;
130                let to = from + cell_height;
131
132                for image_y in from..to {
133                    let i = (image_y * width + image_x) * channel_count;
134
135                    unsafe {
136                        luma += *bytes.get_unchecked(i) as f64;
137                    }
138                }
139            }
140
141            *cell += luma;
142        }
143
144        grid[y] = row;
145    }
146
147    grid
148}