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 let _span =
321 profile_traits::trace_span!("image_descriptor_and_serializable_data",).entered();
322 self.draw_target.image_descriptor_and_serializable_data()
323 };
324
325 self.compositor_api
326 .update_image(image_key, descriptor, data, canvas_epoch);
327 }
328
329 pub(crate) fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
331 assert_eq!(rect.size, snapshot.size());
332 let source_surface = self
333 .draw_target
334 .create_source_surface_from_data(snapshot)
335 .unwrap();
336 self.draw_target.copy_surface(
337 source_surface,
338 Rect::from_size(rect.size.to_i32()),
339 rect.origin.to_i32(),
340 );
341 }
342
343 fn create_draw_target_for_shadow(&self, source_rect: &Rect<f32>) -> DrawTarget {
344 self.draw_target.create_similar_draw_target(&Size2D::new(
345 source_rect.size.width as i32,
346 source_rect.size.height as i32,
347 ))
348 }
349
350 fn draw_with_shadow<F>(
351 &self,
352 rect: &Rect<f32>,
353 shadow_options: ShadowOptions,
354 composition_options: CompositionOptions,
355 transform: Transform2D<f64>,
356 draw_shadow_source: F,
357 ) where
358 F: FnOnce(&mut DrawTarget, Transform2D<f64>),
359 {
360 let shadow_src_rect = transform.outer_transformed_rect(&rect.cast());
361 let mut new_draw_target = self.create_draw_target_for_shadow(&shadow_src_rect.cast());
363 let shadow_transform = transform
364 .then(&Transform2D::identity().pre_translate(-shadow_src_rect.origin.to_vector()));
365 draw_shadow_source(&mut new_draw_target, shadow_transform);
366 self.draw_target.draw_surface_with_shadow(
367 new_draw_target.surface(),
368 &Point2D::new(
369 shadow_src_rect.origin.x as f32,
370 shadow_src_rect.origin.y as f32,
371 ),
372 shadow_options,
373 composition_options,
374 );
375 }
376
377 fn maybe_bound_shape_with_pattern<F>(
380 &mut self,
381 style: FillOrStrokeStyle,
382 composition_options: CompositionOptions,
383 path_bound_box: &Rect<f64>,
384 transform: Transform2D<f64>,
385 draw_shape: F,
386 ) where
387 F: FnOnce(&mut Self, FillOrStrokeStyle),
388 {
389 let x_bound = style.x_bound();
390 let y_bound = style.y_bound();
391 if matches!(
393 composition_options.composition_operation,
394 CompositionOrBlending::Composition(CompositionStyle::Clear)
395 ) || (x_bound.is_none() && y_bound.is_none())
396 {
397 draw_shape(self, style);
398 return;
399 }
400 let rect = Rect::from_size(Size2D::new(
401 x_bound.unwrap_or(path_bound_box.size.width.ceil() as u32),
402 y_bound.unwrap_or(path_bound_box.size.height.ceil() as u32),
403 ))
404 .cast();
405 let rect = transform.outer_transformed_rect(&rect);
406 self.draw_target.push_clip_rect(&rect.cast());
407 draw_shape(self, style);
408 self.draw_target.pop_clip();
409 }
410
411 #[servo_tracing::instrument(skip_all)]
415 pub(crate) fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
416 let canvas_size = self.draw_target.get_size().cast();
417
418 if let Some(read_rect) = read_rect {
419 let canvas_rect = Rect::from_size(canvas_size);
420 if canvas_rect
421 .intersection(&read_rect)
422 .is_none_or(|rect| rect.is_empty())
423 {
424 Snapshot::empty()
425 } else {
426 self.draw_target.snapshot().get_rect(read_rect)
427 }
428 } else {
429 self.draw_target.snapshot()
430 }
431 }
432
433 pub(crate) fn pop_clips(&mut self, clips: usize) {
434 for _ in 0..clips {
435 self.draw_target.pop_clip();
436 }
437 }
438}
439
440impl<D: GenericDrawTarget> Drop for CanvasData<D> {
441 fn drop(&mut self) {
442 if let Some(image_key) = self.image_key {
443 self.compositor_api.delete_image(image_key);
444 }
445 }
446}
447
448fn write_image<DrawTarget: GenericDrawTarget>(
456 draw_target: &mut DrawTarget,
457 snapshot: Snapshot,
458 dest_rect: Rect<f64>,
459 smoothing_enabled: bool,
460 composition_options: CompositionOptions,
461 transform: Transform2D<f64>,
462) {
463 if snapshot.size().is_empty() {
464 return;
465 }
466
467 let image_rect = Rect::new(Point2D::zero(), snapshot.size().cast());
468
469 let filter = if smoothing_enabled {
474 Filter::Bilinear
475 } else {
476 Filter::Nearest
477 };
478
479 let source_surface = draw_target
480 .create_source_surface_from_data(snapshot)
481 .unwrap();
482
483 draw_target.draw_surface(
484 source_surface,
485 dest_rect,
486 image_rect,
487 filter,
488 composition_options,
489 transform,
490 );
491}
492
493pub(crate) trait RectToi32 {
494 fn ceil(&self) -> Rect<f64>;
495}
496
497impl RectToi32 for Rect<f64> {
498 fn ceil(&self) -> Rect<f64> {
499 Rect::new(
500 Point2D::new(self.origin.x.ceil(), self.origin.y.ceil()),
501 Size2D::new(self.size.width.ceil(), self.size.height.ceil()),
502 )
503 }
504}