femtovg/
image.rs

1use bitflags::bitflags;
2use imgref::*;
3use rgb::alt::Gray;
4use rgb::*;
5use slotmap::{DefaultKey, SlotMap};
6
7#[cfg(feature = "image-loading")]
8use ::image::DynamicImage;
9
10#[cfg(feature = "image-loading")]
11use std::convert::TryFrom;
12
13use crate::{ErrorKind, Renderer};
14
15/// An image handle.
16#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
17pub struct ImageId(DefaultKey);
18
19/// Specifies the format of an image's pixels.
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
21pub enum PixelFormat {
22    /// 24-bit RGB image format (8 bits per channel)
23    Rgb8,
24    /// 32-bit RGBA image format (8 bits per channel, including alpha)
25    Rgba8,
26    /// 8-bit grayscale image format
27    Gray8,
28}
29
30bitflags! {
31    /// Represents a set of flags that modify the behavior of an image.
32    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33    pub struct ImageFlags: u32 {
34        /// Generates mipmaps during the creation of the image.
35        const GENERATE_MIPMAPS = 1;
36        /// Repeats the image in the X direction when rendered.
37        const REPEAT_X = 1 << 1;
38        /// Repeats the image in the Y direction when rendered.
39        const REPEAT_Y = 1 << 2;
40        /// Flips (inverses) the image in the Y direction when rendered.
41        const FLIP_Y = 1 << 3;
42        /// Indicates that the image data has premultiplied alpha.
43        const PREMULTIPLIED = 1 << 4;
44        /// Uses nearest-neighbor interpolation instead of linear interpolation when rendering the image.
45        const NEAREST = 1 << 5;
46    }
47}
48
49/// Represents the source of an image.
50#[derive(Copy, Clone, Debug)]
51#[non_exhaustive]
52pub enum ImageSource<'a> {
53    /// Image source with RGB image format (8 bits per channel)
54    Rgb(ImgRef<'a, RGB8>),
55    /// Image source with RGBA image format (8 bits per channel, including alpha)
56    Rgba(ImgRef<'a, RGBA8>),
57    /// Image source with 8-bit grayscale image format
58    Gray(ImgRef<'a, Gray<u8>>),
59    /// Image source referencing a HTML image element (only available on `wasm32` target)
60    #[cfg(target_arch = "wasm32")]
61    HtmlImageElement(&'a web_sys::HtmlImageElement),
62}
63
64impl ImageSource<'_> {
65    /// Returns the format of the image source.
66    pub fn format(&self) -> PixelFormat {
67        match self {
68            Self::Rgb(_) => PixelFormat::Rgb8,
69            Self::Rgba(_) => PixelFormat::Rgba8,
70            Self::Gray(_) => PixelFormat::Gray8,
71            #[cfg(target_arch = "wasm32")]
72            Self::HtmlImageElement(_) => PixelFormat::Rgba8,
73        }
74    }
75
76    /// Returns the dimensions (width and height) of the image source.
77    pub fn dimensions(&self) -> Size {
78        match self {
79            Self::Rgb(imgref) => Size::new(imgref.width(), imgref.height()),
80            Self::Rgba(imgref) => Size::new(imgref.width(), imgref.height()),
81            Self::Gray(imgref) => Size::new(imgref.width(), imgref.height()),
82            #[cfg(target_arch = "wasm32")]
83            Self::HtmlImageElement(element) => Size::new(element.width() as usize, element.height() as usize),
84        }
85    }
86}
87
88impl<'a> From<ImgRef<'a, RGB8>> for ImageSource<'a> {
89    fn from(src: ImgRef<'a, RGB8>) -> Self {
90        Self::Rgb(src)
91    }
92}
93
94impl<'a> From<ImgRef<'a, RGBA8>> for ImageSource<'a> {
95    fn from(src: ImgRef<'a, RGBA8>) -> Self {
96        Self::Rgba(src)
97    }
98}
99
100impl<'a> From<ImgRef<'a, Gray<u8>>> for ImageSource<'a> {
101    fn from(src: ImgRef<'a, Gray<u8>>) -> Self {
102        Self::Gray(src)
103    }
104}
105
106#[cfg(target_arch = "wasm32")]
107impl<'a> From<&'a web_sys::HtmlImageElement> for ImageSource<'a> {
108    fn from(src: &'a web_sys::HtmlImageElement) -> Self {
109        Self::HtmlImageElement(src)
110    }
111}
112
113#[cfg(feature = "image-loading")]
114impl<'a> TryFrom<&'a DynamicImage> for ImageSource<'a> {
115    type Error = ErrorKind;
116
117    fn try_from(src: &'a DynamicImage) -> Result<Self, ErrorKind> {
118        Ok(match src {
119            ::image::DynamicImage::ImageLuma8(img) => {
120                let src: Img<&[Gray<u8>]> = Img::new(img.as_pixels(), img.width() as usize, img.height() as usize);
121                ImageSource::from(src)
122            }
123            ::image::DynamicImage::ImageRgb8(img) => {
124                let src = Img::new(img.as_rgb(), img.width() as usize, img.height() as usize);
125                ImageSource::from(src)
126            }
127            ::image::DynamicImage::ImageRgba8(img) => {
128                let src = Img::new(img.as_rgba(), img.width() as usize, img.height() as usize);
129                ImageSource::from(src)
130            }
131            // TODO: if format is not supported maybe we should convert it here,
132            // But that is an expensive operation on the render thread that will remain hidden from the user
133            _ => return Err(ErrorKind::UnsupportedImageFormat),
134        })
135    }
136}
137
138#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
139pub struct Size {
140    pub width: usize,
141    pub height: usize,
142}
143
144impl Size {
145    pub fn new(width: usize, height: usize) -> Self {
146        Self { width, height }
147    }
148}
149
150/// Information about an image.
151#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
152pub struct ImageInfo {
153    flags: ImageFlags,
154    size: Size,
155    format: PixelFormat,
156}
157
158impl ImageInfo {
159    /// Creates a new `ImageInfo` with the specified flags, width, height, and format.
160    pub fn new(flags: ImageFlags, width: usize, height: usize, format: PixelFormat) -> Self {
161        Self {
162            flags,
163            size: Size { width, height },
164            format,
165        }
166    }
167
168    /// Returns the image flags.
169    pub fn flags(&self) -> ImageFlags {
170        self.flags
171    }
172
173    /// Returns the image width in pixels.
174    pub fn width(&self) -> usize {
175        self.size.width
176    }
177
178    /// Returns the image height in pixels.
179    pub fn height(&self) -> usize {
180        self.size.height
181    }
182
183    /// Returns the image size (width and height) in pixels.
184    pub fn size(&self) -> Size {
185        self.size
186    }
187
188    /// Returns the image format.
189    pub fn format(&self) -> PixelFormat {
190        self.format
191    }
192
193    /// Sets the image format.
194    pub fn set_format(&mut self, format: PixelFormat) {
195        self.format = format;
196    }
197}
198
199pub struct ImageStore<T>(SlotMap<DefaultKey, (ImageInfo, T)>);
200
201impl<T> Default for ImageStore<T> {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207impl<T> ImageStore<T> {
208    pub fn new() -> Self {
209        Self(SlotMap::new())
210    }
211
212    pub fn alloc<R: Renderer<Image = T>>(&mut self, renderer: &mut R, info: ImageInfo) -> Result<ImageId, ErrorKind> {
213        let image = renderer.alloc_image(info)?;
214        Ok(ImageId(self.0.insert((info, image))))
215    }
216
217    pub fn register_native_texture<R: Renderer<Image = T>>(
218        &mut self,
219        renderer: &mut R,
220        texture: R::NativeTexture,
221        info: ImageInfo,
222    ) -> Result<ImageId, ErrorKind> {
223        let image = renderer.create_image_from_native_texture(texture, info)?;
224        Ok(ImageId(self.0.insert((info, image))))
225    }
226
227    // Reallocates the image without changing the id.
228    pub fn realloc<R: Renderer<Image = T>>(
229        &mut self,
230        renderer: &mut R,
231        id: ImageId,
232        info: ImageInfo,
233    ) -> Result<(), ErrorKind> {
234        if let Some(old) = self.0.get_mut(id.0) {
235            let new = renderer.alloc_image(info)?;
236            old.0 = info;
237            old.1 = new;
238            Ok(())
239        } else {
240            Err(ErrorKind::ImageIdNotFound)
241        }
242    }
243
244    pub fn get(&self, id: ImageId) -> Option<&T> {
245        self.0.get(id.0).map(|inner| &inner.1)
246    }
247
248    pub fn get_mut(&mut self, id: ImageId) -> Option<&mut T> {
249        self.0.get_mut(id.0).map(|inner| &mut inner.1)
250    }
251
252    pub fn update<R: Renderer<Image = T>>(
253        &mut self,
254        renderer: &mut R,
255        id: ImageId,
256        data: ImageSource,
257        x: usize,
258        y: usize,
259    ) -> Result<(), ErrorKind> {
260        if let Some(image) = self.0.get_mut(id.0) {
261            renderer.update_image(&mut image.1, data, x, y)?;
262            Ok(())
263        } else {
264            Err(ErrorKind::ImageIdNotFound)
265        }
266    }
267
268    pub fn info(&self, id: ImageId) -> Option<ImageInfo> {
269        self.0.get(id.0).map(|inner| inner.0)
270    }
271
272    pub fn remove<R: Renderer<Image = T>>(&mut self, renderer: &mut R, id: ImageId) {
273        if let Some(image) = self.0.remove(id.0) {
274            renderer.delete_image(image.1, id);
275        }
276    }
277
278    pub fn clear<R: Renderer<Image = T>>(&mut self, renderer: &mut R) {
279        for (idx, image) in self.0.drain() {
280            renderer.delete_image(image.1, ImageId(idx));
281        }
282    }
283}
284
285/// Specifies the type of filter to apply to images with `crate::Canvas::filter_image`.
286#[derive(Clone, Copy, Debug)]
287#[non_exhaustive]
288pub enum ImageFilter {
289    /// Applies a Gaussian blur filter with the specified standard deviation.
290    GaussianBlur {
291        /// The standard deviation of the Gaussian blur filter.
292        sigma: f32,
293    },
294}