1mod ac;
25mod base83;
26mod dc;
27mod util;
28
29use std::f32::consts::PI;
30pub use util::{linear_to_srgb, srgb_to_linear};
31
32pub fn encode(components_x: u32, components_y: u32, width: u32, height: u32, rgb: &[u8]) -> String {
34 if components_x < 1 || components_x > 9 || components_y < 1 || components_y > 9 {
35 panic!("BlurHash must have between 1 and 9 components");
36 }
37
38 let mut factors: Vec<[f32; 3]> = Vec::new();
39
40 for y in 0..components_y {
41 for x in 0..components_x {
42 let factor = multiply_basis_function(x, y, width, height, rgb);
43 factors.push(factor);
44 }
45 }
46
47 let dc = factors[0];
48 let ac = &factors[1..];
49
50 let mut blurhash = String::new();
51
52 let size_flag = (components_x - 1) + (components_y - 1) * 9;
53 blurhash.push_str(&base83::encode(size_flag, 1));
54
55 let maximum_value: f32;
56 if !ac.is_empty() {
57 let mut actualmaximum_value = 0.0;
58 for i in 0..components_y * components_x - 1 {
59 actualmaximum_value = f32::max(f32::abs(ac[i as usize][0]), actualmaximum_value);
60 actualmaximum_value = f32::max(f32::abs(ac[i as usize][1]), actualmaximum_value);
61 actualmaximum_value = f32::max(f32::abs(ac[i as usize][2]), actualmaximum_value);
62 }
63
64 let quantised_maximum_value = f32::max(
65 0.,
66 f32::min(82., f32::floor(actualmaximum_value * 166. - 0.5)),
67 ) as u32;
68
69 maximum_value = (quantised_maximum_value + 1) as f32 / 166.;
70 blurhash.push_str(&base83::encode(quantised_maximum_value, 1));
71 } else {
72 maximum_value = 1.;
73 blurhash.push_str(&base83::encode(0, 1));
74 }
75
76 blurhash.push_str(&base83::encode(dc::encode(dc), 4));
77
78 for i in 0..components_y * components_x - 1 {
79 blurhash.push_str(&base83::encode(
80 ac::encode(ac[i as usize], maximum_value),
81 2,
82 ));
83 }
84
85 blurhash
86}
87
88fn multiply_basis_function(
89 component_x: u32,
90 component_y: u32,
91 width: u32,
92 height: u32,
93 rgb: &[u8],
94) -> [f32; 3] {
95 let mut r = 0.;
96 let mut g = 0.;
97 let mut b = 0.;
98 let normalisation = match (component_x, component_y) {
99 (0, 0) => 1.,
100 _ => 2.,
101 };
102
103 let bytes_per_row = width * 4;
104
105 for y in 0..height {
106 for x in 0..width {
107 let basis = f32::cos(PI * component_x as f32 * x as f32 / width as f32)
108 * f32::cos(PI * component_y as f32 * y as f32 / height as f32);
109 r += basis * srgb_to_linear(u32::from(rgb[(4 * x + y * bytes_per_row) as usize]));
110 g += basis * srgb_to_linear(u32::from(rgb[(4 * x + 1 + y * bytes_per_row) as usize]));
111 b += basis * srgb_to_linear(u32::from(rgb[(4 * x + 2 + y * bytes_per_row) as usize]));
112 }
113 }
114
115 let scale = normalisation / (width * height) as f32;
116
117 [r * scale, g * scale, b * scale]
118}
119
120pub fn decode(blurhash: &str, width: u32, height: u32, punch: f32) -> Vec<u8> {
125 let (num_x, num_y) = components(blurhash);
126
127 let quantised_maximum_value = base83::decode(&blurhash.chars().nth(1).unwrap().to_string());
128 let maximum_value = (quantised_maximum_value + 1) as f32 / 166.;
129
130 let mut colors = vec![[0.; 3]; num_x * num_y];
131
132 for i in 0..colors.len() {
133 if i == 0 {
134 let value = base83::decode(&blurhash[2..6]);
135 colors[i as usize] = dc::decode(value as u32);
136 } else {
137 let value = base83::decode(&blurhash[4 + i * 2..6 + i * 2]);
138 colors[i as usize] = ac::decode(value as u32, maximum_value * punch);
139 }
140 }
141
142 let bytes_per_row = width * 4;
143 let mut pixels = vec![0; (bytes_per_row * height) as usize];
144
145 for y in 0..height {
146 for x in 0..width {
147 let mut pixel = [0.; 3];
148
149 for j in 0..num_y {
150 for i in 0..num_x {
151 let basis = f32::cos((PI * x as f32 * i as f32) / width as f32)
152 * f32::cos((PI * y as f32 * j as f32) / height as f32);
153 let color = &colors[i + j * num_x as usize];
154
155 pixel[0] += color[0] * basis;
156 pixel[1] += color[1] * basis;
157 pixel[2] += color[2] * basis;
158 }
159 }
160
161 let int_r = linear_to_srgb(pixel[0]);
162 let int_g = linear_to_srgb(pixel[1]);
163 let int_b = linear_to_srgb(pixel[2]);
164
165 pixels[(4 * x + y * bytes_per_row) as usize] = int_r as u8;
166 pixels[(4 * x + 1 + y * bytes_per_row) as usize] = int_g as u8;
167 pixels[(4 * x + 2 + y * bytes_per_row) as usize] = int_b as u8;
168 pixels[(4 * x + 3 + y * bytes_per_row) as usize] = 255 as u8;
169 }
170 }
171 pixels
172}
173
174fn components(blurhash: &str) -> (usize, usize) {
175 if blurhash.len() < 6 {
176 panic!("The blurhash string must be at least 6 characters");
177 }
178
179 let size_flag = base83::decode(&blurhash.chars().nth(0).unwrap().to_string());
180 let num_y = (f32::floor(size_flag as f32 / 9.) + 1.) as usize;
181 let num_x = (size_flag % 9) + 1;
182
183 if blurhash.len() != 4 + 2 * num_x * num_y {
184 panic!(
185 "blurhash length mismatch: length is {} but it should be {}",
186 blurhash.len(),
187 (4 + 2 * num_x * num_y)
188 );
189 }
190
191 (num_x, num_y)
192}
193
194#[cfg(test)]
195mod tests {
196 use super::{decode, encode};
197 use image::GenericImageView;
198 use image::{save_buffer, RGBA};
199
200 #[test]
201 fn decode_blurhash() {
202 let img = image::open("octocat.png").unwrap();
203 let (width, height) = img.dimensions();
204
205 let blurhash = encode(4, 3, width, height, &img.to_rgba().into_vec());
206 let img = decode(&blurhash, width, height, 1.0);
207 save_buffer("out.png", &img, width, height, RGBA(8)).unwrap();
208
209 assert_eq!(img[0..5], [45, 1, 56, 255, 45]);
210 }
211}