iced_tiny_skia/
raster.rs

1use crate::core::image as raster;
2use crate::core::{Rectangle, Size};
3use crate::graphics;
4
5use rustc_hash::{FxHashMap, FxHashSet};
6use std::cell::RefCell;
7use std::collections::hash_map;
8
9#[derive(Debug)]
10pub struct Pipeline {
11    cache: RefCell<Cache>,
12}
13
14impl Pipeline {
15    pub fn new() -> Self {
16        Self {
17            cache: RefCell::new(Cache::default()),
18        }
19    }
20
21    pub fn load(&self, handle: &raster::Handle) -> Result<raster::Allocation, raster::Error> {
22        let mut cache = self.cache.borrow_mut();
23        let image = cache.allocate(handle)?;
24
25        #[allow(unsafe_code)]
26        Ok(unsafe { raster::allocate(handle, Size::new(image.width(), image.height())) })
27    }
28
29    pub fn dimensions(&self, handle: &raster::Handle) -> Option<Size<u32>> {
30        let mut cache = self.cache.borrow_mut();
31        let image = cache.allocate(handle).ok()?;
32
33        Some(Size::new(image.width(), image.height()))
34    }
35
36    pub fn draw(
37        &mut self,
38        handle: &raster::Handle,
39        filter_method: raster::FilterMethod,
40        bounds: Rectangle,
41        opacity: f32,
42        pixels: &mut tiny_skia::PixmapMut<'_>,
43        transform: tiny_skia::Transform,
44        clip_mask: Option<&tiny_skia::Mask>,
45    ) {
46        let mut cache = self.cache.borrow_mut();
47
48        let Ok(image) = cache.allocate(handle) else {
49            return;
50        };
51
52        let width_scale = bounds.width / image.width() as f32;
53        let height_scale = bounds.height / image.height() as f32;
54
55        let transform = transform.pre_scale(width_scale, height_scale);
56
57        let quality = match filter_method {
58            raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear,
59            raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest,
60        };
61
62        pixels.draw_pixmap(
63            (bounds.x / width_scale) as i32,
64            (bounds.y / height_scale) as i32,
65            image,
66            &tiny_skia::PixmapPaint {
67                quality,
68                opacity,
69                ..Default::default()
70            },
71            transform,
72            clip_mask,
73        );
74    }
75
76    pub fn trim_cache(&mut self) {
77        self.cache.borrow_mut().trim();
78    }
79}
80
81#[derive(Debug, Default)]
82struct Cache {
83    entries: FxHashMap<raster::Id, Option<Entry>>,
84    hits: FxHashSet<raster::Id>,
85}
86
87impl Cache {
88    pub fn allocate(
89        &mut self,
90        handle: &raster::Handle,
91    ) -> Result<tiny_skia::PixmapRef<'_>, raster::Error> {
92        let id = handle.id();
93
94        if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) {
95            let image = match graphics::image::load(handle) {
96                Ok(image) => image,
97                Err(error) => {
98                    let _ = entry.insert(None);
99
100                    return Err(error);
101                }
102            };
103
104            if image.width() == 0 || image.height() == 0 {
105                return Err(raster::Error::Empty);
106            }
107
108            let mut buffer = vec![0u32; image.width() as usize * image.height() as usize];
109
110            for (i, pixel) in image.pixels().enumerate() {
111                let [r, g, b, a] = pixel.0;
112
113                buffer[i] = bytemuck::cast(tiny_skia::ColorU8::from_rgba(b, g, r, a).premultiply());
114            }
115
116            let _ = entry.insert(Some(Entry {
117                width: image.width(),
118                height: image.height(),
119                pixels: buffer,
120            }));
121        }
122
123        let _ = self.hits.insert(id);
124
125        Ok(self
126            .entries
127            .get(&id)
128            .unwrap()
129            .as_ref()
130            .map(|entry| {
131                tiny_skia::PixmapRef::from_bytes(
132                    bytemuck::cast_slice(&entry.pixels),
133                    entry.width,
134                    entry.height,
135                )
136                .expect("Build pixmap from image bytes")
137            })
138            .expect("Image should be allocated"))
139    }
140
141    fn trim(&mut self) {
142        self.entries.retain(|key, _| self.hits.contains(key));
143        self.hits.clear();
144    }
145}
146
147#[derive(Debug)]
148struct Entry {
149    width: u32,
150    height: u32,
151    pixels: Vec<u32>,
152}