masking/masking.rs
1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Using masks with Vello CPU.
5
6use vello_cpu::color::palette::css::{BLUE, RED, WHITE};
7use vello_cpu::kurbo::Rect;
8use vello_cpu::{Mask, Pixmap, RenderContext, Resources};
9
10const SIZE: u16 = 200;
11
12fn main() {
13 // Vello CPU supports applying luminance and alpha masks to your drawings.
14
15 // First, we need to create our actual mask. There are multiple ways how
16 // you can get to it, in our case we are going to draw our own custom mask.
17 let mask = {
18 // In this case, we are drawing the mask ourselves. Note that the
19 // dimensions of the final mask need to match the dimensions of our
20 // original render context!
21 let mut mask_ctx = RenderContext::new(SIZE, SIZE);
22 let mut mask_resources = Resources::new();
23 let mut pixmap = Pixmap::new(SIZE, SIZE);
24
25 mask_ctx.set_paint(RED);
26 mask_ctx.fill_rect(&Rect::new(30.0, 30.0, 170.0, 170.0));
27 mask_ctx.flush();
28 mask_ctx.render(&mut pixmap, &mut mask_resources);
29
30 Mask::new_luminance(&pixmap)
31 };
32
33 // Create the main render context.
34 let mut ctx = RenderContext::new(SIZE, SIZE);
35
36 // Similarly to clip paths (see the clipping example), there are two
37 // different ways of applying them:
38 // The first method is by creating a new isolated layer where the mask
39 // will be applied once the whole layer has been drawn and is composited
40 // into the backdrop. The second method is by setting the mask in the
41 // render context, in which case the mask will be applied to each shape
42 // directly before being drawn. Which method you should use once again
43 // depends on the imaging model you are reflecting.
44
45 // Method 1: Non-isolated masking via `set_mask`.
46 {
47 ctx.set_paint(WHITE);
48 ctx.fill_rect(&Rect::new(0.0, 0.0, SIZE as f64, SIZE as f64));
49
50 // Once the mask is set, the mask will be applied to every path we
51 // are drawing individually before compositing it into the background.
52 ctx.set_mask(mask.clone());
53 // We first apply the mask to the blue rectangle and then composite it.
54 ctx.set_paint(BLUE);
55 ctx.fill_rect(&Rect::new(20.0, 20.0, 130.0, 130.0));
56 // Now, we yet again first apply the mask to the red rectangle only and
57 // then composite the result.
58 ctx.set_paint(RED);
59 ctx.fill_rect(&Rect::new(70.0, 70.0, 180.0, 180.0));
60 // Use this method if you want to reset the mask currently in place.
61 ctx.reset_mask();
62
63 ctx.flush();
64 save_pixmap(&ctx, "example_masking1");
65 }
66
67 ctx.reset();
68 // Method 2: Isolated masking via `push_mask_layer`.
69 {
70 ctx.set_paint(WHITE);
71 ctx.fill_rect(&Rect::new(0.0, 0.0, SIZE as f64, SIZE as f64));
72
73 // Using this method, we first push a new isolated layer. Apart from that,
74 // nothing happens so far.
75 ctx.push_mask_layer(mask);
76 // Here, the blue rectangle will be drawn first. Then, the red one is drawn
77 // and subsequently composited on top of the blue one,
78 // without any special handling.
79 ctx.set_paint(BLUE);
80 ctx.fill_rect(&Rect::new(20.0, 20.0, 130.0, 130.0));
81 ctx.set_paint(RED);
82 ctx.fill_rect(&Rect::new(70.0, 70.0, 180.0, 180.0));
83 // Now, the whole layer is taken, the mask is applied to all of it
84 // and then composited into the background.
85 ctx.pop_layer();
86
87 ctx.flush();
88 save_pixmap(&ctx, "example_masking2");
89 }
90
91 // As can be seen, the visual result of the two methods can be different!
92}
93
94fn save_pixmap(ctx: &RenderContext, filename: &str) {
95 let mut resources = Resources::new();
96 let mut pixmap = Pixmap::new(ctx.width(), ctx.height());
97 ctx.render(&mut pixmap, &mut resources);
98 let png = pixmap.into_png().unwrap();
99 std::fs::write(format!("{filename}.png"), png).unwrap();
100}