pixel_widgets/
cache.rs

1use std::mem;
2use std::sync::{Arc, Weak};
3
4use anyhow::*;
5use image::{Rgba, RgbaImage};
6use rusttype::{point, vector};
7use smallvec::SmallVec;
8
9use crate::atlas::*;
10use crate::draw::*;
11use crate::layout::Rectangle;
12use crate::text::Text;
13
14type GlyphCache = rusttype::gpu_cache::Cache<'static>;
15pub(crate) type Font = rusttype::Font<'static>;
16pub(crate) type FontId = usize;
17
18/// A cache for textures and text
19pub struct Cache {
20    #[allow(unused)]
21    size: usize,
22    glyphs: GlyphCache,
23    textures: Vec<TextureSlot>,
24    updates: Vec<Update>,
25    font_id_counter: usize,
26    image_id_counter: usize,
27}
28
29enum TextureSlot {
30    Atlas(Atlas<Weak<usize>>),
31    Big,
32}
33
34impl Cache {
35    /// Create a new cache. Size is the width and height of textures in pixels.
36    /// Offset is the offset to apply to texture ids
37    pub fn new(size: usize) -> Cache {
38        let glyphs = GlyphCache::builder().dimensions(size as u32, size as u32).build();
39
40        let atlas = Atlas::new(size);
41
42        Cache {
43            size,
44            glyphs,
45            textures: vec![
46                // glyph cache
47                TextureSlot::Big,
48                // atlas for textures
49                TextureSlot::Atlas(atlas),
50            ],
51            updates: vec![
52                // glyph cache
53                Update::Texture {
54                    id: 0,
55                    size: [size as u32, size as u32],
56                    data: Vec::new(),
57                    atlas: true,
58                },
59                // atlas for textures
60                Update::Texture {
61                    id: 1,
62                    size: [size as u32, size as u32],
63                    data: Vec::new(),
64                    atlas: true,
65                },
66            ],
67            font_id_counter: 0,
68            image_id_counter: 1,
69        }
70    }
71
72    /// Take updates for the texture system from the cache
73    pub fn take_updates(&mut self) -> Vec<Update> {
74        mem::take(&mut self.updates)
75    }
76
77    pub(crate) fn draw_text<F: FnMut(Rectangle, Rectangle)>(
78        &mut self,
79        text: &Text,
80        rect: Rectangle,
81        mut place_glyph: F,
82    ) {
83        let start = point(rect.left, rect.top);
84
85        let mut placed_glyphs = Vec::with_capacity(text.text.len());
86        text.layout(rect, |g, x, _, y| {
87            placed_glyphs.push(g.positioned(start + vector(x, y)));
88        });
89
90        for g in placed_glyphs.iter() {
91            self.glyphs.queue_glyph(text.font.id as usize, g.clone());
92        }
93
94        let updates = &mut self.updates;
95        self.glyphs
96            .cache_queued(|rect, data| {
97                let mut new_data = Vec::with_capacity(data.len() * 4);
98                for x in data {
99                    new_data.push(255);
100                    new_data.push(255);
101                    new_data.push(255);
102                    new_data.push(*x);
103                }
104
105                let update = Update::TextureSubresource {
106                    id: 0,
107                    offset: [rect.min.x, rect.min.y],
108                    size: [rect.width(), rect.height()],
109                    data: new_data,
110                };
111
112                updates.push(update);
113            })
114            .unwrap();
115
116        for g in placed_glyphs.iter() {
117            if let Some((uv, pos)) = self.glyphs.rect_for(text.font.id as usize, g).unwrap() {
118                place_glyph(
119                    Rectangle {
120                        left: uv.min.x,
121                        top: uv.min.y,
122                        right: uv.max.x,
123                        bottom: uv.max.y,
124                    },
125                    Rectangle {
126                        left: pos.min.x as f32,
127                        top: pos.min.y as f32,
128                        right: pos.max.x as f32,
129                        bottom: pos.max.y as f32,
130                    },
131                );
132            }
133        }
134    }
135
136    pub(crate) fn load_image(&mut self, image: RgbaImage) -> ImageData {
137        let size = Rectangle {
138            left: 0.0,
139            top: 0.0,
140            right: image.width() as f32,
141            bottom: image.height() as f32,
142        };
143        let (texture, cache_id, texcoords) = self.insert_image(image);
144        ImageData {
145            texture,
146            cache_id,
147            texcoords,
148            size,
149        }
150    }
151
152    pub(crate) fn load_patch(&mut self, mut image: RgbaImage) -> Patch {
153        // find 9 patch borders in image data
154        let black = Rgba([0u8, 0u8, 0u8, 255u8]);
155
156        let mut h_stretch = SmallVec::<[(f32, f32); 2]>::new();
157        let mut h_content = (1.0, 0.0);
158        let mut v_stretch = SmallVec::<[(f32, f32); 2]>::new();
159        let mut v_content = (1.0, 0.0);
160        let mut h_current_stretch = None;
161        let mut v_current_stretch = None;
162
163        // scan horizontal stretch and content bars
164        for x in 1..image.width() - 1 {
165            let h_begin = (x - 1) as f32 / (image.width() - 2) as f32;
166            let h_end = (x) as f32 / (image.width() - 2) as f32;
167
168            // check stretch pixel
169            if image[(x, 0)] == black {
170                h_current_stretch = Some(h_current_stretch.map_or_else(|| (h_begin, h_end), |(s, _)| (s, h_end)));
171            } else if let Some(s) = h_current_stretch.take() {
172                h_stretch.push(s);
173            }
174
175            // check content pixel
176            if image[(x, image.height() - 1)] == black {
177                h_content.0 = h_begin.min(h_content.0);
178                h_content.1 = h_end.max(h_content.1);
179            }
180        }
181
182        // scan vertical stretch and content bars
183        for y in 1..image.height() - 1 {
184            let v_begin = (y - 1) as f32 / (image.height() - 2) as f32;
185            let v_end = (y) as f32 / (image.height() - 2) as f32;
186
187            // check stretch pixel
188            if image[(0, y)] == black {
189                v_current_stretch = Some(v_current_stretch.map_or_else(|| (v_begin, v_end), |(s, _)| (s, v_end)));
190            } else if let Some(s) = v_current_stretch.take() {
191                v_stretch.push(s);
192            }
193
194            // check content pixel
195            if image[(image.width() - 1, y)] == black {
196                v_content.0 = v_begin.min(v_content.0);
197                v_content.1 = v_end.max(v_content.1);
198            }
199        }
200
201        if let Some(s) = h_current_stretch.take() {
202            h_stretch.push(s);
203        }
204        if let Some(s) = v_current_stretch.take() {
205            v_stretch.push(s);
206        }
207
208        // strip stretch and content bars from the image
209        let patch_width = image.width() - 2;
210        let patch_height = image.height() - 2;
211        let image = image::imageops::crop(&mut image, 1, 1, patch_width, patch_height).to_image();
212        let size = Rectangle {
213            left: 0.0,
214            top: 0.0,
215            right: image.width() as f32,
216            bottom: image.height() as f32,
217        };
218        let (texture, cache_id, texcoords) = self.insert_image(image);
219
220        Patch {
221            image: ImageData {
222                texture,
223                cache_id,
224                texcoords,
225                size,
226            },
227            h_stretch,
228            v_stretch,
229            h_content,
230            v_content,
231        }
232    }
233
234    pub(crate) fn load_font<D: Into<Vec<u8>>>(&mut self, data: D) -> Result<crate::text::Font> {
235        let inner = Font::try_from_vec(data.into()).ok_or_else(|| anyhow!("Invalid .ttf data"))?;
236
237        let id = self.font_id_counter;
238        self.font_id_counter += 1;
239
240        Ok(crate::text::Font { inner, id, tex_slot: 0 })
241    }
242
243    fn insert_image(&mut self, image: image::RgbaImage) -> (usize, Arc<usize>, Rectangle) {
244        for slot in self.textures.iter_mut() {
245            if let TextureSlot::Atlas(atlas) = slot {
246                atlas.remove_expired();
247            }
248        }
249
250        let image_id = Arc::new(self.image_id_counter);
251        self.image_id_counter += 1;
252
253        let slot = self
254            .textures
255            .iter_mut()
256            .enumerate()
257            .filter_map(|(index, slot)| match slot {
258                TextureSlot::Atlas(atlas) => {
259                    let image_size = image.width().max(image.height()) as usize;
260                    atlas
261                        .insert(Arc::downgrade(&image_id), image_size)
262                        .ok()
263                        .map(|area| (area, atlas.size() as f32, index))
264                }
265                TextureSlot::Big => None,
266            })
267            .next();
268
269        if let Some((mut area, atlas_size, tex_id)) = slot {
270            area.right = area.left + image.width() as usize;
271            area.bottom = area.top + image.height() as usize;
272
273            let update = Update::TextureSubresource {
274                id: tex_id,
275                offset: [area.left as u32, area.top as u32],
276                size: [image.width(), image.height()],
277                data: image.to_vec(),
278            };
279            self.updates.push(update);
280
281            (
282                tex_id,
283                image_id,
284                Rectangle {
285                    left: area.left as f32 / atlas_size,
286                    top: area.top as f32 / atlas_size,
287                    right: area.right as f32 / atlas_size,
288                    bottom: area.bottom as f32 / atlas_size,
289                },
290            )
291        } else {
292            let tex_id = self.textures.len();
293
294            let update = Update::Texture {
295                id: tex_id,
296                size: [image.width(), image.height()],
297                data: image.to_vec(),
298                atlas: false,
299            };
300
301            self.updates.push(update);
302            self.textures.push(TextureSlot::Big);
303
304            (tex_id, image_id, Rectangle::from_wh(1.0, 1.0))
305        }
306    }
307}