vg/
paint.rs

1// TODO: prefix paint creation functions with make_ or new_
2// so that they are easier to find when autocompleting
3
4use crate::geometry::Transform2D;
5use crate::{
6    Align,
7    Baseline,
8    Color,
9    FillRule,
10    FontId,
11    ImageId,
12    LineCap,
13    LineJoin,
14};
15
16#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Default)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub(crate) struct GradientStop(pub f32, pub Color);
19
20// We use MultiStopGradient as a key since we cache them. We either need
21// to define Hash (for HashMap) or Ord for (BTreeMap).
22impl Eq for GradientStop {}
23impl Ord for GradientStop {
24    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
25        if other < self {
26            std::cmp::Ordering::Less
27        } else if self < other {
28            std::cmp::Ordering::Greater
29        } else {
30            std::cmp::Ordering::Equal
31        }
32    }
33}
34
35pub(crate) type MultiStopGradient = [GradientStop; 16];
36
37#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39pub(crate) enum GradientColors {
40    TwoStop {
41        start_color: Color,
42        end_color: Color,
43    },
44    MultiStop {
45        // We support up to 16 stops.
46        stops: MultiStopGradient,
47    },
48}
49impl GradientColors {
50    fn mul_alpha(&mut self, a: f32) {
51        match self {
52            GradientColors::TwoStop { start_color, end_color } => {
53                start_color.a *= a;
54                end_color.a *= a;
55            }
56            GradientColors::MultiStop { stops } => {
57                for stop in stops {
58                    stop.1.a *= a;
59                }
60            }
61        }
62    }
63    fn from_stops(stops: &[(f32, Color)]) -> GradientColors {
64        if stops.is_empty() {
65            // No stops, we use black.
66            GradientColors::TwoStop {
67                start_color: Color::black(),
68                end_color: Color::black(),
69            }
70        } else if stops.len() == 1 {
71            // One stop devolves to a solid color fill (but using the gradient shader variation).
72            GradientColors::TwoStop {
73                start_color: stops[0].1,
74                end_color: stops[0].1,
75            }
76        } else if stops.len() == 2 && stops[0].0 <= 0.0 && stops[1].0 >= 1.0 {
77            // Two stops takes the classic gradient path, so long as the stop positions are at
78            // the extents (if the stop positions are inset then we'll fill to them).
79            GradientColors::TwoStop {
80                start_color: stops[0].1,
81                end_color: stops[1].1,
82            }
83        } else {
84            // Actual multistop gradient. We copy out the stops and then use a stop with a
85            // position > 1.0 as a sentinel. GradientStore ignores stop positions > 1.0
86            // when synthesizing the gradient texture.
87            let mut out_stops: [GradientStop; 16] = Default::default();
88            for i in 0..16 {
89                if i < stops.len() {
90                    out_stops[i] = GradientStop(stops[i].0, stops[i].1);
91                } else {
92                    out_stops[i] = GradientStop(2.0, Color::black());
93                }
94            }
95            GradientColors::MultiStop { stops: out_stops }
96        }
97    }
98}
99
100#[derive(Copy, Clone, Debug)]
101#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
102pub(crate) enum PaintFlavor {
103    Color(Color),
104    #[cfg_attr(feature = "serde", serde(skip))]
105    Image {
106        id: ImageId,
107        cx: f32,
108        cy: f32,
109        width: f32,
110        height: f32,
111        angle: f32,
112        alpha: f32,
113    },
114    LinearGradient {
115        start_x: f32,
116        start_y: f32,
117        end_x: f32,
118        end_y: f32,
119        colors: GradientColors,
120    },
121    BoxGradient {
122        x: f32,
123        y: f32,
124        width: f32,
125        height: f32,
126        radius: f32,
127        feather: f32,
128        colors: GradientColors,
129    },
130    RadialGradient {
131        cx: f32,
132        cy: f32,
133        in_radius: f32,
134        out_radius: f32,
135        colors: GradientColors,
136    },
137}
138
139// Convenience method to fetch the GradientColors out of a PaintFlavor
140impl PaintFlavor {
141    pub(crate) fn gradient_colors(&self) -> Option<&GradientColors> {
142        match self {
143            PaintFlavor::LinearGradient { colors, .. } => Some(colors),
144            PaintFlavor::BoxGradient { colors, .. } => Some(colors),
145            PaintFlavor::RadialGradient { colors, .. } => Some(colors),
146            _ => None,
147        }
148    }
149}
150
151/// Reperesent glyph texture
152#[derive(Copy, Clone, Debug)]
153pub enum GlyphTexture {
154    /// Without texture
155    None,
156    /// Masked texture
157    AlphaMask(ImageId),
158    /// Color texture
159    ColorTexture(ImageId),
160}
161
162impl Default for GlyphTexture {
163    fn default() -> Self {
164        Self::None
165    }
166}
167
168/// Struct controlling how graphical shapes are rendered.
169///
170/// The Paint struct is a relatively lightweight object which contains all the information needed to
171/// display something on the canvas. Unlike the HTML canvas where the current drawing style is stored
172/// in an internal stack this paint struct is simply passed to the relevant drawing methods on the canvas.
173///
174/// Clients code can have as many paints as they desire for different use cases and styles. This makes
175/// the internal stack in the [Canvas](struct.Canvas.html) struct much lighter since it only needs to
176/// contain the transform stack and current scissor rectangle.
177///
178/// # Example
179/// ```
180/// use vg::{Paint, Path, Color, Canvas, renderer::Void};
181///
182/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
183///
184/// let fill_paint = Paint::color(Color::hex("454545"));
185/// let mut stroke_paint = Paint::color(Color::hex("bababa"));
186/// stroke_paint.set_line_width(4.0);
187///
188/// let mut path = Path::new();
189/// path.rounded_rect(10.0, 10.0, 100.0, 100.0, 20.0);
190/// canvas.fill_path(&mut path, fill_paint);
191/// canvas.stroke_path(&mut path, stroke_paint);
192/// ```
193#[derive(Copy, Clone, Debug)]
194#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
195pub struct Paint {
196    pub(crate) flavor: PaintFlavor,
197    pub(crate) transform: Transform2D,
198    #[cfg_attr(feature = "serialization", serde(skip))]
199    pub(crate) glyph_texture: GlyphTexture,
200    pub(crate) shape_anti_alias: bool,
201    pub(crate) stencil_strokes: bool,
202    pub(crate) miter_limit: f32,
203    pub(crate) line_width: f32,
204    pub(crate) line_cap_start: LineCap,
205    pub(crate) line_cap_end: LineCap,
206    pub(crate) line_join: LineJoin,
207    #[cfg_attr(feature = "serialization", serde(skip))]
208    pub(crate) font_ids: [Option<FontId>; 8],
209    pub(crate) font_size: f32,
210    pub(crate) letter_spacing: f32,
211    pub(crate) text_baseline: Baseline,
212    pub(crate) text_align: Align,
213    pub(crate) fill_rule: FillRule,
214}
215
216impl Default for Paint {
217    fn default() -> Self {
218        Self {
219            flavor: PaintFlavor::Color(Color::white()),
220            transform: Default::default(),
221            glyph_texture: Default::default(),
222            shape_anti_alias: true,
223            stencil_strokes: true,
224            miter_limit: 10.0,
225            line_width: 1.0,
226            line_cap_start: Default::default(),
227            line_cap_end: Default::default(),
228            line_join: Default::default(),
229            font_ids: Default::default(),
230            font_size: 16.0,
231            letter_spacing: 0.0,
232            text_baseline: Default::default(),
233            text_align: Default::default(),
234            fill_rule: Default::default(),
235        }
236    }
237}
238
239impl Paint {
240    /// Creates a new solid color paint
241    pub fn color(color: Color) -> Self {
242        Self {
243            flavor: PaintFlavor::Color(color),
244            ..Default::default()
245        }
246    }
247
248    /// Creates a new image pattern paint.
249    ///
250    /// * `id` - is handle to the image to render
251    /// * `cx` `cy` - Specify the top-left location of the image pattern
252    /// * `width` `height` - The size of one image
253    /// * `angle` - Rotation around the top-left corner
254    /// * `alpha` - Transparency applied on the image
255    ///
256    /// # Example
257    /// ```
258    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
259    ///
260    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
261    ///
262    /// let image_id = canvas.load_image_file("examples/assets/rust-logo.png", ImageFlags::GENERATE_MIPMAPS).expect("Cannot create image");
263    /// let fill_paint = Paint::image(image_id, 10.0, 10.0, 85.0, 85.0, 0.0, 1.0);
264    ///
265    /// let mut path = Path::new();
266    /// path.rect(10.0, 10.0, 85.0, 85.0);
267    /// canvas.fill_path(&mut path, fill_paint);
268    /// ```
269    pub fn image(id: ImageId, cx: f32, cy: f32, width: f32, height: f32, angle: f32, alpha: f32) -> Self {
270        Self {
271            flavor: PaintFlavor::Image {
272                id,
273                cx,
274                cy,
275                width,
276                height,
277                angle,
278                alpha,
279            },
280            ..Default::default()
281        }
282    }
283
284    /// Creates and returns a linear gradient paint.
285    ///
286    /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
287    ///
288    /// # Example
289    /// ```
290    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
291    ///
292    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
293    ///
294    /// let bg = Paint::linear_gradient(0.0, 0.0, 0.0, 100.0, Color::rgba(255, 255, 255, 16), Color::rgba(0, 0, 0, 16));
295    /// let mut path = Path::new();
296    /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
297    /// canvas.fill_path(&mut path, bg);
298    /// ```
299    pub fn linear_gradient(
300        start_x: f32,
301        start_y: f32,
302        end_x: f32,
303        end_y: f32,
304        start_color: Color,
305        end_color: Color,
306    ) -> Self {
307        Self {
308            flavor: PaintFlavor::LinearGradient {
309                start_x,
310                start_y,
311                end_x,
312                end_y,
313                colors: GradientColors::TwoStop { start_color, end_color },
314            },
315            ..Default::default()
316        }
317    }
318    /// Creates and returns a linear gradient paint with two or more stops.
319    ///
320    /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
321    /// # Example
322    /// ```
323    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
324    ///
325    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
326    ///
327    /// let bg = Paint::linear_gradient_stops(
328    ///    0.0, 0.0,
329    ///    0.0, 100.0,
330    ///    &[
331    ///         (0.0, Color::rgba(255, 255, 255, 16)),
332    ///         (0.5, Color::rgba(0, 0, 0, 16)),
333    ///         (1.0, Color::rgba(255, 0, 0, 16))
334    ///    ]);
335    /// let mut path = Path::new();
336    /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
337    /// canvas.fill_path(&mut path, bg);
338    /// ```
339    pub fn linear_gradient_stops(start_x: f32, start_y: f32, end_x: f32, end_y: f32, stops: &[(f32, Color)]) -> Self {
340        Self {
341            flavor: PaintFlavor::LinearGradient {
342                start_x,
343                start_y,
344                end_x,
345                end_y,
346                colors: GradientColors::from_stops(stops),
347            },
348            ..Default::default()
349        }
350    }
351
352    /// Creates and returns a box gradient.
353    ///
354    /// Box gradient is a feathered rounded rectangle, it is useful for rendering
355    /// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
356    /// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
357    /// the border of the rectangle is. Parameter inner_color specifies the inner color and outer_color the outer color of the gradient.
358    /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
359    ///
360    /// # Example
361    /// ```
362    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
363    ///
364    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
365    ///
366    /// let bg = Paint::box_gradient(
367    ///    0.0,
368    ///    0.0,
369    ///    100.0,
370    ///    100.0,
371    ///    10.0,
372    ///    10.0,
373    ///    Color::rgba(0, 0, 0, 128),
374    ///    Color::rgba(0, 0, 0, 0),
375    /// );
376    ///
377    /// let mut path = Path::new();
378    /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
379    /// canvas.fill_path(&mut path, bg);
380    /// ```
381    pub fn box_gradient(
382        x: f32,
383        y: f32,
384        width: f32,
385        height: f32,
386        radius: f32,
387        feather: f32,
388        inner_color: Color,
389        outer_color: Color,
390    ) -> Self {
391        Self {
392            flavor: PaintFlavor::BoxGradient {
393                x,
394                y,
395                width,
396                height,
397                radius,
398                feather,
399                colors: GradientColors::TwoStop {
400                    start_color: inner_color,
401                    end_color: outer_color,
402                },
403            },
404            ..Default::default()
405        }
406    }
407
408    /// Creates and returns a radial gradient.
409    ///
410    /// Parameters (cx,cy) specify the center, in_radius and out_radius specify
411    /// the inner and outer radius of the gradient, inner_color specifies the start color and outer_color the end color.
412    /// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
413    ///
414    /// # Example
415    /// ```
416    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
417    ///
418    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
419    ///
420    /// let bg = Paint::radial_gradient(
421    ///    50.0,
422    ///    50.0,
423    ///    18.0,
424    ///    24.0,
425    ///    Color::rgba(0, 0, 0, 128),
426    ///    Color::rgba(0, 0, 0, 0),
427    /// );
428    ///
429    /// let mut path = Path::new();
430    /// path.circle(50.0, 50.0, 20.0);
431    /// canvas.fill_path(&mut path, bg);
432    /// ```
433    pub fn radial_gradient(
434        cx: f32,
435        cy: f32,
436        in_radius: f32,
437        out_radius: f32,
438        inner_color: Color,
439        outer_color: Color,
440    ) -> Self {
441        Self {
442            flavor: PaintFlavor::RadialGradient {
443                cx,
444                cy,
445                in_radius,
446                out_radius,
447                colors: GradientColors::TwoStop {
448                    start_color: inner_color,
449                    end_color: outer_color,
450                },
451            },
452            ..Default::default()
453        }
454    }
455
456    /// Creates and returns a multi-stop radial gradient.
457    ///
458    /// Parameters (cx,cy) specify the center, in_radius and out_radius specify the inner and outer radius of the gradient,
459    /// colors specifies a list of color stops with offsets. The first offset should be 0.0 and the last offset should be 1.0.
460    /// If a gradient has more than 16 stops, then only the first 16 stops will be used.
461    ///
462    /// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
463    ///
464    /// # Example
465    /// ```
466    /// use vg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
467    ///
468    /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
469    ///
470    /// let bg = Paint::radial_gradient_stops(
471    ///    50.0,
472    ///    50.0,
473    ///    18.0,
474    ///    24.0,
475    ///    &[
476    ///         (0.0, Color::rgba(0, 0, 0, 128)),
477    ///         (0.5, Color::rgba(0, 0, 128, 128)),
478    ///         (1.0, Color::rgba(0, 128, 0, 128))
479    ///    ]
480    /// );
481    ///
482    /// let mut path = Path::new();
483    /// path.circle(50.0, 50.0, 20.0);
484    /// canvas.fill_path(&mut path, bg);
485    /// ```
486    pub fn radial_gradient_stops(cx: f32, cy: f32, in_radius: f32, out_radius: f32, stops: &[(f32, Color)]) -> Self {
487        Self {
488            flavor: PaintFlavor::RadialGradient {
489                cx,
490                cy,
491                in_radius,
492                out_radius,
493                colors: GradientColors::from_stops(stops),
494            },
495            ..Default::default()
496        }
497    }
498
499    /// Creates a new solid color paint
500    pub fn set_color(&mut self, color: Color) {
501        self.flavor = PaintFlavor::Color(color);
502    }
503
504    pub(crate) fn glyph_texture(&self) -> GlyphTexture {
505        self.glyph_texture
506    }
507
508    /// Set an alpha mask or color glyph texture; this is only used by draw_triangles which is used for text.
509    // This is scoped to crate visibility because fill_path and stroke_path don't propagate
510    // the alpha mask (so nothing draws), and the texture coordinates are used for antialiasing
511    // when path drawing.
512    pub(crate) fn set_glyph_texture(&mut self, texture: GlyphTexture) {
513        self.glyph_texture = texture;
514    }
515
516    /// Returns boolean if the shapes drawn with this paint will be antialiased.
517    pub fn anti_alias(&self) -> bool {
518        self.shape_anti_alias
519    }
520
521    /// Sets whether shapes drawn with this paint will be anti aliased. Enabled by default.
522    pub fn set_anti_alias(&mut self, value: bool) {
523        self.shape_anti_alias = value;
524    }
525
526    /// True if this paint uses higher quality stencil strokes.
527    pub fn stencil_strokes(&self) -> bool {
528        self.stencil_strokes
529    }
530
531    /// Sets whether to use higher quality stencil strokes.
532    pub fn set_stencil_strokes(&mut self, value: bool) {
533        self.stencil_strokes = value;
534    }
535
536    /// Returns the current line width.
537    pub fn line_width(&self) -> f32 {
538        self.line_width
539    }
540
541    /// Sets the line width for shapes stroked with this paint.
542    pub fn set_line_width(&mut self, width: f32) {
543        self.line_width = width;
544    }
545
546    /// Getter for the miter limit
547    pub fn miter_limit(&self) -> f32 {
548        self.miter_limit
549    }
550
551    /// Sets the limit at which a sharp corner is drawn beveled.
552    ///
553    /// If the miter at a corner exceeds this limit, LineJoin is replaced with LineJoin::Bevel.
554    pub fn set_miter_limit(&mut self, limit: f32) {
555        self.miter_limit = limit;
556    }
557
558    /// Returns the current start line cap for this paint.
559    pub fn line_cap_start(&self) -> LineCap {
560        self.line_cap_start
561    }
562
563    /// Returns the current start line cap for this paint.
564    pub fn line_cap_end(&self) -> LineCap {
565        self.line_cap_end
566    }
567
568    /// Sets how the start and end of the line (cap) is drawn
569    ///
570    /// By default it's set to LineCap::Butt
571    pub fn set_line_cap(&mut self, cap: LineCap) {
572        self.line_cap_start = cap;
573        self.line_cap_end = cap;
574    }
575
576    /// Sets how the beggining cap of the line is drawn
577    ///
578    /// By default it's set to LineCap::Butt
579    pub fn set_line_cap_start(&mut self, cap: LineCap) {
580        self.line_cap_start = cap;
581    }
582
583    /// Sets how the end cap of the line is drawn
584    ///
585    /// By default it's set to LineCap::Butt
586    pub fn set_line_cap_end(&mut self, cap: LineCap) {
587        self.line_cap_end = cap;
588    }
589
590    /// Returns the current line join for this paint.
591    pub fn line_join(&self) -> LineJoin {
592        self.line_join
593    }
594
595    /// Sets how sharp path corners are drawn.
596    ///
597    /// By default it's set to LineJoin::Miter
598    pub fn set_line_join(&mut self, join: LineJoin) {
599        self.line_join = join;
600    }
601
602    /// Set the font
603    pub fn set_font(&mut self, font_ids: &[FontId]) {
604        self.font_ids = Default::default();
605
606        for (i, id) in font_ids.iter().take(8).enumerate() {
607            self.font_ids[i] = Some(*id);
608        }
609    }
610
611    /// Returns the current font size
612    ///
613    /// Only has effect on canvas text operations
614    pub fn font_size(&self) -> f32 {
615        self.font_size
616    }
617
618    /// Sets the font size.
619    ///
620    /// Only has effect on canvas text operations
621    pub fn set_font_size(&mut self, size: f32) {
622        self.font_size = size;
623    }
624
625    /// Returns the current letter spacing
626    pub fn letter_spacing(&self) -> f32 {
627        self.letter_spacing
628    }
629
630    /// Sets the letter spacing for this paint
631    ///
632    /// Only has effect on canvas text operations
633    pub fn set_letter_spacing(&mut self, spacing: f32) {
634        self.letter_spacing = spacing;
635    }
636
637    /// Returns the current vertical align
638    pub fn text_baseline(&self) -> Baseline {
639        self.text_baseline
640    }
641
642    /// Sets the text vertical alignment for this paint
643    ///
644    /// Only has effect on canvas text operations
645    pub fn set_text_baseline(&mut self, align: Baseline) {
646        self.text_baseline = align;
647    }
648
649    /// Returns the current horizontal align
650    pub fn text_align(&self) -> Align {
651        self.text_align
652    }
653
654    /// Sets the text horizontal alignment for this paint
655    ///
656    /// Only has effect on canvas text operations
657    pub fn set_text_align(&mut self, align: Align) {
658        self.text_align = align;
659    }
660
661    /// Retrieves the current fill rule setting for this paint
662    pub fn fill_rule(&self) -> FillRule {
663        self.fill_rule
664    }
665
666    /// Sets the current rule to be used when filling a path
667    ///
668    /// [https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule][1]
669    ///
670    /// [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule
671    pub fn set_fill_rule(&mut self, rule: FillRule) {
672        self.fill_rule = rule;
673    }
674
675    pub(crate) fn mul_alpha(&mut self, a: f32) {
676        match &mut self.flavor {
677            PaintFlavor::Color(color) => {
678                color.a *= a;
679            }
680            PaintFlavor::Image { alpha, .. } => {
681                *alpha *= a;
682            }
683            PaintFlavor::LinearGradient { colors, .. } => {
684                colors.mul_alpha(a);
685            }
686            PaintFlavor::BoxGradient { colors, .. } => {
687                colors.mul_alpha(a);
688            }
689            PaintFlavor::RadialGradient { colors, .. } => {
690                colors.mul_alpha(a);
691            }
692        }
693    }
694}