1pub mod base83;
105pub mod convert;
106
107use std::f32::consts::PI;
108use convert::*;
109use base83::encode_fixed_to;
110
111#[derive(Debug, Clone, Copy, PartialEq)]
112pub enum BlurhashError {
113 InvalidLength,
116 InvalidPunch,
118 BadFormat(base83::Base83ConversionError),
120 UnsupportedMode,
122}
123
124impl std::fmt::Display for BlurhashError {
125 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
126 use BlurhashError::*;
127 match self {
128 InvalidLength => write!(fmt, "The extracted length of the blurhash does not match the actual length"),
129 InvalidPunch => write!(fmt, "The punch parameter must be positive and non-zero"),
130 BadFormat(kind) => write!(fmt, "The blurhash contains invalid base83 codes ({kind:?})"),
131 UnsupportedMode => write!(fmt, "The blurhash's number of X or Y components was greater than 9")
132 }
133 }
134}
135
136impl From<base83::Base83ConversionError> for BlurhashError {
137 fn from(err: base83::Base83ConversionError) -> Self {
138 Self::BadFormat(err)
139 }
140}
141
142#[derive(Default, Clone, Debug)]
146pub struct DCTResult {
147 ac_max: f32,
149 currents: Vec<Factor>,
154 x_components: usize,
156 y_components: usize
158}
159
160impl DCTResult {
161 pub fn new(ac_max: f32, currents: Vec<Factor>, x_components: usize, y_components: usize) -> DCTResult {
163 assert!(currents.len() == x_components * y_components);
164 assert!(ac_max != 0.);
165
166 DCTResult { ac_max, currents, x_components, y_components }
167 }
168
169 pub fn into_blurhash(self) -> String {
172 encode(&self)
173 }
174
175 pub fn to_image<T>(&self, width: usize, height: usize, convert: fn(Linear) -> T) -> Vec<T> {
179 let mut pixels = Vec::with_capacity(width * height);
180
181 for y in 0..height {
182 let percent_y = y as f32 / height as f32;
183 for x in 0..width {
184 let percent_x = x as f32 / width as f32;
185
186 let mut col = inv_multiply_basis(self.x_components, self.y_components,
187 percent_x, percent_y, &self.currents);
188
189 col[0] = col[0].max(0.).min(1.);
190 col[1] = col[1].max(0.).min(1.);
191 col[2] = col[2].max(0.).min(1.);
192
193 pixels.push(convert(col));
194 }
195 }
196
197 pixels
198 }
199
200 pub fn to_rgb8(&self, width: usize, height: usize) -> Vec<[u8; 3]> {
204 self.to_image(width, height, |col| [
205 linear_to_srgb(col[0]),
206 linear_to_srgb(col[1]),
207 linear_to_srgb(col[2]),
208 ])
209 }
210
211 pub fn to_rgba8(&self, width: usize, height: usize) -> Vec<[u8; 4]> {
215 self.to_image(width, height, |col| [
216 linear_to_srgb(col[0]),
217 linear_to_srgb(col[1]),
218 linear_to_srgb(col[2]),
219 255
220 ])
221 }
222
223 pub fn to_rgba(&self, width: usize, height: usize) -> Vec<u32> {
227 self.to_image(width, height, |col|
228 ((linear_to_srgb(col[2]) as u32) << 0) |
229 ((linear_to_srgb(col[1]) as u32) << 8) |
230 ((linear_to_srgb(col[0]) as u32) << 16) |
231 ((255 as u32) << 24)
232 )
233 }
234
235 pub fn currents(&self) -> &[Factor] {
240 &self.currents
241 }
242
243 pub fn acs(&self) -> &[Factor] {
247 &self.currents[1..]
248 }
249
250 pub fn dc(&self) -> &Factor {
254 &self.currents[0]
255 }
256
257 pub fn x_components(&self) -> usize {
259 self.x_components
260 }
261
262 pub fn y_components(&self) -> usize {
264 self.y_components
265 }
266
267 pub fn dim(&self) -> (usize, usize) {
269 (self.x_components, self.y_components)
270 }
271}
272
273pub fn encode(dct: &DCTResult) -> String {
277 let DCTResult { mut ac_max, currents, x_components, y_components } = dct;
278 assert!((1..=9).contains(x_components), "The number of X components must be between 1 and 9");
279 assert!((1..=9).contains(y_components), "The number of Y components must be between 1 and 9");
280
281 let mut blurhash = String::with_capacity(1 + 1 + 4 + 2 * (currents.len() - 1));
282
283 encode_fixed_to(((x_components - 1) + (y_components - 1) * 9) as u32, 1, &mut blurhash);
284
285 if currents.len() > 0 {
286 let quantised_max = (ac_max * 166. - 0.5).floor().min(82.).max(0.);
287 encode_fixed_to(quantised_max as u32, 1, &mut blurhash);
288 ac_max = (quantised_max + 1.) / 166.;
289 } else {
290 encode_fixed_to(0, 1, &mut blurhash);
291 }
292
293 encode_fixed_to(to_rgb(currents[0]), 4, &mut blurhash);
294
295 for &ac in currents.iter().skip(1) {
296 encode_fixed_to(encode_ac(ac, ac_max), 2, &mut blurhash);
297 }
298
299 blurhash
300}
301
302pub fn decode(blurhash: &str, punch: f32) -> Result<DCTResult, BlurhashError> {
306 if punch <= 0. {
307 return Err(BlurhashError::InvalidPunch)
308 }
309
310 if blurhash.is_empty() {
311 return Err(BlurhashError::InvalidLength)
312 }
313 let total = base83::decode(&blurhash[..1])? as usize;
314 let (x_components, y_components) = ((total % 9) + 1, (total / 9) + 1);
315
316 if x_components > 9 || y_components > 9 {
317 return Err(BlurhashError::UnsupportedMode)
318 }
319
320 let current_count = x_components * y_components;
321 if blurhash.len() != 1 + 1 + 4 + 2 * (current_count - 1) {
322 return Err(BlurhashError::InvalidLength)
323 }
324
325 let ac_max = base83::decode(&blurhash[1..2])? + 1;
326 let ac_max = ((ac_max as f32) / 166.) * punch;
327
328 let mut currents = Vec::with_capacity(current_count);
329 currents.push(decode_dc(base83::decode(&blurhash[2..6])?));
330
331 for i in 1..current_count {
332 let idx = (i - 1) * 2 + 6;
333 let ac = base83::decode(&blurhash[idx..(idx + 2)])?;
334 currents.push(decode_ac(ac, ac_max));
335 }
336
337 Ok(DCTResult { ac_max, currents, x_components, y_components })
338}
339
340pub fn compute_dct_iter<T: AsLinear>(image: impl Iterator<Item = T>, width: usize, height: usize, x_components: usize, y_components: usize) -> DCTResult {
350 let mut currents: Vec<Factor> = vec![[0., 0., 0.]; x_components * y_components];
351
352 let total = width * height;
353 for (i, pixel) in image.take(total).enumerate() {
354 let col = pixel.as_linear();
355
356 let p = i as f32 / width as f32;
357 let percent_y = p / height as f32;
358 let percent_x = p.fract();
359
360 multiply_basis(x_components, y_components, percent_x, percent_y, &col, &mut currents);
361 }
362
363 let ac_max = normalize_and_max(&mut currents, total);
364
365 DCTResult { ac_max, currents, x_components, y_components }
366}
367
368pub fn compute_dct<T: AsLinear>(image: &[T], width: usize, height: usize, x_components: usize, y_components: usize) -> DCTResult {
378 assert!(image.len() >= width * height);
379 let mut currents: Vec<Factor> = vec![[0., 0., 0.]; x_components * y_components];
380
381 for y in 0..height {
382 let percent_y = y as f32 / height as f32;
383 for x in 0..width {
384 let percent_x = x as f32 / width as f32;
385
386 let col = image[y * width + x].as_linear();
387 multiply_basis(x_components, y_components, percent_x, percent_y, &col, &mut currents);
388 }
389 }
390
391 let ac_max = normalize_and_max(&mut currents, width * height);
392
393 DCTResult { ac_max, currents, x_components, y_components }
394}
395
396#[inline]
401pub fn multiply_basis(x_comps: usize, y_comps: usize, x: f32, y: f32, col: &[f32; 3], currents: &mut [Factor]) {
402 for comp_y in 0..y_comps {
403 let base_y = (PI * comp_y as f32 * y).cos();
404
405 for comp_x in 0..x_comps {
406 let f = &mut currents[comp_y * x_comps + comp_x];
407
408 let base_x = (PI * comp_x as f32 * x).cos();
409 let basis = base_y * base_x;
410
411 f[0] += basis * col[0];
412 f[1] += basis * col[1];
413 f[2] += basis * col[2];
414 }
415 }
416}
417
418#[inline]
422pub fn inv_multiply_basis(x_comps: usize, y_comps: usize, x: f32, y: f32, currents: &[Factor]) -> [f32; 3] {
423 let mut col = [0.; 3];
424 for comp_y in 0..y_comps {
425 let base_y = (PI * comp_y as f32 * y).cos();
426
427 for comp_x in 0..x_comps {
428 let f = currents[comp_y * x_comps + comp_x];
429
430 let base_x = (PI * comp_x as f32 * x).cos();
431 let basis = base_y * base_x;
432
433 col[0] += basis * f[0];
434 col[1] += basis * f[1];
435 col[2] += basis * f[2];
436 }
437 }
438
439 col
440}
441
442pub fn normalize_and_max(currents: &mut [Factor], len: usize) -> f32 {
448 let len = len as f32;
449 let norm = 1. / len; let f = &mut currents[0];
451 f[0] *= norm;
452 f[1] *= norm;
453 f[2] *= norm;
454
455 if currents.len() == 1 {
456 return 1.
457 }
458
459 let mut ac_max = 0f32;
460 let norm = 2. / len; for f in currents.iter_mut().skip(1).flatten() {
462 *f *= norm;
463 ac_max = ac_max.max(f.abs());
464 }
465
466 ac_max
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn test_multiply_basis() {
475 let width: usize = 4;
476 let height: usize = 4;
477 let x_comps: usize = 5;
478 let y_comps: usize = 5;
479 let image: [Linear; 16] = [
480 [1., 1., 1.], [0., 0., 0.], [1., 1., 1.], [0., 0., 0.],
481 [0., 0., 0.], [0., 0., 0.], [1., 1., 1.], [0., 0., 0.],
482 [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.],
483 [0., 0., 0.], [0., 0., 0.], [1., 1., 1.], [0., 0., 0.],
484 ];
485 let mut currents: Vec<Factor> = vec![[0., 0., 0.]; x_comps * y_comps];
486
487 for y in 0..height {
488 let percent_y = y as f32 / height as f32;
489 for x in 0..width {
490 let percent_x = x as f32 / width as f32;
491 multiply_basis(x_comps, y_comps, percent_x, percent_y,
492 &image[y * width + x], &mut currents);
493 }
494 }
495
496 let average_color = [8., 8., 8.]; assert_eq!(currents[0 * x_comps + 0], average_color);
498
499 assert_eq!(currents[2 * x_comps + 0], [-2., -2., -2.]);
510
511 assert_eq!(currents[0 * x_comps + 2], [-2., -2., -2.]);
522
523 assert_eq!(currents[3 * x_comps + 3], [1., 1., 1.]);
534
535 assert_eq!(currents[2 * x_comps + 4], [2., 2., 2.]);
546
547 assert_eq!(currents[4 * x_comps + 2], [2., 2., 2.]);
558 }
559
560 #[test]
561 fn test_encode_33() {
562 let image: [Rgb; 16] = [
563 [255, 0, 0], [ 0, 0, 0], [255, 255, 255], [ 0, 0, 0],
564 [ 0, 0, 0], [ 0, 0, 0], [255, 255, 255], [ 0, 0, 0],
565 [255, 255, 255], [255, 255, 255], [ 0, 255, 0], [255, 255, 255],
566 [ 0, 0, 0], [ 0, 0, 0], [255, 255, 255], [ 0, 0, 0],
567 ];
568 assert_eq!(compute_dct(&image, 4, 4, 3, 3).into_blurhash(), "KzKUZY=|HZ=|$5e9HZe9IS");
569 }
570
571 #[test]
572 fn test_encode_decode_no_comps() {
573 let image: [Rgb; 16] = [[255, 127, 55]; 16];
574 let dct = compute_dct(&image, 4, 4, 1, 1);
575 let blurhash = encode(&dct);
576 assert_eq!(blurhash, "0~TNl]");
577
578 let inv = decode(&blurhash, 1.).unwrap();
579 assert_eq!(inv.x_components, dct.x_components);
580 assert_eq!(inv.y_components, dct.y_components);
581
582 for (i, (a, b)) in inv.currents.iter().flatten().zip(dct.currents.iter().flatten()).enumerate() {
583 assert!((a - b).abs() < 0.05, "{a}, {b}: Error too big at index {i}");
584 }
585 }
586
587 #[test]
588 fn test_encode_decode_white() {
589 let image: [Rgb; 16] = [[255, 255, 255]; 16];
590 let dct = compute_dct(&image, 4, 4, 4, 4);
591 let blurhash = encode(&dct);
592 assert_eq!(blurhash, "U~TSUA~qfQ~q~q%MfQ%MfQfQfQfQ~q%MfQ%M");
593 let inv = decode(&blurhash, 1.).unwrap();
594 assert_eq!(inv.x_components, dct.x_components);
595 assert_eq!(inv.y_components, dct.y_components);
596
597 for (i, (a, b)) in inv.currents.iter().flatten().zip(dct.currents.iter().flatten()).enumerate() {
598 assert!((a - b).abs() < 0.05, "{a}, {b}: Error too big at index {i}");
599 }
600
601 let generated = inv.to_image(4, 4, |p| p);
602 println!("{generated:?}");
603 for (i, &pixel) in generated.iter().flatten().enumerate() {
604 assert!((pixel - 1.).abs() < 0.05, "Expected white pixel got {pixel} at index {i}");
605 }
606 }
607
608 #[test]
609 fn test_encode_decode_black() {
610 let image: [Rgb; 16] = [[0, 0, 0]; 16];
611 let dct = compute_dct(&image, 4, 4, 4, 4);
612 let blurhash = encode(&dct);
613 assert_eq!(blurhash, "U00000fQfQfQfQfQfQfQfQfQfQfQfQfQfQfQ");
614
615 let inv = decode(&blurhash, 1.).unwrap();
616 assert_eq!(inv.x_components, dct.x_components);
617 assert_eq!(inv.y_components, dct.y_components);
618 assert_eq!(inv.dc(), dct.dc());
619
620 for (i, (a, b)) in inv.currents.iter().flatten().zip(dct.currents.iter().flatten()).enumerate() {
621 assert!((a - b).abs() < 0.05, "{a}, {b} at index {i}");
622 }
623
624 let generated = inv.to_image(4, 4, |p| p);
625 for (i, &pixel) in generated.iter().flatten().enumerate() {
626 assert!((pixel - 0.).abs() < 0.05, "Expected black pixel got {pixel} at index {i}");
627 }
628 }
629
630 use ril::prelude::Image;
631
632 impl AsLinear for &ril::pixel::Rgb {
633 fn as_linear(&self) -> Linear {
634 [srgb_to_linear(self.r), srgb_to_linear(self.g), srgb_to_linear(self.b)]
635 }
636 }
637
638 #[test]
639 fn test_encode_image() {
640 let img = Image::<ril::pixel::Rgb>::open("test.webp").unwrap();
641 let w = img.width() as usize;
642 let h = img.height() as usize;
643 let s = compute_dct_iter(img.pixels().flatten(), w, h, 4, 7);
644 assert_eq!(s.into_blurhash(), "vbHCG?SgNGxD~pX9R+i_NfNIt7V@NL%Mt7Rj-;t7e:WCfPWXV[ofM{WXbHof");
645 }
646
647 #[test]
648 fn test_decode_image() {
649 let s = decode("vbHLxdSgNHxD~pX9R+i_NfNIt7V@NL%Mt7Rj-;t7e:WCj[WXV[ofM{WXbHof", 1.)
650 .unwrap().to_rgb8(32, 48);
651
652 let img = Image::<ril::pixel::Rgb>::from_fn(32, 48, |x, y| {
653 let [r, g, b] = s[y as usize * 32 + x as usize];
654 ril::pixel::Rgb::new(r, g, b)
655 });
656 img.save_inferred("out.webp").unwrap();
657 }
658}