wonfy_tools/util/dhash/
mod.rs1#[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 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}