1mod utils;
2
3use std::f64::consts::PI;
4use wasm_bindgen::prelude::*;
5
6#[cfg(feature = "wee_alloc")]
9#[global_allocator]
10static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Error {
19 LengthInvalid,
20 LengthMismatch,
21}
22
23#[wasm_bindgen(js_name = "decode")]
37pub fn wasm_decode(blur_hash: &str, width: usize, height: usize) -> Option<Vec<u8>> {
38 match decode(blur_hash, width, height) {
39 Ok(img) => Some(img),
40 Err(_err) => None,
41 }
42}
43
44pub fn decode(blur_hash: &str, width: usize, height: usize) -> Result<Vec<u8>, Error> {
45 if blur_hash.len() < 6 {
46 return Err(Error::LengthInvalid);
47 }
48
49 let size_flag = decode_base83_string(blur_hash.get(0..1).unwrap());
53
54 let num_y = (size_flag / 9) + 1;
55 let num_x = (size_flag % 9) + 1;
56
57 let expected_digits = 4 + 2 * num_x * num_y;
60
61 if blur_hash.len() != expected_digits {
62 return Err(Error::LengthMismatch);
63 }
64
65 let quantised_maximum_value = decode_base83_string(blur_hash.get(1..2).unwrap());
69 let maximum_value = ((quantised_maximum_value + 1) as f64) / 166f64;
70
71 let mut colours: Vec<[f64; 3]> = Vec::new();
72
73 for i in 0..(num_x * num_y) {
74 if i == 0 {
75 let value = decode_base83_string(blur_hash.get(2..6).unwrap());
77 colours.push(decode_dc(value));
78 } else {
79 let value = decode_base83_string(blur_hash.get((4 + i * 2)..(4 + i * 2 + 2)).unwrap());
81 colours.push(decode_ac(value, maximum_value * 1.0));
82 }
83 }
84
85 let bytes_per_row = width * 4;
90
91 let mut pixels = vec![0; bytes_per_row * height];
92
93 for y in 0..height {
94 for x in 0..width {
95 let mut r = 0f64;
96 let mut g = 0f64;
97 let mut b = 0f64;
98
99 for j in 0..num_y {
100 for i in 0..num_x {
101 let basis = f64::cos(PI * (x as f64) * (i as f64) / (width as f64))
102 * f64::cos(PI * (y as f64) * (j as f64) / (height as f64));
103 let colour = colours[i + j * num_x];
104 r += colour[0] * basis;
105 g += colour[1] * basis;
106 b += colour[2] * basis;
107 }
108 }
109
110 let int_r = linear_to_srgb(r);
111 let int_g = linear_to_srgb(g);
112 let int_b = linear_to_srgb(b);
113
114 pixels[4 * x + 0 + y * bytes_per_row] = int_r;
115 pixels[4 * x + 1 + y * bytes_per_row] = int_g;
116 pixels[4 * x + 2 + y * bytes_per_row] = int_b;
117 pixels[4 * x + 3 + y * bytes_per_row] = 255;
118 }
119 }
120
121 Ok(pixels)
122}
123
124fn decode_dc(value: usize) -> [f64; 3] {
125 let int_r = value >> 16;
126 let int_g = (value >> 8) & 255;
127 let int_b = value & 255;
128 [
129 srgb_to_linear(int_r),
130 srgb_to_linear(int_g),
131 srgb_to_linear(int_b),
132 ]
133}
134
135fn decode_ac(value: usize, maximum_value: f64) -> [f64; 3] {
136 let quant_r = f64::floor((value / (19 * 19)) as f64);
137 let quant_g = f64::floor(((value / 19) as f64) % 19f64);
138 let quant_b = (value as f64) % 19f64;
139
140 let rgb = [
141 sign_pow((quant_r - 9f64) / 9f64, 2f64) * maximum_value,
142 sign_pow((quant_g - 9f64) / 9f64, 2f64) * maximum_value,
143 sign_pow((quant_b - 9f64) / 9f64, 2f64) * maximum_value,
144 ];
145 rgb
146}
147
148fn sign_pow(value: f64, exp: f64) -> f64 {
149 get_sign(value) * f64::powf(f64::abs(value), exp)
150}
151
152fn get_sign(n: f64) -> f64 {
153 if n < 0f64 {
154 -1f64
155 } else {
156 1f64
157 }
158}
159
160fn linear_to_srgb(value: f64) -> u8 {
161 let v = f64::max(0f64, f64::min(1f64, value));
162 if v <= 0.0031308 {
163 return (v * 12.92 * 255f64 + 0.5) as u8;
164 } else {
165 return ((1.055 * f64::powf(v, 1f64 / 2.4) - 0.055) * 255f64 + 0.5) as u8;
166 }
167}
168
169fn srgb_to_linear(value: usize) -> f64 {
170 let v = (value as f64) / 255f64;
171 if v <= 0.04045 {
172 return v / 12.92;
173 } else {
174 return ((v + 0.055) / 1.055).powf(2.4);
175 }
176}
177
178const ENCODE_CHARACTERS: &str =
180 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
181
182fn decode_base83_string(string: &str) -> usize {
183 let mut value: usize = 0;
184
185 for character in string.chars() {
186 match ENCODE_CHARACTERS.find(character) {
187 Some(digit) => value = value * 83 + digit,
188
189 None => (),
190 }
191 }
192 value
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn it_decodes_size_flag() {
201 assert_eq!(21, decode_base83_string("L"));
202 assert_eq!(0, decode_base83_string("0"));
203 }
204 #[test]
205 fn decodes_size_0_out_of_range() {
206 let res = decode_base83_string("/");
207 assert_eq!(
208 0, res,
209 "Did not expect to decode size for input out of range (expected 0), but got {}",
210 res
211 );
212 }
213}