1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Integration of the experimental Vello API preview into Vello CPU.
//!
//! <div class="warning">
//!
//! Vello API is currently in an experimental phase, released only as a preview, and has no stability guarantees.
//! See [its documentation](vello_api) for more details.
//!
//! </div>
use vello_api::{
PaintScene, Scene,
exact::ExactPathElements,
peniko::Style,
scene::{RenderCommand, extract_integer_translation},
texture::TextureId,
};
use vello_common::{
kurbo::{self, Affine, BezPath},
paint::{ImageId, ImageSource},
peniko::{BlendMode, Brush, Color, Fill, ImageBrush},
};
use crate::RenderContext;
/// An adapter to implement [`PaintScene`] for Vello CPU's ['`Scene`'][RenderContext] type.
///
/// This type exists to avoid breaking the other APIs in this crate whilst we land/stabilise Vello API.
#[derive(Debug)]
pub struct CPUScenePainter {
/// The underlying render context. This is public on an interim basis, whilst we decide how
/// Vello API will develop further.
pub render_context: RenderContext,
}
impl PaintScene for CPUScenePainter {
fn append(
&mut self,
mut scene_transform: Affine,
Scene {
// Make sure we consider all the fields of Scene by destructuring
paths: input_paths,
commands: input_commands,
hinted: input_hinted,
}: &Scene,
) -> Result<(), ()> {
if *input_hinted {
if let Some((dx, dy)) = extract_integer_translation(scene_transform) {
// Update the transform to be a pure integer translation.
// This is valid as the scene is hinted, so we know it won't be later scaled.
// As such, a displacement of up to 1/100 of a pixel is inperceptible, but it
// makes our reasoning about this easier.
scene_transform = Affine::translate((dx, dy));
} else {
// Translation not hinting compatible.
return Err(());
}
}
for command in input_commands {
match command {
RenderCommand::DrawPath(affine, path_id) => {
self.render_context.set_transform(scene_transform * *affine);
let path = &input_paths.meta[usize::try_from(path_id.0).unwrap()];
let path_end = &input_paths
.meta
.get(usize::try_from(path_id.0).unwrap() + 1)
.map_or(input_paths.elements.len(), |it| it.start_index);
let segments = &input_paths.elements[path.start_index..*path_end];
// Obviously, ideally we'd not be allocating here. This is forced by the current public API of Vello CPU.
let bezpath = BezPath::from_iter(segments.iter().cloned());
match &path.operation {
Style::Stroke(stroke) => {
self.render_context.set_stroke(stroke.clone());
self.render_context.stroke_path(&bezpath);
}
Style::Fill(fill) => {
self.render_context.set_fill_rule(*fill);
self.render_context.fill_path(&bezpath);
}
}
}
RenderCommand::PushLayer(push_layer_command) => {
self.render_context
.set_transform(push_layer_command.clip_transform);
let clip_path = if let Some(path_id) = push_layer_command.clip_path {
let path = &input_paths.meta[usize::try_from(path_id.0).unwrap()];
// TODO: Also correctly support the case where the meta has a `Style::Stroke`
let path_end = &input_paths
.meta
.get(usize::try_from(path_id.0).unwrap() + 1)
.map_or(input_paths.elements.len(), |it| it.start_index);
let segments = &input_paths.elements[path.start_index..*path_end];
// TODO: Obviously, ideally we'd not be allocating here. This is forced by the current public API of Vello CPU.
let bezpath = BezPath::from_iter(segments.iter().cloned());
Some(bezpath)
} else {
None
};
self.render_context.push_layer(
clip_path.as_ref(),
push_layer_command.blend_mode,
push_layer_command.opacity,
None,
None,
);
}
RenderCommand::PopLayer => self.render_context.pop_layer(),
RenderCommand::SetPaint(paint_transform, brush) => {
self.render_context.set_paint_transform(*paint_transform);
let brush = match brush {
Brush::Solid(alpha_color) => Brush::Solid(*alpha_color),
Brush::Gradient(gradient) => Brush::Gradient(gradient.clone()),
Brush::Image(brush) => {
let image_index =
brush.image.to_raw().try_into().expect("Handle this.");
Brush::Image(ImageBrush {
image: ImageSource::opaque_id(ImageId::new(image_index)),
sampler: brush.sampler,
})
}
};
self.render_context.set_paint(brush);
}
RenderCommand::BlurredRoundedRectPaint(_) => {
unimplemented!(
"Vello CPU doesn't expose drawing a blurred rounded rectangle in custom shapes yet."
)
}
}
}
Ok(())
}
fn fill_path(&mut self, transform: Affine, fill_rule: Fill, path: &impl ExactPathElements) {
self.render_context.set_transform(transform);
self.render_context.set_fill_rule(fill_rule);
// However, using `to_path` avoids allocation in some cases.
// TODO: Tweak inner API to accept an `ExactPathElements` (or at least, the resultant iterator)
// That would avoid the superfluous allocation here.
self.render_context
.fill_path(&path.exact_path_elements().collect());
}
fn stroke_path(
&mut self,
transform: Affine,
stroke_params: &kurbo::Stroke,
path: &impl ExactPathElements,
) {
self.render_context.set_transform(transform);
self.render_context.set_stroke(stroke_params.clone());
// TODO: As in `fill_path`
self.render_context
.stroke_path(&path.exact_path_elements().collect());
}
fn set_brush(
&mut self,
brush: impl Into<Brush<ImageBrush<TextureId>>>,
paint_transform: Affine,
) {
self.render_context.set_paint_transform(paint_transform);
let brush = match brush.into() {
Brush::Solid(alpha_color) => Brush::Solid(alpha_color),
Brush::Gradient(gradient) => Brush::Gradient(gradient),
Brush::Image(brush) => {
// TODO: Make this read more easily.
let image_index = brush.image.to_raw().try_into().expect("Handle this.");
Brush::Image(ImageBrush {
image: ImageSource::opaque_id(ImageId::new(image_index)),
sampler: brush.sampler,
})
}
};
self.render_context.set_paint(brush);
}
fn set_blurred_rounded_rect_brush(
&mut self,
_paint_transform: Affine,
_color: Color,
_rect: &kurbo::Rect,
_radius: f32,
_std_dev: f32,
) {
// This is "trivially" fixable.
unimplemented!(
"Vello CPU doesn't expose drawing a blurred rounded rectangle in custom shapes yet."
)
}
fn fill_blurred_rounded_rect(
&mut self,
transform: Affine,
color: Color,
rect: &kurbo::Rect,
radius: f32,
std_dev: f32,
) {
self.render_context.set_paint(color);
self.render_context.set_transform(transform);
self.render_context
.fill_blurred_rounded_rect(rect, radius, std_dev);
}
fn push_layer(
&mut self,
clip_transform: Affine,
clip_path: Option<&impl ExactPathElements>,
blend_mode: Option<BlendMode>,
opacity: Option<f32>,
// mask: Option<Mask>,
) {
// We set the fill rule to nonzero for the clip path as a reasonable default.
// We should make it user provided in the future
self.render_context.set_fill_rule(Fill::NonZero);
self.render_context.set_transform(clip_transform);
self.render_context.push_layer(
// TODO: As in `fill_path`
clip_path
.map(|it| it.exact_path_elements().collect())
.as_ref(),
blend_mode,
opacity,
None,
None,
);
}
fn push_clip_layer(&mut self, clip_transform: Affine, path: &impl ExactPathElements) {
self.render_context.set_fill_rule(Fill::NonZero);
self.render_context.set_transform(clip_transform);
// TODO: As in `fill_path`
self.render_context
.push_clip_layer(&path.exact_path_elements().collect());
}
fn pop_layer(&mut self) {
self.render_context.pop_layer();
}
}