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}