canvas/
canvas_data.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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
14// Asserts on WR texture cache update for zero sized image with raw data.
15// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475
16const 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        // We round up the floating pixel values to draw the pixels
63        let source_rect = source_rect.ceil();
64        // It discards the extra pixels (if any) that won't be painted
65        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; // Paint nothing if gradient size is zero.
159        }
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; // Paint nothing if gradient size is zero.
201        }
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; // Paint nothing if gradient size is zero.
249        }
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; // Paint nothing if gradient size is zero.
275        }
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    /// <https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state>
300    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        // Step 1. Clear canvas's bitmap to transparent black.
306        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    /// Update image in WebRender
314    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    // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
334    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        // Because this comes from the rect on f32 precision, casting it down should be ok.
366        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    /// Push a clip to the draw target to respect the non-repeating bound (either x, y, or both)
382    /// of the given pattern.
383    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        // Clear operations are also unbounded.
396        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    /// It reads image data from the canvas
416    /// canvas_size: The size of the canvas we're reading from
417    /// read_rect: The area of the canvas we want to read from
418    #[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
452/// It writes an image to the destination target
453/// draw_target: the destination target where the image_data will be copied
454/// image_data: Pixel information of the image to be written. It takes RGBA8
455/// image_size: The size of the image to be written
456/// dest_rect: Area of the destination target where the pixels will be copied
457/// smoothing_enabled: It determines if smoothing is applied to the image result
458/// premultiply: Determines whenever the image data should be premultiplied or not
459fn 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    // From spec https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
474    // When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt
475    // to apply a smoothing algorithm to the image data when it is scaled.
476    // Otherwise, the image must be rendered using nearest-neighbor interpolation.
477    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}