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 dimensions(&self, handle: &raster::Handle) -> Size<u32> {
22 if let Some(image) = self.cache.borrow_mut().allocate(handle) {
23 Size::new(image.width(), image.height())
24 } else {
25 Size::new(0, 0)
26 }
27 }
28
29 pub fn draw(
30 &mut self,
31 handle: &raster::Handle,
32 filter_method: raster::FilterMethod,
33 bounds: Rectangle,
34 opacity: f32,
35 pixels: &mut tiny_skia::PixmapMut<'_>,
36 transform: tiny_skia::Transform,
37 clip_mask: Option<&tiny_skia::Mask>,
38 border_radius: [f32; 4],
39 ) {
40 if let Some(mut image) = self.cache.borrow_mut().allocate(handle) {
41 let width_scale = bounds.width / image.width() as f32;
42 let height_scale = bounds.height / image.height() as f32;
43
44 let transform = transform.pre_scale(width_scale, height_scale);
45
46 let quality = match filter_method {
47 raster::FilterMethod::Linear => {
48 tiny_skia::FilterQuality::Bilinear
49 }
50 raster::FilterMethod::Nearest => {
51 tiny_skia::FilterQuality::Nearest
52 }
53 };
54 let mut scratch;
55
56 if border_radius.iter().any(|&corner| corner != 0.0) {
58 scratch = image.to_owned();
59 round(&mut scratch.as_mut(), {
60 let [a, b, c, d] = border_radius;
61 let scale_by = width_scale.min(height_scale);
62 let max_radius = image.width().min(image.height()) / 2;
63 [
64 ((a / scale_by) as u32).max(1).min(max_radius),
65 ((b / scale_by) as u32).max(1).min(max_radius),
66 ((c / scale_by) as u32).max(1).min(max_radius),
67 ((d / scale_by) as u32).max(1).min(max_radius),
68 ]
69 });
70 image = scratch.as_ref();
71 }
72
73 pixels.draw_pixmap(
74 (bounds.x / width_scale) as i32,
75 (bounds.y / height_scale) as i32,
76 image,
77 &tiny_skia::PixmapPaint {
78 quality,
79 opacity,
80 ..Default::default()
81 },
82 transform,
83 clip_mask,
84 );
85 }
86 }
87
88 pub fn trim_cache(&mut self) {
89 self.cache.borrow_mut().trim();
90 }
91}
92
93#[derive(Debug, Default)]
94struct Cache {
95 entries: FxHashMap<raster::Id, Option<Entry>>,
96 hits: FxHashSet<raster::Id>,
97}
98
99impl Cache {
100 pub fn allocate(
101 &mut self,
102 handle: &raster::Handle,
103 ) -> Option<tiny_skia::PixmapRef<'_>> {
104 let id = handle.id();
105
106 if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) {
107 let image = graphics::image::load(handle).ok()?;
108
109 let mut buffer =
110 vec![0u32; image.width() as usize * image.height() as usize];
111
112 for (i, pixel) in image.pixels().enumerate() {
113 let [r, g, b, a] = pixel.0;
114
115 buffer[i] = bytemuck::cast(
116 tiny_skia::ColorU8::from_rgba(b, g, r, a).premultiply(),
117 );
118 }
119
120 let _ = entry.insert(Some(Entry {
121 width: image.width(),
122 height: image.height(),
123 pixels: buffer,
124 }));
125 }
126
127 let _ = self.hits.insert(id);
128 self.entries.get(&id).unwrap().as_ref().map(|entry| {
129 tiny_skia::PixmapRef::from_bytes(
130 bytemuck::cast_slice(&entry.pixels),
131 entry.width,
132 entry.height,
133 )
134 .expect("Build pixmap from image bytes")
135 })
136 }
137
138 fn trim(&mut self) {
139 self.entries.retain(|key, _| self.hits.contains(key));
140 self.hits.clear();
141 }
142}
143
144#[derive(Debug)]
145struct Entry {
146 width: u32,
147 height: u32,
148 pixels: Vec<u32>,
149}
150
151fn round(img: &mut tiny_skia::PixmapMut<'_>, radius: [u32; 4]) {
153 let (width, height) = (img.width(), img.height());
154 assert!(radius[0] + radius[1] <= width);
155 assert!(radius[3] + radius[2] <= width);
156 assert!(radius[0] + radius[3] <= height);
157 assert!(radius[1] + radius[2] <= height);
158
159 border_radius(img, radius[0], |x, y| (x - 1, y - 1));
161 border_radius(img, radius[1], |x, y| (width - x, y - 1));
163 border_radius(img, radius[2], |x, y| (width - x, height - y));
165 border_radius(img, radius[3], |x, y| (x - 1, height - y));
167}
168
169fn border_radius(
170 img: &mut tiny_skia::PixmapMut<'_>,
171 r: u32,
172 coordinates: impl Fn(u32, u32) -> (u32, u32),
173) {
174 if r == 0 {
175 return;
176 }
177 let r0 = r;
178
179 let r = 16 * r;
181
182 let mut x = 0;
183 let mut y = r - 1;
184 let mut p: i32 = 2 - r as i32;
185
186 let mut alpha: u16 = 0;
189 let mut skip_draw = true;
190
191 fn pixel_id(width: u32, (x, y): (u32, u32)) -> usize {
192 ((width as usize * y as usize) + x as usize) * 4
193 }
194
195 let clear_pixel = |img: &mut tiny_skia::PixmapMut<'_>,
196 (x, y): (u32, u32)| {
197 let pixel = pixel_id(img.width(), (x, y));
198 img.data_mut()[pixel..pixel + 4].copy_from_slice(&[0; 4]);
199 };
200
201 let draw = |img: &mut tiny_skia::PixmapMut<'_>, alpha, x, y| {
202 debug_assert!((1..=256).contains(&alpha));
203 let pixel = pixel_id(img.width(), coordinates(r0 - x, r0 - y));
204 let pixel_alpha = &mut img.data_mut()[pixel + 3];
205 *pixel_alpha = ((alpha * *pixel_alpha as u16 + 128) / 256) as u8;
206 };
207
208 'l: loop {
209 {
212 let i = x / 16;
213 for j in y / 16 + 1..r0 {
214 clear_pixel(img, coordinates(r0 - i, r0 - j));
215 }
216 }
217 {
219 let j = x / 16;
220 for i in y / 16 + 1..r0 {
221 clear_pixel(img, coordinates(r0 - i, r0 - j));
222 }
223 }
224
225 if !skip_draw {
227 draw(img, alpha, x / 16 - 1, y / 16);
228 draw(img, alpha, y / 16, x / 16 - 1);
229 alpha = 0;
230 }
231
232 for _ in 0..16 {
233 skip_draw = false;
234
235 if x >= y {
236 break 'l;
237 }
238
239 alpha += y as u16 % 16 + 1;
240 if p < 0 {
241 x += 1;
242 p += (2 * x + 2) as i32;
243 } else {
244 if y % 16 == 0 {
246 draw(img, alpha, x / 16, y / 16);
247 draw(img, alpha, y / 16, x / 16);
248 skip_draw = true;
249 alpha = (x + 1) as u16 % 16 * 16;
250 }
251
252 x += 1;
253 p -= (2 * (y - x) + 2) as i32;
254 y -= 1;
255 }
256 }
257 }
258
259 if x / 16 == y / 16 {
261 if x == y {
263 alpha += y as u16 % 16 + 1;
264 }
265 let s = y as u16 % 16 + 1;
266 let alpha = 2 * alpha - s * s;
267 draw(img, alpha, x / 16, y / 16);
268 }
269
270 let range = y / 16 + 1..r0;
272 for i in range.clone() {
273 for j in range.clone() {
274 clear_pixel(img, coordinates(r0 - i, r0 - j));
275 }
276 }
277}