Skip to main content

gpui/
assets.rs

1use crate::{DevicePixels, Pixels, Result, SharedString, Size, size};
2use smallvec::SmallVec;
3
4use image::{Delay, Frame};
5use std::{
6    borrow::Cow,
7    fmt,
8    hash::Hash,
9    sync::atomic::{AtomicUsize, Ordering::SeqCst},
10};
11
12/// A source of assets for this app to use.
13pub trait AssetSource: 'static + Send + Sync {
14    /// Load the given asset from the source path.
15    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>>;
16
17    /// List the assets at the given path.
18    fn list(&self, path: &str) -> Result<Vec<SharedString>>;
19}
20
21impl AssetSource for () {
22    fn load(&self, _path: &str) -> Result<Option<Cow<'static, [u8]>>> {
23        Ok(None)
24    }
25
26    fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
27        Ok(vec![])
28    }
29}
30
31/// A unique identifier for the image cache
32#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
33pub struct ImageId(pub usize);
34
35#[derive(PartialEq, Eq, Hash, Clone)]
36#[expect(missing_docs)]
37pub struct RenderImageParams {
38    pub image_id: ImageId,
39    pub frame_index: usize,
40}
41
42/// A cached and processed image, in BGRA format
43pub struct RenderImage {
44    /// The ID associated with this image
45    pub id: ImageId,
46    /// The scale factor of this image on render.
47    pub(crate) scale_factor: f32,
48    data: SmallVec<[Frame; 1]>,
49}
50
51impl PartialEq for RenderImage {
52    fn eq(&self, other: &Self) -> bool {
53        self.id == other.id
54    }
55}
56
57impl Eq for RenderImage {}
58
59impl RenderImage {
60    /// Create a new image from the given data.
61    pub fn new(data: impl Into<SmallVec<[Frame; 1]>>) -> Self {
62        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
63
64        Self {
65            id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
66            scale_factor: 1.0,
67            data: data.into(),
68        }
69    }
70
71    /// Convert this image into a byte slice.
72    pub fn as_bytes(&self, frame_index: usize) -> Option<&[u8]> {
73        self.data
74            .get(frame_index)
75            .map(|frame| frame.buffer().as_raw().as_slice())
76    }
77
78    /// Get the size of this image, in pixels.
79    pub fn size(&self, frame_index: usize) -> Size<DevicePixels> {
80        self.data
81            .get(frame_index)
82            .map(|frame| {
83                let (width, height) = frame.buffer().dimensions();
84                size(width.into(), height.into())
85            })
86            .unwrap_or_default()
87    }
88
89    /// Get the size of this image, in pixels for display, adjusted for the scale factor.
90    pub(crate) fn render_size(&self, frame_index: usize) -> Size<Pixels> {
91        self.size(frame_index)
92            .map(|v| (v.0 as f32 / self.scale_factor).into())
93    }
94
95    /// Get the delay of this frame from the previous
96    pub fn delay(&self, frame_index: usize) -> Delay {
97        self.data
98            .get(frame_index)
99            .map(|frame| frame.delay())
100            .unwrap_or(Delay::from_numer_denom_ms(100, 1))
101    }
102
103    /// Get the number of frames for this image.
104    pub fn frame_count(&self) -> usize {
105        self.data.len()
106    }
107}
108
109impl fmt::Debug for RenderImage {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct("ImageData")
112            .field("id", &self.id)
113            .field("size", &self.data.first().map(|f| f.buffer().dimensions()))
114            .finish()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use smallvec::SmallVec;
122
123    #[test]
124    fn empty_render_image_does_not_panic() {
125        let image = RenderImage::new(SmallVec::new());
126        assert_eq!(image.frame_count(), 0);
127        assert_eq!(image.size(0), Size::default());
128        assert_eq!(image.as_bytes(0), None);
129        assert_eq!(image.render_size(0), Size::default());
130        assert_eq!(image.delay(0), Delay::from_numer_denom_ms(100, 1));
131        let _ = format!("{image:?}");
132    }
133}