Skip to main content

floem_skia_renderer/
lib.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use anyhow::{Result, anyhow};
5use anyrender::{ImageRenderer as _, WindowRenderer as _};
6use anyrender_skia::{SkiaImageRenderer, SkiaWindowRenderer};
7use floem_renderer::text::{Glyph, GlyphRunProps};
8use floem_renderer::{Img, Renderer, Svg};
9use peniko::kurbo::{Affine, BezPath, Point, Rect, Shape, Size, Stroke};
10use peniko::{
11    Blob, Brush, BrushRef, Compose, Fill, ImageAlphaType, ImageData, ImageFormat, Mix, Style,
12};
13use raw_window_handle::{
14    DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
15};
16use resvg::tiny_skia::{Pixmap, Transform};
17use winit::window::Window;
18
19const PATH_TOLERANCE: f64 = 0.1;
20
21enum Command {
22    Stroke {
23        shape: BezPath,
24        brush: Brush,
25        stroke: Stroke,
26        transform: Affine,
27    },
28    Fill {
29        shape: BezPath,
30        brush: Brush,
31        transform: Affine,
32    },
33    BoxShadow {
34        rect: Rect,
35        brush: peniko::Color,
36        radius: f64,
37        std_dev: f64,
38        transform: Affine,
39    },
40    PushLayer {
41        blend: peniko::BlendMode,
42        alpha: f32,
43        transform: Affine,
44        clip: BezPath,
45    },
46    PushClip {
47        transform: Affine,
48        clip: BezPath,
49    },
50    PopLayer,
51    Glyphs {
52        font: peniko::FontData,
53        font_size: f32,
54        hint: bool,
55        normalized_coords: Vec<i16>,
56        style: Style,
57        brush: Brush,
58        brush_alpha: f32,
59        transform: Affine,
60        glyph_transform: Option<Affine>,
61        glyphs: Vec<anyrender::Glyph>,
62    },
63    Image {
64        image: peniko::ImageBrush,
65        rect: Rect,
66        transform: Affine,
67    },
68    Svg {
69        image: peniko::ImageBrush,
70        rect: Rect,
71        transform: Affine,
72        brush: Option<Brush>,
73    },
74}
75
76pub struct SkiaRenderer {
77    window_renderer: SkiaWindowRenderer,
78    capture_renderer: SkiaImageRenderer,
79    commands: Vec<Command>,
80    svg_cache: HashMap<(Vec<u8>, u32, u32), peniko::ImageBrush>,
81    size: Size,
82    transform: Affine,
83    capture: bool,
84}
85
86#[derive(Clone)]
87struct AnyrenderWindow(Arc<dyn Window>);
88
89impl HasWindowHandle for AnyrenderWindow {
90    fn window_handle(&self) -> std::result::Result<WindowHandle<'_>, HandleError> {
91        self.0.window_handle()
92    }
93}
94
95impl HasDisplayHandle for AnyrenderWindow {
96    fn display_handle(&self) -> std::result::Result<DisplayHandle<'_>, HandleError> {
97        self.0.display_handle()
98    }
99}
100
101impl SkiaRenderer {
102    pub fn new(
103        window: Arc<dyn Window>,
104        width: u32,
105        height: u32,
106        scale: f64,
107        _font_embolden: f32,
108    ) -> Result<Self> {
109        let width = width.max(1);
110        let height = height.max(1);
111
112        let mut window_renderer = SkiaWindowRenderer::new();
113        let handle: Arc<dyn anyrender::WindowHandle> = Arc::new(AnyrenderWindow(window));
114        window_renderer.resume(handle, width, height);
115
116        let mut capture_renderer = SkiaImageRenderer::new(width, height);
117        capture_renderer.reset();
118
119        Ok(Self {
120            window_renderer,
121            capture_renderer,
122            commands: Vec::new(),
123            svg_cache: HashMap::new(),
124            size: Size::new(width as f64, height as f64),
125            transform: Affine::scale(scale),
126            capture: false,
127        })
128    }
129
130    pub fn resize(&mut self, width: u32, height: u32, _scale: f64) {
131        let width = width.max(1);
132        let height = height.max(1);
133        self.size = Size::new(width as f64, height as f64);
134        self.window_renderer.set_size(width, height);
135        self.capture_renderer.resize(width, height);
136    }
137
138    pub const fn set_scale(&mut self, _scale: f64) {}
139
140    pub const fn size(&self) -> Size {
141        self.size
142    }
143
144    fn current_scale(&self) -> (f64, f64) {
145        let coeffs = self.transform.as_coeffs();
146        let scale_x = coeffs[0].hypot(coeffs[1]);
147        let scale_y = coeffs[2].hypot(coeffs[3]);
148        (scale_x, scale_y)
149    }
150
151    fn replay<S: anyrender::PaintScene>(commands: &[Command], scene: &mut S) {
152        for command in commands {
153            match command {
154                Command::Stroke {
155                    shape,
156                    brush,
157                    stroke,
158                    transform,
159                } => {
160                    scene.stroke(
161                        stroke,
162                        *transform,
163                        anyrender::PaintRef::from(BrushRef::from(brush)),
164                        None,
165                        shape,
166                    );
167                }
168                Command::Fill {
169                    shape,
170                    brush,
171                    transform,
172                } => {
173                    scene.fill(
174                        Fill::NonZero,
175                        *transform,
176                        anyrender::PaintRef::from(BrushRef::from(brush)),
177                        None,
178                        shape,
179                    );
180                }
181                Command::BoxShadow {
182                    rect,
183                    brush,
184                    radius,
185                    std_dev,
186                    transform,
187                } => {
188                    scene.draw_box_shadow(*transform, *rect, *brush, *radius, *std_dev);
189                }
190                Command::PushLayer {
191                    blend,
192                    alpha,
193                    transform,
194                    clip,
195                } => {
196                    scene.push_layer(*blend, *alpha, *transform, clip);
197                }
198                Command::PushClip { transform, clip } => {
199                    scene.push_clip_layer(*transform, clip);
200                }
201                Command::PopLayer => {
202                    scene.pop_layer();
203                }
204                Command::Glyphs {
205                    font,
206                    font_size,
207                    hint,
208                    normalized_coords,
209                    style,
210                    brush,
211                    brush_alpha,
212                    transform,
213                    glyph_transform,
214                    glyphs,
215                } => {
216                    scene.draw_glyphs(
217                        font,
218                        *font_size,
219                        *hint,
220                        normalized_coords,
221                        style,
222                        anyrender::PaintRef::from(BrushRef::from(brush)),
223                        *brush_alpha,
224                        *transform,
225                        *glyph_transform,
226                        glyphs.iter().copied(),
227                    );
228                }
229                Command::Image {
230                    image,
231                    rect,
232                    transform,
233                } => {
234                    let image_transform = image_transform(*transform, *rect, image);
235                    scene.fill(
236                        Fill::NonZero,
237                        image_transform,
238                        image.as_ref(),
239                        None,
240                        &Rect::new(
241                            0.0,
242                            0.0,
243                            image.image.width as f64,
244                            image.image.height as f64,
245                        ),
246                    );
247                }
248                Command::Svg {
249                    image,
250                    rect,
251                    transform,
252                    brush,
253                } => {
254                    let image_transform = image_transform(*transform, *rect, image);
255                    let src_rect = Rect::new(
256                        0.0,
257                        0.0,
258                        image.image.width as f64,
259                        image.image.height as f64,
260                    );
261
262                    if let Some(brush) = brush {
263                        scene.push_layer(
264                            peniko::BlendMode::default(),
265                            1.0,
266                            image_transform,
267                            &src_rect,
268                        );
269                        scene.fill(
270                            Fill::NonZero,
271                            image_transform,
272                            image.as_ref(),
273                            None,
274                            &src_rect,
275                        );
276                        scene.push_layer(
277                            peniko::BlendMode {
278                                mix: Mix::Normal,
279                                compose: Compose::SrcIn,
280                            },
281                            1.0,
282                            image_transform,
283                            &src_rect,
284                        );
285                        scene.fill(
286                            Fill::NonZero,
287                            image_transform,
288                            anyrender::PaintRef::from(BrushRef::from(brush)),
289                            None,
290                            &src_rect,
291                        );
292                        scene.pop_layer();
293                        scene.pop_layer();
294                    } else {
295                        scene.fill(
296                            Fill::NonZero,
297                            image_transform,
298                            image.as_ref(),
299                            None,
300                            &src_rect,
301                        );
302                    }
303                }
304            }
305        }
306    }
307
308    fn cached_svg_image(
309        &mut self,
310        svg: Svg<'_>,
311        width: u32,
312        height: u32,
313    ) -> Result<peniko::ImageBrush> {
314        let key = (svg.hash.to_vec(), width, height);
315        if let Some(image) = self.svg_cache.get(&key) {
316            return Ok(image.clone());
317        }
318
319        let mut pixmap =
320            Pixmap::new(width, height).ok_or_else(|| anyhow!("failed to allocate svg pixmap"))?;
321        let transform = Transform::from_scale(
322            width as f32 / svg.tree.size().width(),
323            height as f32 / svg.tree.size().height(),
324        );
325        resvg::render(svg.tree, transform, &mut pixmap.as_mut());
326        let image = image_brush_from_rgba(width, height, pixmap.take());
327        self.svg_cache.insert(key, image.clone());
328        Ok(image)
329    }
330}
331
332impl Renderer for SkiaRenderer {
333    fn begin(&mut self, capture: bool) {
334        self.commands.clear();
335        self.transform = Affine::IDENTITY;
336        self.capture = capture;
337    }
338
339    fn set_transform(&mut self, transform: Affine) {
340        self.transform = transform;
341    }
342
343    fn set_z_index(&mut self, _z_index: i32) {}
344
345    fn clip(&mut self, shape: &impl Shape) {
346        self.commands.push(Command::PushClip {
347            transform: self.transform,
348            clip: shape.to_path(PATH_TOLERANCE),
349        });
350    }
351
352    fn clear_clip(&mut self) {
353        self.commands.push(Command::PopLayer);
354    }
355
356    fn stroke<'b, 's>(
357        &mut self,
358        shape: &impl Shape,
359        brush: impl Into<BrushRef<'b>>,
360        stroke: &'s Stroke,
361    ) {
362        self.commands.push(Command::Stroke {
363            shape: shape.to_path(PATH_TOLERANCE),
364            brush: brush.into().to_owned(),
365            stroke: stroke.clone(),
366            transform: self.transform,
367        });
368    }
369
370    fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into<BrushRef<'b>>, blur_radius: f64) {
371        let brush = brush.into();
372
373        if blur_radius > 0.0
374            && let BrushRef::Solid(color) = brush
375        {
376            if let Some(rounded) = path.as_rounded_rect() {
377                let radii = rounded.radii();
378                if radii.top_left == radii.top_right
379                    && radii.top_left == radii.bottom_left
380                    && radii.top_left == radii.bottom_right
381                {
382                    self.commands.push(Command::BoxShadow {
383                        rect: rounded.rect(),
384                        brush: color,
385                        radius: radii.top_left,
386                        std_dev: blur_radius,
387                        transform: self.transform,
388                    });
389                    return;
390                }
391            } else if let Some(rect) = path.as_rect() {
392                self.commands.push(Command::BoxShadow {
393                    rect,
394                    brush: color,
395                    radius: 0.0,
396                    std_dev: blur_radius,
397                    transform: self.transform,
398                });
399                return;
400            }
401        }
402
403        self.commands.push(Command::Fill {
404            shape: path.to_path(PATH_TOLERANCE),
405            brush: brush.to_owned(),
406            transform: self.transform,
407        });
408    }
409
410    fn push_layer(
411        &mut self,
412        blend: impl Into<peniko::BlendMode>,
413        alpha: f32,
414        transform: Affine,
415        clip: &impl Shape,
416    ) {
417        self.commands.push(Command::PushLayer {
418            blend: blend.into(),
419            alpha,
420            transform: self.transform * transform,
421            clip: clip.to_path(PATH_TOLERANCE),
422        });
423    }
424
425    fn pop_layer(&mut self) {
426        self.commands.push(Command::PopLayer);
427    }
428
429    fn draw_glyphs<'a>(
430        &mut self,
431        origin: Point,
432        props: &GlyphRunProps<'a>,
433        glyphs: impl Iterator<Item = Glyph> + 'a,
434    ) {
435        let transform = self.transform * Affine::translate((origin.x, origin.y)) * props.transform;
436        self.commands.push(Command::Glyphs {
437            font: props.font.clone(),
438            font_size: props.font_size,
439            hint: props.hint,
440            normalized_coords: props.normalized_coords.to_vec(),
441            style: props.style.to_owned(),
442            brush: props.brush.to_owned(),
443            brush_alpha: props.brush_alpha,
444            transform,
445            glyph_transform: props.glyph_transform,
446            glyphs: glyphs
447                .map(|glyph| anyrender::Glyph {
448                    id: glyph.id,
449                    x: glyph.x,
450                    y: glyph.y,
451                })
452                .collect(),
453        });
454    }
455
456    fn draw_svg<'b>(&mut self, svg: Svg<'b>, rect: Rect, brush: Option<impl Into<BrushRef<'b>>>) {
457        let (scale_x, scale_y) = self.current_scale();
458        let width = (rect.width() * scale_x.abs()).round().max(1.0) as u32;
459        let height = (rect.height() * scale_y.abs()).round().max(1.0) as u32;
460        let image = match self.cached_svg_image(svg, width, height) {
461            Ok(image) => image,
462            Err(_) => return,
463        };
464
465        self.commands.push(Command::Svg {
466            image,
467            rect,
468            transform: self.transform,
469            brush: brush.map(|brush| brush.into().to_owned()),
470        });
471    }
472
473    fn draw_img(&mut self, img: Img<'_>, rect: Rect) {
474        self.commands.push(Command::Image {
475            image: img.img,
476            rect,
477            transform: self.transform,
478        });
479    }
480
481    fn finish(&mut self) -> Option<peniko::ImageBrush> {
482        if self.capture {
483            let commands = std::mem::take(&mut self.commands);
484            let mut buffer = vec![0; self.size.width as usize * self.size.height as usize * 4];
485            self.capture_renderer.reset();
486            self.capture_renderer
487                .render(|scene| Self::replay(&commands, scene), &mut buffer);
488            return Some(image_brush_from_rgba(
489                self.size.width as u32,
490                self.size.height as u32,
491                buffer,
492            ));
493        }
494
495        let commands = std::mem::take(&mut self.commands);
496        self.window_renderer
497            .render(|scene| Self::replay(&commands, scene));
498        None
499    }
500
501    fn debug_info(&self) -> String {
502        "name: Skia\ninfo: AnyRender Skia".to_string()
503    }
504}
505
506fn image_transform(transform: Affine, rect: Rect, image: &peniko::ImageBrush) -> Affine {
507    transform
508        .pre_scale_non_uniform(
509            rect.width().max(1.0) / image.image.width as f64,
510            rect.height().max(1.0) / image.image.height as f64,
511        )
512        .pre_translate((rect.min_x(), rect.min_y()).into())
513}
514
515fn image_brush_from_rgba(width: u32, height: u32, data: Vec<u8>) -> peniko::ImageBrush {
516    peniko::ImageBrush::new(ImageData {
517        data: Blob::new(Arc::new(data)),
518        format: ImageFormat::Rgba8,
519        alpha_type: ImageAlphaType::AlphaPremultiplied,
520        width,
521        height,
522    })
523}