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 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 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 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 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}