ajazz_sdk/
images.rs

1use image::{ColorType, DynamicImage, GenericImageView, ImageError};
2use image::codecs::jpeg::JpegEncoder;
3use image::imageops::FilterType;
4
5use crate::{Kind, AjazzError};
6
7/// Image rotation
8#[derive(Copy, Clone, Debug, Hash)]
9pub enum ImageRotation {
10    /// No rotation
11    Rot0,
12    /// 90 degrees clockwise
13    Rot90,
14    /// 180 degrees
15    Rot180,
16    /// 90 degrees counter-clockwise
17    Rot270,
18}
19
20/// Image mirroring
21#[derive(Copy, Clone, Debug, Hash)]
22pub enum ImageMirroring {
23    /// No image mirroring
24    None,
25    /// Flip by X
26    X,
27    /// Flip by Y
28    Y,
29    /// Flip by both axes
30    Both,
31}
32
33/// Image format
34#[derive(Copy, Clone, Debug, Hash)]
35pub enum ImageMode {
36    /// No image
37    None,
38    /// Jpeg image
39    JPEG,
40}
41
42/// Image format used by the device
43#[derive(Copy, Clone, Debug, Hash)]
44pub struct ImageFormat {
45    /// Image format/mode
46    pub mode: ImageMode,
47    /// Image size
48    pub size: (usize, usize),
49    /// Image rotation
50    pub rotation: ImageRotation,
51    /// Image mirroring
52    pub mirror: ImageMirroring,
53}
54
55impl Default for ImageFormat {
56    fn default() -> Self {
57        Self {
58            mode: ImageMode::None,
59            size: (0, 0),
60            rotation: ImageRotation::Rot0,
61            mirror: ImageMirroring::None,
62        }
63    }
64}
65
66/// Converts image into image data depending on provided kind of device
67pub fn convert_image(kind: Kind, image: DynamicImage) -> Result<Vec<u8>, ImageError> {
68    convert_image_with_format(kind.key_image_format(), image)
69}
70
71/// Converts image into image data depending on provided image format
72pub fn convert_image_with_format(
73    image_format: ImageFormat,
74    image: DynamicImage,
75) -> Result<Vec<u8>, ImageError> {
76    // Ensuring size of the image
77    let (ws, hs) = image_format.size;
78
79    // Applying rotation
80    let image = match image_format.rotation {
81        ImageRotation::Rot0 => image,
82        ImageRotation::Rot90 => image.rotate90(),
83        ImageRotation::Rot180 => image.rotate180(),
84        ImageRotation::Rot270 => image.rotate270(),
85    };
86
87    let image = image.resize_exact(ws as u32, hs as u32, FilterType::Triangle);
88
89    // Applying mirroring
90    let image = match image_format.mirror {
91        ImageMirroring::None => image,
92        ImageMirroring::X => image.fliph(),
93        ImageMirroring::Y => image.flipv(),
94        ImageMirroring::Both => image.fliph().flipv(),
95    };
96
97    let image_data = image.into_rgb8().to_vec();
98
99    // Encoding image
100    match image_format.mode {
101        ImageMode::None => Ok(vec![]),
102        ImageMode::JPEG => {
103            let mut buf = Vec::new();
104            let mut encoder = JpegEncoder::new_with_quality(&mut buf, 90);
105            encoder.encode(&image_data, ws as u32, hs as u32, ColorType::Rgb8.into())?;
106            Ok(buf)
107        }
108    }
109}
110
111/// Converts image into image data depending on provided kind of device, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
112#[cfg(feature = "async")]
113#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
114pub fn convert_image_async(kind: Kind, image: DynamicImage) -> Result<Vec<u8>, AjazzError> {
115    Ok(tokio::task::block_in_place(move || {
116        convert_image(kind, image)
117    })?)
118}
119
120/// Converts image into image data depending on provided image format, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
121#[cfg(feature = "async")]
122#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
123pub fn convert_image_with_format_async(
124    format: ImageFormat,
125    image: DynamicImage,
126) -> Result<Vec<u8>, AjazzError> {
127    Ok(tokio::task::block_in_place(move || {
128        convert_image_with_format(format, image)
129    })?)
130}
131
132/// Rect to be used when trying to send image to lcd screen
133pub struct ImageRect {
134    /// Width of the image
135    pub w: u16,
136
137    /// Height of the image
138    pub h: u16,
139
140    /// Data of the image row by row as RGB
141    pub data: Vec<u8>,
142}
143
144impl ImageRect {
145    /// Converts image to image rect
146    pub fn from_image(image: DynamicImage) -> Result<ImageRect, AjazzError> {
147        let (image_w, image_h) = image.dimensions();
148
149        let image_data = image.into_rgb8().to_vec();
150
151        let mut buf = Vec::new();
152        let mut encoder = JpegEncoder::new_with_quality(&mut buf, 90);
153        encoder.encode(&image_data, image_w, image_h, ColorType::Rgb8.into())?;
154
155        Ok(ImageRect {
156            w: image_w as u16,
157            h: image_h as u16,
158            data: buf,
159        })
160    }
161
162    /// Converts image to image rect, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
163    #[cfg(feature = "async")]
164    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
165    pub fn from_image_async(image: DynamicImage) -> Result<ImageRect, AjazzError> {
166        tokio::task::block_in_place(move || ImageRect::from_image(image))
167    }
168}
169
170#[derive(Clone, Copy)]
171pub(crate) struct WriteImageParameters {
172    pub image_report_length: usize,
173    pub image_report_payload_length: usize,
174}
175
176impl WriteImageParameters {
177    pub fn for_kind(kind: Kind) -> Self {
178        let image_report_length = match kind {
179            kind if kind.is_v1_api() => 513,
180            kind if kind.is_v2_api() => 1025,
181            _ => 1024,
182        };
183
184        let image_report_header_length = 1;
185        let image_report_payload_length = image_report_length - image_report_header_length;
186
187        Self {
188            image_report_length,
189            image_report_payload_length,
190        }
191    }
192}