web_image/
image.rs

1use crate::error::WebImageError;
2use crate::util::blob_into_bytes;
3use image::{DynamicImage, GenericImageView, ImageBuffer, ImageFormat};
4use wasm_bindgen::prelude::wasm_bindgen;
5use wasm_bindgen::{Clamped, JsValue};
6use wasm_bindgen_futures::JsFuture;
7use web_sys::{window, Blob, ColorSpaceConversion, ImageBitmap, ImageBitmapOptions, ImageData};
8
9#[derive(Clone)]
10#[cfg_attr(feature = "serde", allow(clippy::unsafe_derive_deserialize))]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[wasm_bindgen]
13pub struct WebImage {
14    raw_pixels: Vec<u8>,
15    width: u32,
16    height: u32,
17}
18
19impl From<&DynamicImage> for WebImage {
20    fn from(image: &DynamicImage) -> Self {
21        let (width, height) = image.dimensions();
22        let raw_pixels = image.to_rgba8().to_vec();
23
24        Self {
25            raw_pixels,
26            width,
27            height,
28        }
29    }
30}
31
32impl TryFrom<WebImage> for DynamicImage {
33    type Error = WebImageError;
34
35    fn try_from(value: WebImage) -> Result<Self, Self::Error> {
36        let buffer = ImageBuffer::from_raw(value.width, value.height, value.raw_pixels)
37            .ok_or(WebImageError::ConversionError)?;
38
39        Ok(DynamicImage::ImageRgba8(buffer))
40    }
41}
42
43type WebImageResult = Result<WebImage, WebImageError>;
44
45#[wasm_bindgen]
46impl WebImage {
47    #[must_use]
48    pub fn new(raw_pixels: Vec<u8>, width: u32, height: u32) -> Self {
49        Self {
50            raw_pixels,
51            width,
52            height,
53        }
54    }
55
56    /// Try to create a new WebImage from a byte slice
57    ///
58    /// returns: Result<WebImage, WebImageError>
59    ///
60    /// # Errors
61    ///
62    /// Errors if there's an error while decoding the image from given data
63    pub fn try_from_byte_slice(bytes: &[u8]) -> WebImageResult {
64        let img = image::load_from_memory_with_format(bytes, ImageFormat::Jpeg)?;
65        let raw_pixels = img.to_rgba8().to_vec();
66
67        Ok(Self {
68            raw_pixels,
69            width: img.width(),
70            height: img.height(),
71        })
72    }
73
74    /// Try to create a new WebImage from a blob
75    ///
76    /// returns: Result<WebImage, WebImageError>
77    ///
78    /// # Errors
79    ///
80    /// Errors if there's an error while decoding the image from given data
81    pub async fn try_from_blob(blob: Blob) -> WebImageResult {
82        let bytes = blob_into_bytes(blob)
83            .await
84            .map_err(Into::<WebImageError>::into)?;
85
86        Self::try_from_byte_slice(&bytes)
87    }
88
89    /// Try to convert a WebImage to `ImageDate` that can be used with
90    /// `ImageData` web APIs
91    ///
92    /// returns: Result<ImageData, JsValue>
93    ///
94    /// # Errors
95    ///
96    /// Errors if the web API throws an exception
97    pub fn try_into_image_data(self) -> Result<ImageData, JsValue> {
98        ImageData::new_with_u8_clamped_array_and_sh(
99            Clamped(&self.raw_pixels),
100            self.width,
101            self.height,
102        )
103    }
104
105    /// Try to convert a WebImage to `ImageBitmap` that can be used with
106    /// canvas APIs
107    ///
108    /// returns: Result<ImageBitmap, JsValue>
109    ///
110    /// # Errors
111    ///
112    /// Errors if the web API throws an exception
113    pub async fn into_image_bitmap(self) -> Result<ImageBitmap, JsValue> {
114        let image_data = self.try_into_image_data()?;
115
116        let options = ImageBitmapOptions::new();
117        options.set_color_space_conversion(ColorSpaceConversion::Default);
118
119        let future: JsFuture = window()
120            .ok_or(WebImageError::DomError)
121            .map_err(Into::<JsValue>::into)?
122            .create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)?
123            .into();
124
125        let bitmap = future.await?;
126
127        Ok(bitmap.into())
128    }
129
130    #[must_use]
131    pub fn raw_pixels(self) -> Vec<u8> {
132        self.raw_pixels
133    }
134
135    #[must_use]
136    pub fn width(&self) -> u32 {
137        self.width
138    }
139
140    #[must_use]
141    pub fn height(&self) -> u32 {
142        self.height
143    }
144}