1use base::Epoch;
6use canvas_traits::canvas::*;
7use compositing_traits::CrossProcessCompositorApi;
8use euclid::default::{Point2D, Rect, Size2D, Transform2D};
9use pixels::Snapshot;
10use webrender_api::ImageKey;
11
12use crate::backend::GenericDrawTarget;
13
14const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1);
17
18#[derive(Clone, Copy)]
19pub(crate) enum Filter {
20 Bilinear,
21 Nearest,
22}
23
24pub(crate) struct CanvasData<DrawTarget: GenericDrawTarget> {
25 drawtarget: DrawTarget,
26 compositor_api: CrossProcessCompositorApi,
27 image_key: ImageKey,
28}
29
30impl<DrawTarget: GenericDrawTarget> CanvasData<DrawTarget> {
31 pub(crate) fn new(
32 size: Size2D<u64>,
33 compositor_api: CrossProcessCompositorApi,
34 ) -> CanvasData<DrawTarget> {
35 let size = size.max(MIN_WR_IMAGE_SIZE);
36 let mut draw_target = DrawTarget::new(size.cast());
37 let image_key = compositor_api.generate_image_key_blocking().unwrap();
38 let (descriptor, data) = draw_target.image_descriptor_and_serializable_data();
39 compositor_api.add_image(image_key, descriptor, data);
40 CanvasData {
41 drawtarget: draw_target,
42 compositor_api,
43 image_key,
44 }
45 }
46
47 pub(crate) fn image_key(&self) -> ImageKey {
48 self.image_key
49 }
50
51 #[allow(clippy::too_many_arguments)]
52 pub(crate) fn draw_image(
53 &mut self,
54 snapshot: Snapshot,
55 dest_rect: Rect<f64>,
56 source_rect: Rect<f64>,
57 smoothing_enabled: bool,
58 shadow_options: ShadowOptions,
59 composition_options: CompositionOptions,
60 transform: Transform2D<f64>,
61 ) {
62 let source_rect = source_rect.ceil();
64 let snapshot = if Rect::from_size(snapshot.size().to_f64()).contains_rect(&source_rect) {
66 snapshot.get_rect(source_rect.to_u32())
67 } else {
68 snapshot
69 };
70
71 let writer = |draw_target: &mut DrawTarget, transform| {
72 write_image::<DrawTarget>(
73 draw_target,
74 snapshot,
75 dest_rect,
76 smoothing_enabled,
77 composition_options,
78 transform,
79 );
80 };
81
82 if shadow_options.need_to_draw_shadow() {
83 let rect = Rect::new(
84 Point2D::new(dest_rect.origin.x as f32, dest_rect.origin.y as f32),
85 Size2D::new(dest_rect.size.width as f32, dest_rect.size.height as f32),
86 );
87
88 self.draw_with_shadow(
89 &rect,
90 shadow_options,
91 composition_options,
92 transform,
93 writer,
94 );
95 } else {
96 writer(&mut self.drawtarget, transform);
97 }
98 }
99
100 pub(crate) fn fill_text(
101 &mut self,
102 text_bounds: Rect<f64>,
103 text_runs: Vec<TextRun>,
104 fill_or_stroke_style: FillOrStrokeStyle,
105 _shadow_options: ShadowOptions,
106 composition_options: CompositionOptions,
107 transform: Transform2D<f64>,
108 ) {
109 self.maybe_bound_shape_with_pattern(
110 fill_or_stroke_style,
111 composition_options,
112 &text_bounds,
113 transform,
114 |self_, style| {
115 self_
116 .drawtarget
117 .fill_text(text_runs, style, composition_options, transform);
118 },
119 );
120 }
121
122 pub(crate) fn fill_rect(
123 &mut self,
124 rect: &Rect<f32>,
125 style: FillOrStrokeStyle,
126 shadow_options: ShadowOptions,
127 composition_options: CompositionOptions,
128 transform: Transform2D<f64>,
129 ) {
130 if style.is_zero_size_gradient() {
131 return; }
133
134 if shadow_options.need_to_draw_shadow() {
135 self.draw_with_shadow(
136 rect,
137 shadow_options,
138 composition_options,
139 transform,
140 |new_draw_target, transform| {
141 new_draw_target.fill_rect(rect, style, composition_options, transform);
142 },
143 );
144 } else {
145 self.maybe_bound_shape_with_pattern(
146 style,
147 composition_options,
148 &rect.cast(),
149 transform,
150 |self_, style| {
151 self_
152 .drawtarget
153 .fill_rect(rect, style, composition_options, transform);
154 },
155 );
156 }
157 }
158
159 pub(crate) fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
160 self.drawtarget.clear_rect(rect, transform);
161 }
162
163 pub(crate) fn stroke_rect(
164 &mut self,
165 rect: &Rect<f32>,
166 style: FillOrStrokeStyle,
167 line_options: LineOptions,
168 shadow_options: ShadowOptions,
169 composition_options: CompositionOptions,
170 transform: Transform2D<f64>,
171 ) {
172 if style.is_zero_size_gradient() {
173 return; }
175
176 if shadow_options.need_to_draw_shadow() {
177 self.draw_with_shadow(
178 rect,
179 shadow_options,
180 composition_options,
181 transform,
182 |new_draw_target, transform| {
183 new_draw_target.stroke_rect(
184 rect,
185 style,
186 line_options,
187 composition_options,
188 transform,
189 );
190 },
191 );
192 } else {
193 self.maybe_bound_shape_with_pattern(
194 style,
195 composition_options,
196 &rect.cast(),
197 transform,
198 |self_, style| {
199 self_.drawtarget.stroke_rect(
200 rect,
201 style,
202 line_options,
203 composition_options,
204 transform,
205 );
206 },
207 )
208 }
209 }
210
211 pub(crate) fn fill_path(
212 &mut self,
213 path: &Path,
214 fill_rule: FillRule,
215 style: FillOrStrokeStyle,
216 _shadow_options: ShadowOptions,
217 composition_options: CompositionOptions,
218 transform: Transform2D<f64>,
219 ) {
220 if style.is_zero_size_gradient() {
221 return; }
223
224 self.maybe_bound_shape_with_pattern(
225 style,
226 composition_options,
227 &path.bounding_box(),
228 transform,
229 |self_, style| {
230 self_
231 .drawtarget
232 .fill(path, fill_rule, style, composition_options, transform)
233 },
234 )
235 }
236
237 pub(crate) fn stroke_path(
238 &mut self,
239 path: &Path,
240 style: FillOrStrokeStyle,
241 line_options: LineOptions,
242 _shadow_options: ShadowOptions,
243 composition_options: CompositionOptions,
244 transform: Transform2D<f64>,
245 ) {
246 if style.is_zero_size_gradient() {
247 return; }
249
250 self.maybe_bound_shape_with_pattern(
251 style,
252 composition_options,
253 &path.bounding_box(),
254 transform,
255 |self_, style| {
256 self_
257 .drawtarget
258 .stroke(path, style, line_options, composition_options, transform);
259 },
260 )
261 }
262
263 pub(crate) fn clip_path(
264 &mut self,
265 path: &Path,
266 fill_rule: FillRule,
267 transform: Transform2D<f64>,
268 ) {
269 self.drawtarget.push_clip(path, fill_rule, transform);
270 }
271
272 pub(crate) fn recreate(&mut self, size: Option<Size2D<u64>>) {
274 let size = size
275 .unwrap_or_else(|| self.drawtarget.get_size().to_u64())
276 .max(MIN_WR_IMAGE_SIZE);
277
278 self.drawtarget = self
280 .drawtarget
281 .create_similar_draw_target(&Size2D::new(size.width, size.height).cast());
282
283 self.update_image_rendering(None);
284 }
285
286 pub(crate) fn update_image_rendering(&mut self, canvas_epoch: Option<Epoch>) {
288 let (descriptor, data) = {
289 #[cfg(feature = "tracing")]
290 let _span = tracing::trace_span!(
291 "image_descriptor_and_serializable_data",
292 servo_profiling = true,
293 )
294 .entered();
295 self.drawtarget.image_descriptor_and_serializable_data()
296 };
297
298 self.compositor_api
299 .update_image(self.image_key, descriptor, data, canvas_epoch);
300 }
301
302 pub(crate) fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
304 assert_eq!(rect.size, snapshot.size());
305 let source_surface = self
306 .drawtarget
307 .create_source_surface_from_data(snapshot)
308 .unwrap();
309 self.drawtarget.copy_surface(
310 source_surface,
311 Rect::from_size(rect.size.to_i32()),
312 rect.origin.to_i32(),
313 );
314 }
315
316 fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> DrawTarget {
317 self.drawtarget.create_similar_draw_target(&Size2D::new(
318 source_rect.size.width as i32,
319 source_rect.size.height as i32,
320 ))
321 }
322
323 fn draw_with_shadow<F>(
324 &self,
325 rect: &Rect<f32>,
326 shadow_options: ShadowOptions,
327 composition_options: CompositionOptions,
328 transform: Transform2D<f64>,
329 draw_shadow_source: F,
330 ) where
331 F: FnOnce(&mut DrawTarget, Transform2D<f64>),
332 {
333 let shadow_src_rect = transform.outer_transformed_rect(&rect.cast());
334 let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect.cast());
336 let shadow_transform = transform
337 .then(&Transform2D::identity().pre_translate(-shadow_src_rect.origin.to_vector()));
338 draw_shadow_source(&mut new_draw_target, shadow_transform);
339 self.drawtarget.draw_surface_with_shadow(
340 new_draw_target.surface(),
341 &Point2D::new(
342 shadow_src_rect.origin.x as f32,
343 shadow_src_rect.origin.y as f32,
344 ),
345 shadow_options,
346 composition_options,
347 );
348 }
349
350 fn maybe_bound_shape_with_pattern<F>(
353 &mut self,
354 style: FillOrStrokeStyle,
355 composition_options: CompositionOptions,
356 path_bound_box: &Rect<f64>,
357 transform: Transform2D<f64>,
358 draw_shape: F,
359 ) where
360 F: FnOnce(&mut Self, FillOrStrokeStyle),
361 {
362 let x_bound = style.x_bound();
363 let y_bound = style.y_bound();
364 if matches!(
366 composition_options.composition_operation,
367 CompositionOrBlending::Composition(CompositionStyle::Clear)
368 ) || (x_bound.is_none() && y_bound.is_none())
369 {
370 draw_shape(self, style);
371 return;
372 }
373 let rect = Rect::from_size(Size2D::new(
374 x_bound.unwrap_or(path_bound_box.size.width.ceil() as u32),
375 y_bound.unwrap_or(path_bound_box.size.height.ceil() as u32),
376 ))
377 .cast();
378 let rect = transform.outer_transformed_rect(&rect);
379 self.drawtarget.push_clip_rect(&rect.cast());
380 draw_shape(self, style);
381 self.drawtarget.pop_clip();
382 }
383
384 #[servo_tracing::instrument(skip_all)]
388 pub(crate) fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
389 let canvas_size = self.drawtarget.get_size().cast();
390
391 if let Some(read_rect) = read_rect {
392 let canvas_rect = Rect::from_size(canvas_size);
393 if canvas_rect
394 .intersection(&read_rect)
395 .is_none_or(|rect| rect.is_empty())
396 {
397 Snapshot::empty()
398 } else {
399 self.drawtarget.snapshot().get_rect(read_rect)
400 }
401 } else {
402 self.drawtarget.snapshot()
403 }
404 }
405
406 pub(crate) fn pop_clips(&mut self, clips: usize) {
407 for _ in 0..clips {
408 self.drawtarget.pop_clip();
409 }
410 }
411}
412
413impl<D: GenericDrawTarget> Drop for CanvasData<D> {
414 fn drop(&mut self) {
415 self.compositor_api.delete_image(self.image_key);
416 }
417}
418
419fn write_image<DrawTarget: GenericDrawTarget>(
427 draw_target: &mut DrawTarget,
428 snapshot: Snapshot,
429 dest_rect: Rect<f64>,
430 smoothing_enabled: bool,
431 composition_options: CompositionOptions,
432 transform: Transform2D<f64>,
433) {
434 if snapshot.size().is_empty() {
435 return;
436 }
437
438 let image_rect = Rect::new(Point2D::zero(), snapshot.size().cast());
439
440 let filter = if smoothing_enabled {
445 Filter::Bilinear
446 } else {
447 Filter::Nearest
448 };
449
450 let source_surface = draw_target
451 .create_source_surface_from_data(snapshot)
452 .unwrap();
453
454 draw_target.draw_surface(
455 source_surface,
456 dest_rect,
457 image_rect,
458 filter,
459 composition_options,
460 transform,
461 );
462}
463
464pub(crate) trait RectToi32 {
465 fn ceil(&self) -> Rect<f64>;
466}
467
468impl RectToi32 for Rect<f64> {
469 fn ceil(&self) -> Rect<f64> {
470 Rect::new(
471 Point2D::new(self.origin.x.ceil(), self.origin.y.ceil()),
472 Size2D::new(self.size.width.ceil(), self.size.height.ceil()),
473 )
474 }
475}