vg/
image.rs

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