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}