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 draw_target: DrawTarget,
26 compositor_api: CrossProcessCompositorApi,
27 image_key: Option<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 CanvasData {
36 draw_target: DrawTarget::new(size.max(MIN_WR_IMAGE_SIZE).cast()),
37 compositor_api,
38 image_key: None,
39 }
40 }
41
42 pub(crate) fn set_image_key(&mut self, image_key: ImageKey) {
43 let (descriptor, data) = self.draw_target.image_descriptor_and_serializable_data();
44 self.compositor_api.add_image(image_key, descriptor, data);
45
46 if let Some(old_image_key) = self.image_key.replace(image_key) {
47 self.compositor_api.delete_image(old_image_key);
48 }
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.draw_target, 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 .draw_target
117 .fill_text(text_runs, style, composition_options, transform);
118 },
119 );
120 }
121
122 pub(crate) fn stroke_text(
123 &mut self,
124 text_bounds: Rect<f64>,
125 text_runs: Vec<TextRun>,
126 fill_or_stroke_style: FillOrStrokeStyle,
127 line_options: LineOptions,
128 _shadow_options: ShadowOptions,
129 composition_options: CompositionOptions,
130 transform: Transform2D<f64>,
131 ) {
132 self.maybe_bound_shape_with_pattern(
133 fill_or_stroke_style,
134 composition_options,
135 &text_bounds,
136 transform,
137 |self_, style| {
138 self_.draw_target.stroke_text(
139 text_runs,
140 style,
141 line_options,
142 composition_options,
143 transform,
144 );
145 },
146 );
147 }
148
149 pub(crate) fn fill_rect(
150 &mut self,
151 rect: &Rect<f32>,
152 style: FillOrStrokeStyle,
153 shadow_options: ShadowOptions,
154 composition_options: CompositionOptions,
155 transform: Transform2D<f64>,
156 ) {
157 if style.is_zero_size_gradient() {
158 return; }
160
161 if shadow_options.need_to_draw_shadow() {
162 self.draw_with_shadow(
163 rect,
164 shadow_options,
165 composition_options,
166 transform,
167 |new_draw_target, transform| {
168 new_draw_target.fill_rect(rect, style, composition_options, transform);
169 },
170 );
171 } else {
172 self.maybe_bound_shape_with_pattern(
173 style,
174 composition_options,
175 &rect.cast(),
176 transform,
177 |self_, style| {
178 self_
179 .draw_target
180 .fill_rect(rect, style, composition_options, transform);
181 },
182 );
183 }
184 }
185
186 pub(crate) fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f64>) {
187 self.draw_target.clear_rect(rect, transform);
188 }
189
190 pub(crate) fn stroke_rect(
191 &mut self,
192 rect: &Rect<f32>,
193 style: FillOrStrokeStyle,
194 line_options: LineOptions,
195 shadow_options: ShadowOptions,
196 composition_options: CompositionOptions,
197 transform: Transform2D<f64>,
198 ) {
199 if style.is_zero_size_gradient() {
200 return; }
202
203 if shadow_options.need_to_draw_shadow() {
204 self.draw_with_shadow(
205 rect,
206 shadow_options,
207 composition_options,
208 transform,
209 |new_draw_target, transform| {
210 new_draw_target.stroke_rect(
211 rect,
212 style,
213 line_options,
214 composition_options,
215 transform,
216 );
217 },
218 );
219 } else {
220 self.maybe_bound_shape_with_pattern(
221 style,
222 composition_options,
223 &rect.cast(),
224 transform,
225 |self_, style| {
226 self_.draw_target.stroke_rect(
227 rect,
228 style,
229 line_options,
230 composition_options,
231 transform,
232 );
233 },
234 )
235 }
236 }
237
238 pub(crate) fn fill_path(
239 &mut self,
240 path: &Path,
241 fill_rule: FillRule,
242 style: FillOrStrokeStyle,
243 _shadow_options: ShadowOptions,
244 composition_options: CompositionOptions,
245 transform: Transform2D<f64>,
246 ) {
247 if style.is_zero_size_gradient() {
248 return; }
250
251 self.maybe_bound_shape_with_pattern(
252 style,
253 composition_options,
254 &path.bounding_box(),
255 transform,
256 |self_, style| {
257 self_
258 .draw_target
259 .fill(path, fill_rule, style, composition_options, transform)
260 },
261 )
262 }
263
264 pub(crate) fn stroke_path(
265 &mut self,
266 path: &Path,
267 style: FillOrStrokeStyle,
268 line_options: LineOptions,
269 _shadow_options: ShadowOptions,
270 composition_options: CompositionOptions,
271 transform: Transform2D<f64>,
272 ) {
273 if style.is_zero_size_gradient() {
274 return; }
276
277 self.maybe_bound_shape_with_pattern(
278 style,
279 composition_options,
280 &path.bounding_box(),
281 transform,
282 |self_, style| {
283 self_
284 .draw_target
285 .stroke(path, style, line_options, composition_options, transform);
286 },
287 )
288 }
289
290 pub(crate) fn clip_path(
291 &mut self,
292 path: &Path,
293 fill_rule: FillRule,
294 transform: Transform2D<f64>,
295 ) {
296 self.draw_target.push_clip(path, fill_rule, transform);
297 }
298
299 pub(crate) fn recreate(&mut self, size: Option<Size2D<u64>>) {
301 let size = size
302 .unwrap_or_else(|| self.draw_target.get_size().to_u64())
303 .max(MIN_WR_IMAGE_SIZE);
304
305 self.draw_target = self
307 .draw_target
308 .create_similar_draw_target(&Size2D::new(size.width, size.height).cast());
309
310 self.update_image_rendering(None);
311 }
312
313 pub(crate) fn update_image_rendering(&mut self, canvas_epoch: Option<Epoch>) {
315 let Some(image_key) = self.image_key else {
316 return;
317 };
318
319 let (descriptor, data) = {
320 #[cfg(feature = "tracing")]
321 let _span = tracing::trace_span!(
322 "image_descriptor_and_serializable_data",
323 servo_profiling = true,
324 )
325 .entered();
326 self.draw_target.image_descriptor_and_serializable_data()
327 };
328
329 self.compositor_api
330 .update_image(image_key, descriptor, data, canvas_epoch);
331 }
332
333 pub(crate) fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
335 assert_eq!(rect.size, snapshot.size());
336 let source_surface = self
337 .draw_target
338 .create_source_surface_from_data(snapshot)
339 .unwrap();
340 self.draw_target.copy_surface(
341 source_surface,
342 Rect::from_size(rect.size.to_i32()),
343 rect.origin.to_i32(),
344 );
345 }
346
347 fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> DrawTarget {
348 self.draw_target.create_similar_draw_target(&Size2D::new(
349 source_rect.size.width as i32,
350 source_rect.size.height as i32,
351 ))
352 }
353
354 fn draw_with_shadow<F>(
355 &self,
356 rect: &Rect<f32>,
357 shadow_options: ShadowOptions,
358 composition_options: CompositionOptions,
359 transform: Transform2D<f64>,
360 draw_shadow_source: F,
361 ) where
362 F: FnOnce(&mut DrawTarget, Transform2D<f64>),
363 {
364 let shadow_src_rect = transform.outer_transformed_rect(&rect.cast());
365 let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect.cast());
367 let shadow_transform = transform
368 .then(&Transform2D::identity().pre_translate(-shadow_src_rect.origin.to_vector()));
369 draw_shadow_source(&mut new_draw_target, shadow_transform);
370 self.draw_target.draw_surface_with_shadow(
371 new_draw_target.surface(),
372 &Point2D::new(
373 shadow_src_rect.origin.x as f32,
374 shadow_src_rect.origin.y as f32,
375 ),
376 shadow_options,
377 composition_options,
378 );
379 }
380
381 fn maybe_bound_shape_with_pattern<F>(
384 &mut self,
385 style: FillOrStrokeStyle,
386 composition_options: CompositionOptions,
387 path_bound_box: &Rect<f64>,
388 transform: Transform2D<f64>,
389 draw_shape: F,
390 ) where
391 F: FnOnce(&mut Self, FillOrStrokeStyle),
392 {
393 let x_bound = style.x_bound();
394 let y_bound = style.y_bound();
395 if matches!(
397 composition_options.composition_operation,
398 CompositionOrBlending::Composition(CompositionStyle::Clear)
399 ) || (x_bound.is_none() && y_bound.is_none())
400 {
401 draw_shape(self, style);
402 return;
403 }
404 let rect = Rect::from_size(Size2D::new(
405 x_bound.unwrap_or(path_bound_box.size.width.ceil() as u32),
406 y_bound.unwrap_or(path_bound_box.size.height.ceil() as u32),
407 ))
408 .cast();
409 let rect = transform.outer_transformed_rect(&rect);
410 self.draw_target.push_clip_rect(&rect.cast());
411 draw_shape(self, style);
412 self.draw_target.pop_clip();
413 }
414
415 #[servo_tracing::instrument(skip_all)]
419 pub(crate) fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
420 let canvas_size = self.draw_target.get_size().cast();
421
422 if let Some(read_rect) = read_rect {
423 let canvas_rect = Rect::from_size(canvas_size);
424 if canvas_rect
425 .intersection(&read_rect)
426 .is_none_or(|rect| rect.is_empty())
427 {
428 Snapshot::empty()
429 } else {
430 self.draw_target.snapshot().get_rect(read_rect)
431 }
432 } else {
433 self.draw_target.snapshot()
434 }
435 }
436
437 pub(crate) fn pop_clips(&mut self, clips: usize) {
438 for _ in 0..clips {
439 self.draw_target.pop_clip();
440 }
441 }
442}
443
444impl<D: GenericDrawTarget> Drop for CanvasData<D> {
445 fn drop(&mut self) {
446 if let Some(image_key) = self.image_key {
447 self.compositor_api.delete_image(image_key);
448 }
449 }
450}
451
452fn write_image<DrawTarget: GenericDrawTarget>(
460 draw_target: &mut DrawTarget,
461 snapshot: Snapshot,
462 dest_rect: Rect<f64>,
463 smoothing_enabled: bool,
464 composition_options: CompositionOptions,
465 transform: Transform2D<f64>,
466) {
467 if snapshot.size().is_empty() {
468 return;
469 }
470
471 let image_rect = Rect::new(Point2D::zero(), snapshot.size().cast());
472
473 let filter = if smoothing_enabled {
478 Filter::Bilinear
479 } else {
480 Filter::Nearest
481 };
482
483 let source_surface = draw_target
484 .create_source_surface_from_data(snapshot)
485 .unwrap();
486
487 draw_target.draw_surface(
488 source_surface,
489 dest_rect,
490 image_rect,
491 filter,
492 composition_options,
493 transform,
494 );
495}
496
497pub(crate) trait RectToi32 {
498 fn ceil(&self) -> Rect<f64>;
499}
500
501impl RectToi32 for Rect<f64> {
502 fn ceil(&self) -> Rect<f64> {
503 Rect::new(
504 Point2D::new(self.origin.x.ceil(), self.origin.y.ceil()),
505 Size2D::new(self.size.width.ceil(), self.size.height.ceil()),
506 )
507 }
508}