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    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        // 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.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; // Paint nothing if gradient size is zero.
132        }
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; // Paint nothing if gradient size is zero.
174        }
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; // Paint nothing if gradient size is zero.
222        }
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; // Paint nothing if gradient size is zero.
248        }
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    /// <https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state>
273    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        // Step 1. Clear canvas's bitmap to transparent black.
279        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    /// Update image in WebRender
287    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    // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
303    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        // Because this comes from the rect on f32 precision, casting it down should be ok.
335        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    /// Push a clip to the draw target to respect the non-repeating bound (either x, y, or both)
351    /// of the given pattern.
352    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        // Clear operations are also unbounded.
365        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    /// It reads image data from the canvas
385    /// canvas_size: The size of the canvas we're reading from
386    /// read_rect: The area of the canvas we want to read from
387    #[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
419/// It writes an image to the destination target
420/// draw_target: the destination target where the image_data will be copied
421/// image_data: Pixel information of the image to be written. It takes RGBA8
422/// image_size: The size of the image to be written
423/// dest_rect: Area of the destination target where the pixels will be copied
424/// smoothing_enabled: It determines if smoothing is applied to the image result
425/// premultiply: Determines whenever the image data should be premultiplied or not
426fn 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    // From spec https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
441    // When scaling up, if the imageSmoothingEnabled attribute is set to true, the user agent should attempt
442    // to apply a smoothing algorithm to the image data when it is scaled.
443    // Otherwise, the image must be rendered using nearest-neighbor interpolation.
444    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}