Skip to main content

yog_graphics/
draw.rs

1//! Drawings: what to paint, in order — paths filled or stroked, images —
2//! in a drawing's own units (`y` up). Glyphs, icons and shapes are
3//! drawings; a [`Canvas`] paints them.
4
5use yog_data::Schema;
6
7use crate::color::Color;
8use crate::image::Image;
9use crate::mask::Mask;
10use crate::math::Vec2;
11use crate::path::{self, Seg};
12use crate::raster::Canvas;
13use crate::rect::Rect;
14
15/// One step of a drawing.
16#[derive(Schema, Debug, Clone, PartialEq)]
17#[data(tag = "op")]
18pub enum Op {
19    /// A path's inside painted (non-zero winding).
20    Fill(Fill),
21    /// A path's outline painted, round-joined and capped.
22    Stroke(Stroke),
23    /// An image in a box.
24    Image(ImageOp),
25    /// A one-color picture, a bit a pixel, in a box (a pixel font's glyph).
26    Mask(MaskOp),
27}
28
29#[derive(Schema, Debug, Clone, PartialEq)]
30pub struct Fill {
31    pub path: Vec<Seg>,
32    /// None: the current color (what the drawing is drawn in — a text's).
33    pub color: Option<Color>,
34}
35
36#[derive(Schema, Debug, Clone, PartialEq)]
37pub struct Stroke {
38    pub path: Vec<Seg>,
39    /// How wide, in the drawing's units.
40    pub width: f32,
41    /// None: the current color.
42    pub color: Option<Color>,
43}
44
45/// `image` in box `rect` (`rect.x, rect.y` its bottom-left corner).
46#[derive(Schema, Debug, Clone, PartialEq)]
47pub struct ImageOp {
48    pub rect: Rect,
49    pub image: Image,
50    /// Whether the current color tints it (an image in one color, white
51    /// to take the current one whole); else it keeps its colors.
52    #[data(default)]
53    pub tinted: bool,
54}
55
56/// `mask` in box `rect` (`rect.x, rect.y` its bottom-left corner), its set
57/// pixels `color` (none: the current color — a text's).
58#[derive(Schema, Debug, Clone, PartialEq)]
59pub struct MaskOp {
60    pub rect: Rect,
61    pub mask: Mask,
62    pub color: Option<Color>,
63}
64
65/// What `ops` cover (control points included); `None` when they draw
66/// nothing.
67pub fn bounds(ops: &[Op]) -> Option<Rect> {
68    ops.iter()
69        .filter_map(|op| match op {
70            Op::Fill(f) => path::bounds(&f.path, 0.0),
71            Op::Stroke(s) => path::bounds(&s.path, s.width / 2.0),
72            Op::Image(i) => Some(i.rect),
73            Op::Mask(m) => Some(m.rect),
74        })
75        .reduce(|a, b| a.union(&b))
76}
77
78impl Canvas {
79    /// `ops` painted: each point through `to_px` (the drawing's units to
80    /// the canvas's pixels, `scale` pixels a unit), parts without a color
81    /// in `current`.
82    pub fn draw(&mut self, ops: &[Op], to_px: impl Fn(Vec2) -> Vec2, scale: f32, current: Color) {
83        for op in ops {
84            match op {
85                Op::Fill(f) => {
86                    self.fill(&path::flatten(&f.path, &to_px), f.color.unwrap_or(current))
87                }
88                Op::Stroke(s) => self.stroke(
89                    &path::flatten(&s.path, &to_px),
90                    s.width * scale,
91                    s.color.unwrap_or(current),
92                ),
93                Op::Image(i) => {
94                    self.image(
95                        &i.image,
96                        px_box(i.rect, &to_px),
97                        i.tinted.then_some(current),
98                    );
99                }
100                Op::Mask(m) => {
101                    self.mask(&m.mask, px_box(m.rect, &to_px), m.color.unwrap_or(current))
102                }
103            }
104        }
105    }
106}
107
108/// Box `r` of a drawing in the canvas's pixels.
109fn px_box(r: Rect, to_px: &impl Fn(Vec2) -> Vec2) -> Rect {
110    Rect::from_corners(
111        to_px(Vec2::new(r.x, r.y)),
112        to_px(Vec2::new(r.right(), r.bottom())),
113    )
114}