piet_tiny_skia/
lib.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
2// This file is a part of `piet-tiny-skia`.
3//
4// `piet-tiny-skia` is free software: you can redistribute it and/or modify it under the terms of
5// either:
6//
7// * GNU Lesser General Public License as published by the Free Software Foundation, either
8// version 3 of the License, or (at your option) any later version.
9// * Mozilla Public License as published by the Mozilla Foundation, version 2.
10//
11// `piet-tiny-skia` is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13// See the GNU Lesser General Public License or the Mozilla Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public License and the Mozilla
16// Public License along with `piet-tiny-skia`. If not, see <https://www.gnu.org/licenses/>.
17
18//! A [`piet`] frontend for the [`tiny-skia`] framework.
19//!
20//! [`tiny-skia`] is a very high-quality implementation of software rendering, based on the
21//! algorithms used by [Skia]. It is the fastest software renderer in the Rust community and it
22//! produces high-quality results. However, the feature set of the crate is intentionally limited
23//! so that what is there is fast and correct.
24//!
25//! This crate, `piet-tiny-skia`, provides a [`piet`]-based frontend for [`tiny-skia`] that may be
26//! more familiar to users of popular graphics APIs. It may be easier to use than the [`tiny-skia`]
27//! API while also maintaining the flexibility. It also provides text rendering, provided by the
28//! [`cosmic-text`] crate.
29//!
30//! To start, create a `tiny_skia::PixmapMut` and a [`Cache`]. Then, use the [`Cache`] to create a
31//! [`RenderContext`] to render into the pixmap. Finally the pixmap can be saved to a file or
32//! rendered elsewhere.
33//!
34//! [Skia]: https://skia.org/
35//! [`cosmic-text`]: https://crates.io/crates/cosmic-text
36//! [`piet`]: https://crates.io/crates/piet
37//! [`tiny-skia`]: https://crates.io/crates/tiny-skia
38
39#![forbid(unsafe_code, future_incompatible)]
40
41// Public dependencies.
42pub use piet;
43pub use tiny_skia;
44
45use cosmic_text::{Command as ZenoCommand, SwashCache};
46use piet::kurbo::{self, Affine, PathEl, Shape};
47use piet::Error as Pierror;
48
49use std::fmt;
50use std::mem;
51use std::slice;
52
53use tiny_skia as ts;
54use tinyvec::TinyVec;
55use ts::{Mask, PathBuilder, PixmapMut, Shader};
56
57/// Trait to get a `PixmapMut` from some type.
58///
59/// This allows the [`RenderContext`] to be wrapped around borrowed or owned types, without regard
60/// for the storage medium.
61pub trait AsPixmapMut {
62    /// Get a `PixmapMut` from this type.
63    fn as_pixmap_mut(&mut self) -> PixmapMut<'_>;
64}
65
66impl<T: AsPixmapMut + ?Sized> AsPixmapMut for &mut T {
67    fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
68        (**self).as_pixmap_mut()
69    }
70}
71
72impl<'a> AsPixmapMut for PixmapMut<'a> {
73    fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
74        // Awful strategy for reborrowing.
75        let width = self.width();
76        let height = self.height();
77
78        PixmapMut::from_bytes(self.data_mut(), width, height)
79            .expect("PixmapMut::from_bytes should succeed")
80    }
81}
82
83impl AsPixmapMut for ts::Pixmap {
84    fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
85        self.as_mut()
86    }
87}
88
89/// The cache for [`tiny-skia`] resources.
90///
91/// This type contains some resources that can be shared between render contexts. This sharing
92/// reduces the number of allocations involved in the process of rendering.
93pub struct Cache {
94    /// Cached path builder.
95    path_builder: Option<PathBuilder>,
96
97    /// The text renderer object.
98    text: Text,
99
100    /// Drawn glyphs.
101    glyph_cache: Option<SwashCache>,
102
103    /// Allocation to hold dashes in.
104    dash_buffer: Vec<f32>,
105}
106
107impl fmt::Debug for Cache {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.pad("Cache { .. }")
110    }
111}
112
113impl Default for Cache {
114    fn default() -> Self {
115        Self {
116            path_builder: Some(PathBuilder::new()),
117            text: Text(piet_cosmic_text::Text::new()),
118            glyph_cache: Some(SwashCache::new()),
119            dash_buffer: vec![],
120        }
121    }
122}
123
124/// The whole point.
125pub struct RenderContext<'cache, Target: ?Sized> {
126    /// The mutable reference to the cache.
127    cache: &'cache mut Cache,
128
129    /// The last error that occurred.
130    last_error: Result<(), Pierror>,
131
132    /// The stack of render states.
133    states: TinyVec<[RenderState; 1]>,
134
135    /// Tolerance for curves.
136    tolerance: f64,
137
138    /// Scale to apply for bitmaps.
139    bitmap_scale: f64,
140
141    /// Flag to ignore the current state.
142    ignore_state: bool,
143
144    /// The mutable reference to the target.
145    target: Target,
146}
147
148/// Rendering state frame.
149struct RenderState {
150    /// The current transform.
151    transform: Affine,
152
153    /// The current clip.
154    clip: Option<Mask>,
155}
156
157impl Default for RenderState {
158    fn default() -> Self {
159        Self {
160            transform: Affine::IDENTITY,
161            clip: None,
162        }
163    }
164}
165
166/// The text renderer for [`tiny-skia`].
167///
168/// [`tiny-skia`]: https://crates.io/crates/tiny-skia
169#[derive(Debug, Clone)]
170pub struct Text(piet_cosmic_text::Text);
171
172impl Text {
173    /// Get the current DPI for the text renderer.
174    #[inline]
175    pub fn dpi(&self) -> f64 {
176        self.0.dpi()
177    }
178
179    /// Set the current DPI for the text renderer.
180    #[inline]
181    pub fn set_dpi(&mut self, dpi: f64) {
182        self.0.set_dpi(dpi);
183    }
184}
185
186/// The text layout builder for [`tiny-skia`].
187///
188/// [`tiny-skia`]: https://crates.io/crates/tiny-skia
189#[derive(Debug)]
190pub struct TextLayoutBuilder(piet_cosmic_text::TextLayoutBuilder);
191
192/// The text layout for [`tiny-skia`].
193///
194/// [`tiny-skia`]: https://crates.io/crates/tiny-skia
195#[derive(Debug, Clone)]
196pub struct TextLayout(piet_cosmic_text::TextLayout);
197
198/// The brush for [`tiny-skia`].
199///
200/// [`tiny-skia`]: https://crates.io/crates/tiny-skia
201#[derive(Clone)]
202pub struct Brush(BrushInner);
203
204#[derive(Clone)]
205enum BrushInner {
206    /// Solid color.
207    Solid(piet::Color),
208
209    /// Fixed linear gradient brush.
210    LinearGradient(piet::FixedLinearGradient),
211
212    /// Fixed radial gradient brush.
213    RadialGradient(piet::FixedRadialGradient),
214}
215
216/// The image used [`tiny-skia`].
217#[derive(Clone)]
218pub struct Image(tiny_skia::Pixmap);
219
220impl Cache {
221    /// Creates a new cache.
222    pub fn new() -> Self {
223        Self::default()
224    }
225
226    /// Gets a render context for the provided target pixmap.
227    pub fn render_context<Target: AsPixmapMut>(
228        &mut self,
229        target: Target,
230    ) -> RenderContext<'_, Target> {
231        RenderContext {
232            cache: self,
233            target,
234            last_error: Ok(()),
235            states: tinyvec::tiny_vec![RenderState::default(); 1],
236            tolerance: 0.1,
237            bitmap_scale: 1.0,
238            ignore_state: false,
239        }
240    }
241}
242
243impl<T: AsPixmapMut + ?Sized> RenderContext<'_, T> {
244    /// Get the bitmap scale.
245    pub fn bitmap_scale(&self) -> f64 {
246        self.bitmap_scale
247    }
248
249    /// Set the bitmap scale.
250    pub fn set_bitmap_scale(&mut self, scale: f64) {
251        self.bitmap_scale = scale;
252    }
253
254    /// Get the flattening tolerance.
255    pub fn tolerance(&self) -> f64 {
256        self.tolerance
257    }
258
259    /// Set the flattening tolerance.
260    pub fn set_tolerance(&mut self, tolerance: f64) {
261        self.tolerance = tolerance;
262    }
263
264    /// Get the inner target.
265    pub fn target(&self) -> &T {
266        &self.target
267    }
268
269    /// Get the inner target.
270    pub fn target_mut(&mut self) -> &mut T {
271        &mut self.target
272    }
273
274    /// Unwrap this type and reduce it to the inner target.
275    pub fn into_target(self) -> T
276    where
277        T: Sized,
278    {
279        self.target
280    }
281
282    fn fill_impl(
283        &mut self,
284        shape: impl Shape,
285        shader: tiny_skia::Shader<'_>,
286        rule: tiny_skia::FillRule,
287    ) {
288        // Get the last state out.
289        let Self {
290            cache,
291            target,
292            states,
293            tolerance,
294            ..
295        } = self;
296
297        // Get out the path builder.
298        let mut builder = cache.path_builder.take().unwrap_or_default();
299
300        // Convert the shape to a path.
301        cvt_shape_to_skia_path(&mut builder, shape, *tolerance);
302        let path = match builder.finish() {
303            Some(path) => path,
304            None => return,
305        };
306
307        // Draw the shape.
308        let paint = tiny_skia::Paint {
309            shader,
310            ..Default::default()
311        };
312        let state = states.last().unwrap();
313
314        let (transform, mask) = if self.ignore_state {
315            (
316                ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32),
317                None,
318            )
319        } else {
320            let real_transform = Affine::scale(self.bitmap_scale) * state.transform;
321            (cvt_affine(real_transform), state.clip.as_ref())
322        };
323
324        target
325            .as_pixmap_mut()
326            .fill_path(&path, &paint, rule, transform, mask);
327
328        // Keep the allocation around.
329        self.cache.path_builder = Some(path.clear());
330    }
331
332    fn stroke_impl(
333        &mut self,
334        shape: impl Shape,
335        shader: tiny_skia::Shader<'_>,
336        stroke: &ts::Stroke,
337    ) {
338        // Get the last state out.
339        let Self {
340            cache,
341            target,
342            states,
343            tolerance,
344            ..
345        } = self;
346
347        // Get out the path builder.
348        let mut builder = cache.path_builder.take().unwrap_or_default();
349
350        // Convert the shape to a path.
351        cvt_shape_to_skia_path(&mut builder, shape, *tolerance);
352        let path = match builder.finish() {
353            Some(path) => path,
354            None => return,
355        };
356
357        // Draw the shape.
358        let paint = tiny_skia::Paint {
359            shader,
360            ..Default::default()
361        };
362        let state = states.last().unwrap();
363
364        let (transform, mask) = if self.ignore_state {
365            (
366                ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32),
367                None,
368            )
369        } else {
370            let real_transform = Affine::scale(self.bitmap_scale) * state.transform;
371            (cvt_affine(real_transform), state.clip.as_ref())
372        };
373
374        target
375            .as_pixmap_mut()
376            .stroke_path(&path, &paint, stroke, transform, mask);
377
378        // Keep the allocation around.
379        self.cache.path_builder = Some(path.clear());
380    }
381
382    #[allow(clippy::if_same_then_else)]
383    fn draw_glyph(&mut self, pos: kurbo::Point, glyph: &cosmic_text::LayoutGlyph, run_y: f32) {
384        // Take the glyph cache or make a new one.
385        let mut glyph_cache = self
386            .cache
387            .glyph_cache
388            .take()
389            .unwrap_or_else(SwashCache::new);
390
391        let physical = glyph.physical((0., 0.), 1.0);
392        self.cache.text.clone().0.with_font_system_mut(|system| {
393            // Try to get the font outline, which we can draw directly with tiny-skia.
394            if let Some(outline) = glyph_cache.get_outline_commands(system, physical.cache_key) {
395                // Q: Why go through the trouble of rendering the glyph outlines like this, instead
396                // of just using rasterized images?
397                //
398                // A: If we render the glyphs as curves and we scale up the image, the curves will
399                // scale up as well, preventing the "blurry text" issue that happens with rasterized
400                // images. In addition it feels more "pure" to work the font rendering directly
401                // into tiny-skia.
402                //
403                // By the way, I know that tiny-skia is looking to add text drawing support. If
404                // you're reading this and want to try porting parts of this to tiny-skia, feel
405                // free to use it. I hereby release all of the text rendering code in this file
406                // and in piet-cosmic-text under the 3-clause BSD license that tiny-skia uses, as
407                // long as it goes to tiny-skia.
408                let offset = kurbo::Affine::translate((
409                    pos.x + physical.x as f64 + physical.cache_key.x_bin.as_float() as f64,
410                    pos.y
411                        + run_y as f64
412                        + physical.y as f64
413                        + physical.cache_key.y_bin.as_float() as f64,
414                )) * Affine::scale_non_uniform(1.0, -1.0);
415                let color = glyph.color_opt.map_or(
416                    {
417                        let (r, g, b, a) = piet::util::DEFAULT_TEXT_COLOR.as_rgba();
418                        ts::Color::from_rgba(r as f32, g as f32, b as f32, a as f32)
419                            .expect("default text color should be valid")
420                    },
421                    |c| {
422                        let [r, g, b, a] = [c.r(), c.g(), c.b(), c.a()];
423                        ts::Color::from_rgba8(r, g, b, a)
424                    },
425                );
426
427                // Fill in the outline.
428                self.fill_impl(
429                    ZenoShape {
430                        cmds: outline,
431                        offset,
432                    },
433                    ts::Shader::SolidColor(color),
434                    ts::FillRule::EvenOdd,
435                );
436            } else {
437                // Blit the image onto the target.
438                let default_color = {
439                    let (r, g, b, a) = piet::util::DEFAULT_TEXT_COLOR.as_rgba8();
440                    cosmic_text::Color::rgba(r, g, b, a)
441                };
442                glyph_cache.with_pixels(system, physical.cache_key, default_color, |x, y, clr| {
443                    let [r, g, b, a] = [clr.r(), clr.g(), clr.b(), clr.a()];
444                    let color = ts::Color::from_rgba8(r, g, b, a);
445
446                    // Straight-blit the image.
447                    self.fill_impl(
448                        kurbo::Rect::from_origin_size((x as f64, y as f64), (1., 1.)),
449                        Shader::SolidColor(color),
450                        ts::FillRule::EvenOdd,
451                    );
452                });
453            }
454        });
455
456        self.cache.glyph_cache = Some(glyph_cache);
457    }
458}
459
460macro_rules! leap {
461    ($this:expr,$e:expr,$msg:literal) => {{
462        match ($e) {
463            Some(v) => v,
464            None => {
465                $this.last_error = Err(Pierror::BackendError($msg.into()));
466                return;
467            }
468        }
469    }};
470}
471
472impl<T: AsPixmapMut + ?Sized> piet::RenderContext for RenderContext<'_, T> {
473    type Brush = Brush;
474    type Image = Image;
475    type Text = Text;
476    type TextLayout = TextLayout;
477
478    fn status(&mut self) -> Result<(), Pierror> {
479        mem::replace(&mut self.last_error, Ok(()))
480    }
481
482    fn solid_brush(&mut self, color: piet::Color) -> Self::Brush {
483        Brush(BrushInner::Solid(color))
484    }
485
486    fn gradient(
487        &mut self,
488        gradient: impl Into<piet::FixedGradient>,
489    ) -> Result<Self::Brush, Pierror> {
490        Ok(Brush(match gradient.into() {
491            piet::FixedGradient::Linear(lin) => BrushInner::LinearGradient(lin),
492            piet::FixedGradient::Radial(rad) => BrushInner::RadialGradient(rad),
493        }))
494    }
495
496    fn clear(&mut self, region: impl Into<Option<kurbo::Rect>>, mut color: piet::Color) {
497        let region = region.into();
498
499        // Pre-multiply the color.
500        let clamp = |x: f64| {
501            if x < 0.0 {
502                0.0
503            } else if x > 1.0 {
504                1.0
505            } else {
506                x
507            }
508        };
509        let (r, g, b, a) = color.as_rgba();
510        let r = clamp(r * a);
511        let g = clamp(g * a);
512        let b = clamp(b * a);
513        color = piet::Color::rgba(r, g, b, 1.0);
514
515        if let Some(region) = region {
516            // Fill the shape without clipping or transforming.
517            self.ignore_state = true;
518            self.fill_impl(
519                region,
520                Shader::SolidColor(cvt_color(color)),
521                tiny_skia::FillRule::Winding,
522            );
523            // TODO: Preserve this even in a panic.
524            self.ignore_state = false;
525        } else {
526            // Flood-fill the image with this color.
527            self.target.as_pixmap_mut().fill(cvt_color(color));
528        }
529    }
530
531    fn stroke(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>, width: f64) {
532        self.stroke_styled(shape, brush, width, &piet::StrokeStyle::default())
533    }
534
535    fn stroke_styled(
536        &mut self,
537        shape: impl kurbo::Shape,
538        brush: &impl piet::IntoBrush<Self>,
539        width: f64,
540        style: &piet::StrokeStyle,
541    ) {
542        let mut stroke = ts::Stroke {
543            width: width as f32,
544            line_cap: match style.line_cap {
545                piet::LineCap::Butt => ts::LineCap::Butt,
546                piet::LineCap::Round => ts::LineCap::Round,
547                piet::LineCap::Square => ts::LineCap::Square,
548            },
549            dash: if style.dash_pattern.is_empty() {
550                None
551            } else {
552                let dashes = {
553                    let mut dashes = mem::take(&mut self.cache.dash_buffer);
554                    dashes.clear();
555                    dashes.extend(style.dash_pattern.iter().map(|&x| x as f32));
556
557                    // If the length is odd, double it to make it even.
558                    if dashes.len() % 2 != 0 {
559                        dashes.extend_from_within(0..dashes.len() - 1);
560                    }
561
562                    dashes
563                };
564
565                ts::StrokeDash::new(dashes, style.dash_offset as f32)
566            },
567            ..Default::default()
568        };
569
570        match style.line_join {
571            piet::LineJoin::Bevel => stroke.line_join = ts::LineJoin::Bevel,
572            piet::LineJoin::Round => stroke.line_join = ts::LineJoin::Round,
573            piet::LineJoin::Miter { limit } => {
574                stroke.line_join = ts::LineJoin::Miter;
575                stroke.miter_limit = limit as f32;
576            }
577        }
578
579        let shader = leap!(
580            self,
581            brush.make_brush(self, || shape.bounding_box()).to_shader(),
582            "Failed to create shader"
583        );
584
585        self.stroke_impl(shape, shader, &stroke);
586
587        // TODO: Add a way to restore dashes to tiny-skia
588    }
589
590    fn fill(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>) {
591        let shader = leap!(
592            self,
593            brush.make_brush(self, || shape.bounding_box()).to_shader(),
594            "Failed to create shader"
595        );
596        self.fill_impl(&shape, shader, tiny_skia::FillRule::Winding)
597    }
598
599    fn fill_even_odd(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>) {
600        let shader = leap!(
601            self,
602            brush.make_brush(self, || shape.bounding_box()).to_shader(),
603            "Failed to create shader"
604        );
605        self.fill_impl(&shape, shader, tiny_skia::FillRule::EvenOdd)
606    }
607
608    fn clip(&mut self, shape: impl kurbo::Shape) {
609        let current_state = self.states.last_mut().unwrap();
610        let bitmap_scale =
611            ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32);
612        let path = {
613            let mut builder = self.cache.path_builder.take().unwrap_or_default();
614            cvt_shape_to_skia_path(&mut builder, shape, self.tolerance);
615            match builder.finish() {
616                Some(path) => path,
617                None => return,
618            }
619        };
620
621        match &mut current_state.clip {
622            slot @ None => {
623                // Create a new clip mask.
624                let target = self.target.as_pixmap_mut();
625                let mut clip = Mask::new(target.width(), target.height())
626                    .expect("Pixmap width/height should be valid clipmask width/height");
627                clip.fill_path(&path, tiny_skia::FillRule::EvenOdd, false, bitmap_scale);
628                *slot = Some(clip);
629            }
630
631            Some(mask) => {
632                mask.intersect_path(&path, tiny_skia::FillRule::EvenOdd, false, bitmap_scale);
633            }
634        }
635    }
636
637    fn text(&mut self) -> &mut Self::Text {
638        &mut self.cache.text
639    }
640
641    fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<kurbo::Point>) {
642        let pos = pos.into();
643        let mut line_processor = piet_cosmic_text::LineProcessor::new();
644
645        for run in layout.0.layout_runs() {
646            for glyph in run.glyphs {
647                // Process the line state.
648                let color = glyph.color_opt.unwrap_or({
649                    let piet_color = piet::util::DEFAULT_TEXT_COLOR;
650                    let (r, g, b, a) = piet_color.as_rgba8();
651                    cosmic_text::Color::rgba(r, g, b, a)
652                });
653                line_processor.handle_glyph(glyph, run.line_y, color);
654
655                self.draw_glyph(pos, glyph, run.line_y);
656            }
657        }
658
659        // Draw the lines.
660        let mov = pos.to_vec2();
661        for line in line_processor.lines() {
662            self.fill_impl(
663                line.into_rect() + mov,
664                Shader::SolidColor(cvt_color(line.color)),
665                ts::FillRule::EvenOdd,
666            );
667        }
668    }
669
670    fn save(&mut self) -> Result<(), Pierror> {
671        let current_state = self.states.last().unwrap();
672        self.states.push(RenderState {
673            transform: current_state.transform,
674            clip: current_state.clip.clone(),
675        });
676        Ok(())
677    }
678
679    fn restore(&mut self) -> Result<(), Pierror> {
680        if self.states.len() == 1 {
681            return Err(Pierror::StackUnbalance);
682        }
683
684        self.states.pop();
685        Ok(())
686    }
687
688    fn finish(&mut self) -> Result<(), Pierror> {
689        // We don't need to do anything here.
690        Ok(())
691    }
692
693    fn transform(&mut self, transform: Affine) {
694        self.states.last_mut().unwrap().transform *= transform;
695    }
696
697    fn make_image(
698        &mut self,
699        width: usize,
700        height: usize,
701        buf: &[u8],
702        format: piet::ImageFormat,
703    ) -> Result<Self::Image, Pierror> {
704        let data_buffer = match format {
705            piet::ImageFormat::RgbaPremul => buf.to_vec(),
706            piet::ImageFormat::RgbaSeparate => buf
707                .chunks_exact(4)
708                .flat_map(|chunk| {
709                    let [r, g, b, a]: &[u8; 4] = chunk.try_into().unwrap();
710                    let color = tiny_skia::ColorU8::from_rgba(*r, *g, *b, *a);
711                    let premul = color.premultiply();
712                    [premul.red(), premul.green(), premul.blue(), premul.alpha()]
713                })
714                .collect(),
715            piet::ImageFormat::Rgb => buf
716                .chunks_exact(3)
717                .flat_map(|chunk| {
718                    let [r, g, b]: &[u8; 3] = chunk.try_into().unwrap();
719                    [*r, *g, *b, 0xFF]
720                })
721                .collect(),
722            piet::ImageFormat::Grayscale => buf.iter().flat_map(|&v| [v, v, v, 0xFF]).collect(),
723            _ => return Err(Pierror::NotSupported),
724        };
725
726        // Convert from the data buffer to a pixel buffer.
727        let size = tiny_skia::IntSize::from_wh(
728            width.try_into().map_err(|_| Pierror::InvalidInput)?,
729            height.try_into().map_err(|_| Pierror::InvalidInput)?,
730        )
731        .ok_or_else(|| Pierror::InvalidInput)?;
732        let pixmap =
733            tiny_skia::Pixmap::from_vec(data_buffer, size).ok_or_else(|| Pierror::InvalidInput)?;
734
735        Ok(Image(pixmap))
736    }
737
738    fn draw_image(
739        &mut self,
740        image: &Self::Image,
741        dst_rect: impl Into<kurbo::Rect>,
742        interp: piet::InterpolationMode,
743    ) {
744        let bounds = kurbo::Rect::new(0.0, 0.0, image.0.width().into(), image.0.height().into());
745        self.draw_image_area(image, bounds, dst_rect, interp);
746    }
747
748    fn draw_image_area(
749        &mut self,
750        image: &Self::Image,
751        src_rect: impl Into<kurbo::Rect>,
752        dst_rect: impl Into<kurbo::Rect>,
753        interp: piet::InterpolationMode,
754    ) {
755        // Write a transform rule.
756        let src_rect = src_rect.into();
757        let dst_rect = dst_rect.into();
758        let scale_x = dst_rect.width() / src_rect.width();
759        let scale_y = dst_rect.height() / src_rect.height();
760
761        let transform = Affine::translate(-src_rect.origin().to_vec2())
762            * Affine::translate(dst_rect.origin().to_vec2())
763            * Affine::scale_non_uniform(scale_x, scale_y);
764
765        self.fill_impl(
766            dst_rect,
767            tiny_skia::Pattern::new(
768                image.0.as_ref(),
769                tiny_skia::SpreadMode::Repeat,
770                cvt_filter(interp),
771                1.0,
772                cvt_affine(transform),
773            ),
774            tiny_skia::FillRule::Winding,
775        )
776    }
777
778    fn capture_image_area(
779        &mut self,
780        src_rect: impl Into<kurbo::Rect>,
781    ) -> Result<Self::Image, Pierror> {
782        // Get the rectangle making up the image.
783        let src_rect = {
784            let src_rect = src_rect.into();
785
786            match ts::IntRect::from_xywh(
787                (src_rect.x0 * self.bitmap_scale) as i32,
788                (src_rect.y0 * self.bitmap_scale) as i32,
789                (src_rect.width() * self.bitmap_scale) as u32,
790                (src_rect.height() * self.bitmap_scale) as u32,
791            ) {
792                Some(src_rect) => src_rect,
793                None => return Err(Pierror::InvalidInput),
794            }
795        };
796
797        self.target
798            .as_pixmap_mut()
799            .as_ref()
800            .clone_rect(src_rect)
801            .ok_or_else(|| Pierror::InvalidInput)
802            .map(Image)
803    }
804
805    fn blurred_rect(
806        &mut self,
807        input_rect: kurbo::Rect,
808        blur_radius: f64,
809        brush: &impl piet::IntoBrush<Self>,
810    ) {
811        let size = piet::util::size_for_blurred_rect(input_rect, blur_radius);
812        let width = size.width as u32;
813        let height = size.height as u32;
814        if width == 0 || height == 0 {
815            return;
816        }
817
818        // Compute the blurred rectangle image.
819        let (mask, rect_exp) = {
820            let mut mask = Mask::new(width, height).unwrap();
821
822            let rect_exp = piet::util::compute_blurred_rect(
823                input_rect,
824                blur_radius,
825                width.try_into().unwrap(),
826                mask.data_mut(),
827            );
828
829            (mask, rect_exp)
830        };
831
832        // Create an image using this mask.
833        let mut image = Image(
834            ts::Pixmap::new(width, height)
835                .expect("Pixmap width/height should be valid clipmask width/height"),
836        );
837        let shader = leap!(
838            self,
839            brush.make_brush(self, || input_rect).to_shader(),
840            "Failed to create shader"
841        );
842        image.0.fill(ts::Color::TRANSPARENT);
843        image.0.fill_rect(
844            ts::Rect::from_xywh(0., 0., width as f32, height as f32).unwrap(),
845            &ts::Paint {
846                shader,
847                ..Default::default()
848            },
849            ts::Transform::identity(),
850            Some(&mask),
851        );
852
853        // Render this image.
854        self.draw_image(&image, rect_exp, piet::InterpolationMode::Bilinear);
855    }
856
857    fn current_transform(&self) -> Affine {
858        self.states.last().unwrap().transform
859    }
860}
861
862impl Brush {
863    fn to_shader(&self) -> Option<tiny_skia::Shader<'static>> {
864        match &self.0 {
865            BrushInner::Solid(color) => Some(Shader::SolidColor(cvt_color(*color))),
866            BrushInner::LinearGradient(linear) => tiny_skia::LinearGradient::new(
867                cvt_point(linear.start),
868                cvt_point(linear.end),
869                linear
870                    .stops
871                    .iter()
872                    .map(|s| cvt_gradient_stop(s.clone()))
873                    .collect(),
874                tiny_skia::SpreadMode::Pad,
875                tiny_skia::Transform::identity(),
876            ),
877            BrushInner::RadialGradient(radial) => tiny_skia::RadialGradient::new(
878                cvt_point(radial.center + radial.origin_offset),
879                cvt_point(radial.center),
880                radial.radius as f32,
881                radial
882                    .stops
883                    .iter()
884                    .map(|s| cvt_gradient_stop(s.clone()))
885                    .collect(),
886                tiny_skia::SpreadMode::Pad,
887                tiny_skia::Transform::identity(),
888            ),
889        }
890    }
891}
892
893impl<T: AsPixmapMut + ?Sized> piet::IntoBrush<RenderContext<'_, T>> for Brush {
894    fn make_brush<'b>(
895        &'b self,
896        _piet: &mut RenderContext<'_, T>,
897        _bbox: impl FnOnce() -> kurbo::Rect,
898    ) -> std::borrow::Cow<'b, Brush> {
899        std::borrow::Cow::Borrowed(self)
900    }
901}
902
903impl piet::Image for Image {
904    fn size(&self) -> kurbo::Size {
905        kurbo::Size::new(self.0.width() as f64, self.0.height() as f64)
906    }
907}
908
909impl piet::Text for Text {
910    type TextLayout = TextLayout;
911    type TextLayoutBuilder = TextLayoutBuilder;
912
913    fn font_family(&mut self, family_name: &str) -> Option<piet::FontFamily> {
914        self.0.font_family(family_name)
915    }
916
917    fn load_font(&mut self, data: &[u8]) -> Result<piet::FontFamily, Pierror> {
918        self.0.load_font(data)
919    }
920
921    fn new_text_layout(&mut self, text: impl piet::TextStorage) -> Self::TextLayoutBuilder {
922        TextLayoutBuilder(self.0.new_text_layout(text))
923    }
924}
925
926impl piet::TextLayoutBuilder for TextLayoutBuilder {
927    type Out = TextLayout;
928
929    fn max_width(self, width: f64) -> Self {
930        Self(self.0.max_width(width))
931    }
932
933    fn alignment(self, alignment: piet::TextAlignment) -> Self {
934        Self(self.0.alignment(alignment))
935    }
936
937    fn default_attribute(self, attribute: impl Into<piet::TextAttribute>) -> Self {
938        Self(self.0.default_attribute(attribute))
939    }
940
941    fn range_attribute(
942        self,
943        range: impl std::ops::RangeBounds<usize>,
944        attribute: impl Into<piet::TextAttribute>,
945    ) -> Self {
946        Self(self.0.range_attribute(range, attribute))
947    }
948
949    fn build(self) -> Result<Self::Out, Pierror> {
950        Ok(TextLayout(self.0.build()?))
951    }
952}
953
954impl piet::TextLayout for TextLayout {
955    fn size(&self) -> kurbo::Size {
956        self.0.size()
957    }
958
959    fn trailing_whitespace_width(&self) -> f64 {
960        self.0.trailing_whitespace_width()
961    }
962
963    fn image_bounds(&self) -> kurbo::Rect {
964        self.0.image_bounds()
965    }
966
967    fn text(&self) -> &str {
968        self.0.text()
969    }
970
971    fn line_text(&self, line_number: usize) -> Option<&str> {
972        self.0.line_text(line_number)
973    }
974
975    fn line_metric(&self, line_number: usize) -> Option<piet::LineMetric> {
976        self.0.line_metric(line_number)
977    }
978
979    fn line_count(&self) -> usize {
980        self.0.line_count()
981    }
982
983    fn hit_test_point(&self, point: kurbo::Point) -> piet::HitTestPoint {
984        self.0.hit_test_point(point)
985    }
986
987    fn hit_test_text_position(&self, idx: usize) -> piet::HitTestPosition {
988        self.0.hit_test_text_position(idx)
989    }
990}
991
992struct ZenoShape<'a> {
993    cmds: &'a [ZenoCommand],
994    offset: kurbo::Affine,
995}
996
997impl Shape for ZenoShape<'_> {
998    type PathElementsIter<'iter> = ZenoIter<'iter> where Self: 'iter;
999
1000    fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
1001        ZenoIter {
1002            inner: self.cmds.iter(),
1003            offset: self.offset,
1004        }
1005    }
1006
1007    fn area(&self) -> f64 {
1008        self.to_path(1.0).area()
1009    }
1010
1011    fn perimeter(&self, accuracy: f64) -> f64 {
1012        self.to_path(accuracy).perimeter(accuracy)
1013    }
1014
1015    fn winding(&self, pt: kurbo::Point) -> i32 {
1016        self.to_path(1.0).winding(pt)
1017    }
1018
1019    fn bounding_box(&self) -> kurbo::Rect {
1020        self.to_path(1.0).bounding_box()
1021    }
1022}
1023
1024#[derive(Clone)]
1025struct ZenoIter<'a> {
1026    inner: slice::Iter<'a, ZenoCommand>,
1027    offset: kurbo::Affine,
1028}
1029
1030impl ZenoIter<'_> {
1031    fn leap(&self) -> impl Fn(&ZenoCommand) -> kurbo::PathEl {
1032        let offset = self.offset;
1033        move |&cmd| offset * cvt_zeno_command(cmd)
1034    }
1035}
1036
1037impl Iterator for ZenoIter<'_> {
1038    type Item = kurbo::PathEl;
1039
1040    fn next(&mut self) -> Option<Self::Item> {
1041        self.inner.next().map(self.leap())
1042    }
1043
1044    fn size_hint(&self) -> (usize, Option<usize>) {
1045        self.inner.size_hint()
1046    }
1047
1048    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1049        self.inner.nth(n).map(self.leap())
1050    }
1051
1052    fn fold<B, F>(self, init: B, f: F) -> B
1053    where
1054        Self: Sized,
1055        F: FnMut(B, Self::Item) -> B,
1056    {
1057        let m = self.leap();
1058        self.inner.map(m).fold(init, f)
1059    }
1060}
1061
1062fn cvt_zeno_command(cmd: ZenoCommand) -> kurbo::PathEl {
1063    let cvt_vector = |v: zeno::Vector| {
1064        let [x, y]: [f32; 2] = v.into();
1065        kurbo::Point::new(x as f64, y as f64)
1066    };
1067
1068    match cmd {
1069        ZenoCommand::Close => kurbo::PathEl::ClosePath,
1070        ZenoCommand::MoveTo(p) => kurbo::PathEl::MoveTo(cvt_vector(p)),
1071        ZenoCommand::LineTo(p) => kurbo::PathEl::LineTo(cvt_vector(p)),
1072        ZenoCommand::QuadTo(p1, p2) => kurbo::PathEl::QuadTo(cvt_vector(p1), cvt_vector(p2)),
1073        ZenoCommand::CurveTo(p1, p2, p3) => {
1074            kurbo::PathEl::CurveTo(cvt_vector(p1), cvt_vector(p2), cvt_vector(p3))
1075        }
1076    }
1077}
1078
1079fn cvt_shape_to_skia_path(builder: &mut PathBuilder, shape: impl Shape, tolerance: f64) {
1080    shape.path_elements(tolerance).for_each(|el| match el {
1081        PathEl::ClosePath => builder.close(),
1082        PathEl::MoveTo(p) => builder.move_to(p.x as f32, p.y as f32),
1083        PathEl::LineTo(p) => builder.line_to(p.x as f32, p.y as f32),
1084        PathEl::QuadTo(p1, p2) => {
1085            builder.quad_to(p1.x as f32, p1.y as f32, p2.x as f32, p2.y as f32)
1086        }
1087        PathEl::CurveTo(p1, p2, p3) => builder.cubic_to(
1088            p1.x as f32,
1089            p1.y as f32,
1090            p2.x as f32,
1091            p2.y as f32,
1092            p3.x as f32,
1093            p3.y as f32,
1094        ),
1095    })
1096}
1097
1098fn cvt_affine(p: kurbo::Affine) -> tiny_skia::Transform {
1099    let [a, b, c, d, e, f] = p.as_coeffs();
1100    tiny_skia::Transform::from_row(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32)
1101}
1102
1103fn cvt_gradient_stop(stop: piet::GradientStop) -> tiny_skia::GradientStop {
1104    tiny_skia::GradientStop::new(stop.pos, cvt_color(stop.color))
1105}
1106
1107fn cvt_color(p: piet::Color) -> tiny_skia::Color {
1108    let (r, g, b, a) = p.as_rgba();
1109    tiny_skia::Color::from_rgba(r as f32, g as f32, b as f32, a as f32).expect("Color out of range")
1110}
1111
1112fn cvt_point(p: kurbo::Point) -> tiny_skia::Point {
1113    tiny_skia::Point {
1114        x: p.x as f32,
1115        y: p.y as f32,
1116    }
1117}
1118
1119fn cvt_filter(p: piet::InterpolationMode) -> tiny_skia::FilterQuality {
1120    match p {
1121        piet::InterpolationMode::NearestNeighbor => tiny_skia::FilterQuality::Nearest,
1122        piet::InterpolationMode::Bilinear => tiny_skia::FilterQuality::Bilinear,
1123    }
1124}