Skip to main content

busybar_render/
encoding.rs

1use std::fmt;
2use std::io::Cursor;
3use std::path::Path;
4
5use base64::Engine as _;
6use base64::engine::general_purpose::STANDARD;
7
8use crate::error::RenderError;
9use crate::raw::RawImage;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ImageFormat {
13    Bmp,
14    Jpeg,
15    Png,
16}
17
18impl ImageFormat {
19    pub fn from_path(path: &Path) -> Option<Self> {
20        let extension = path.extension()?.to_str()?.to_ascii_lowercase();
21
22        match extension.as_str() {
23            "bmp" => Some(Self::Bmp),
24            "jpg" | "jpeg" => Some(Self::Jpeg),
25            "png" => Some(Self::Png),
26            _ => None,
27        }
28    }
29
30    fn encoding(self) -> image::ImageFormat {
31        match self {
32            Self::Bmp => image::ImageFormat::Bmp,
33            Self::Jpeg => image::ImageFormat::Jpeg,
34            Self::Png => image::ImageFormat::Png,
35        }
36    }
37}
38
39impl fmt::Display for ImageFormat {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::Bmp => f.write_str("bmp"),
43            Self::Jpeg => f.write_str("jpeg"),
44            Self::Png => f.write_str("png"),
45        }
46    }
47}
48
49impl RawImage {
50    pub fn encode(&self, format: ImageFormat) -> Result<Vec<u8>, RenderError> {
51        let mut buffer = Cursor::new(Vec::new());
52
53        self.buffer()
54            .write_to(&mut buffer, format.encoding())
55            .map_err(|source| RenderError::Encode {
56                width: self.width(),
57                height: self.height(),
58                format,
59                source,
60            })?;
61
62        Ok(buffer.into_inner())
63    }
64
65    pub fn encode_base64(&self, format: ImageFormat) -> Result<String, RenderError> {
66        self.encode(format).map(|bytes| STANDARD.encode(bytes))
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::raw::PixelLayout;
74
75    #[test]
76    fn picks_a_format_from_the_file_extension() {
77        assert_eq!(
78            ImageFormat::from_path(Path::new("frame.bmp")),
79            Some(ImageFormat::Bmp)
80        );
81        assert_eq!(
82            ImageFormat::from_path(Path::new("frame.JPEG")),
83            Some(ImageFormat::Jpeg)
84        );
85        assert_eq!(
86            ImageFormat::from_path(Path::new("./out/frame.png")),
87            Some(ImageFormat::Png)
88        );
89        assert_eq!(ImageFormat::from_path(Path::new("frame.raw")), None);
90        assert_eq!(ImageFormat::from_path(Path::new("frame")), None);
91    }
92
93    #[test]
94    fn encodes_every_format_it_supports() {
95        let image = RawImage::new(2, 2, PixelLayout::Gray8, &[0, 64, 128, 255]).unwrap();
96
97        assert!(image.encode(ImageFormat::Bmp).unwrap().starts_with(b"BM"));
98        assert!(
99            image
100                .encode(ImageFormat::Jpeg)
101                .unwrap()
102                .starts_with(&[0xff, 0xd8])
103        );
104        assert!(
105            image
106                .encode(ImageFormat::Png)
107                .unwrap()
108                .starts_with(b"\x89PNG")
109        );
110    }
111
112    #[test]
113    fn encodes_to_base64() {
114        let image = RawImage::new(1, 1, PixelLayout::Gray8, &[0x7f]).unwrap();
115        let encoded = image.encode_base64(ImageFormat::Png).unwrap();
116
117        assert_eq!(
118            STANDARD.decode(&encoded).unwrap(),
119            image.encode(ImageFormat::Png).unwrap()
120        );
121    }
122}