script/dom/canvas/
imagebitmaprenderingcontext.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 std::cell::Cell;
6
7use dom_struct::dom_struct;
8use euclid::default::Size2D;
9use paint_api::SerializableImageData;
10use pixels::Snapshot;
11use servo_base::Epoch;
12use webrender_api::units::DeviceIntSize;
13use webrender_api::{ImageDescriptor, ImageFormat, ImageKey};
14
15use crate::canvas_context::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods;
18use crate::dom::bindings::codegen::Bindings::ImageBitmapRenderingContextBinding::ImageBitmapRenderingContextMethods;
19use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::globalscope::GlobalScope;
24use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
25use crate::dom::imagebitmap::ImageBitmap;
26use crate::dom::node::node::NodeTraits;
27use crate::script_runtime::CanGc;
28
29/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext>
30#[dom_struct]
31pub(crate) struct ImageBitmapRenderingContext {
32    reflector_: Reflector,
33    /// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
34    canvas: HTMLCanvasElementOrOffscreenCanvas,
35    /// Represents both the [output bitmap] and the [bitmap mode] of the context.
36    /// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-output-bitmap>
37    /// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-bitmap-mode>
38    #[no_trace]
39    bitmap: DomRefCell<Option<Snapshot>>,
40    origin_clean: Cell<bool>,
41    #[no_trace]
42    image_key: Cell<Option<ImageKey>>,
43    /// Whether the image has been added to WebRender (first render uses add_image,
44    /// subsequent renders use update_image).
45    image_added: Cell<bool>,
46}
47
48impl ImageBitmapRenderingContext {
49    /// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext-creation-algorithm>
50    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
51    fn new_inherited(canvas: HTMLCanvasElementOrOffscreenCanvas) -> ImageBitmapRenderingContext {
52        ImageBitmapRenderingContext {
53            reflector_: Reflector::new(),
54            canvas,
55            bitmap: DomRefCell::new(None),
56            origin_clean: Cell::new(true),
57            image_key: Cell::new(None),
58            image_added: Cell::new(false),
59        }
60    }
61
62    pub(crate) fn new(
63        global: &GlobalScope,
64        canvas: &RootedHTMLCanvasElementOrOffscreenCanvas,
65        can_gc: CanGc,
66    ) -> DomRoot<ImageBitmapRenderingContext> {
67        reflect_dom_object(
68            Box::new(ImageBitmapRenderingContext::new_inherited(
69                HTMLCanvasElementOrOffscreenCanvas::from(canvas),
70            )),
71            global,
72            can_gc,
73        )
74    }
75
76    pub(crate) fn set_image_key(&self, image_key: ImageKey) {
77        self.image_key.set(Some(image_key));
78    }
79
80    pub(crate) fn update_rendering(&self, epoch: Epoch) -> bool {
81        let Some(image_key) = self.image_key.get() else {
82            return false;
83        };
84
85        let Some(snapshot) = self.bitmap.borrow().as_ref().cloned() else {
86            return false;
87        };
88
89        let size = snapshot.size();
90        let format = match snapshot.format() {
91            pixels::SnapshotPixelFormat::RGBA => ImageFormat::RGBA8,
92            pixels::SnapshotPixelFormat::BGRA => ImageFormat::BGRA8,
93        };
94        let shared = snapshot.to_shared();
95        let descriptor = ImageDescriptor {
96            format,
97            size: DeviceIntSize::new(size.width as i32, size.height as i32),
98            stride: None,
99            offset: 0,
100            flags: webrender_api::ImageDescriptorFlags::empty(),
101        };
102        let data = SerializableImageData::Raw(shared.shared_memory());
103
104        // Here we get the paint API from the canvas element's window.
105        if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) = self.canvas {
106            let canvas: &HTMLCanvasElement = canvas;
107            let doc = canvas.owner_document();
108            let paint_api = doc.window().paint_api();
109            if self.image_added.get() {
110                paint_api.update_image(image_key, descriptor, data, Some(epoch));
111            } else {
112                paint_api.add_image(image_key, descriptor, data, false);
113                self.image_added.set(true);
114            }
115        }
116        false
117    }
118
119    /// <https://html.spec.whatwg.org/multipage/#set-an-imagebitmaprenderingcontext's-output-bitmap>
120    fn set_bitmap(&self, image_bitmap: Option<&ImageBitmap>) {
121        match image_bitmap {
122            Some(image_bitmap) => {
123                // Step 2.1. Set context's bitmap mode to valid.
124                // Step 2.2. Set context's output bitmap to refer to the same
125                // underlying bitmap data as bitmap, without making a copy.
126                *self.bitmap.borrow_mut() = image_bitmap.bitmap_data().clone();
127
128                // The origin-clean flag of bitmap is included in the bitmap
129                // data to be referenced by context's output bitmap.
130                self.origin_clean.set(image_bitmap.origin_is_clean());
131            },
132            None => {
133                // Step 1.1. Set context's bitmap mode to blank.
134                // Step 1.2. Let canvas be the canvas element to which context is bound.
135                // Step 1.3. Set context's output bitmap to be transparent black
136                // with a natural width equal to the numeric value of canvas's
137                // width attribute and a natural height equal to the numeric
138                // value of canvas's height attribute, those values being
139                // interpreted in CSS pixels.
140                *self.bitmap.borrow_mut() = None;
141
142                // Step 1.4. Set the output bitmap's origin-clean flag to true.
143                self.origin_clean.set(true);
144            },
145        }
146    }
147}
148
149impl CanvasContext for ImageBitmapRenderingContext {
150    type ID = ();
151
152    fn context_id(&self) -> Self::ID {}
153
154    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
155        Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
156    }
157
158    /// <https://html.spec.whatwg.org/multipage/#the-canvas-element:concept-canvas-bitmaprenderer>
159    fn resize(&self) {
160        // The absence of the bitmap is the context's blank bitmap mode so the
161        // steps to set output bitmap could be omitted.
162    }
163
164    fn reset_bitmap(&self) {
165        // The newly created bitmap should be of the same dimensions as the
166        // previous bitmap if the context's bitmap mode is valid.
167        if self.bitmap.borrow().is_none() {
168            return;
169        }
170
171        let size = self.bitmap.borrow().as_ref().unwrap().size();
172        *self.bitmap.borrow_mut() = Some(Snapshot::cleared(size));
173    }
174
175    fn get_image_data(&self) -> Option<Snapshot> {
176        match self.bitmap.borrow().as_ref() {
177            Some(bitmap) => Some(bitmap.clone()),
178            None => {
179                let size = self.canvas.size();
180                if size.is_empty() ||
181                    pixels::compute_rgba8_byte_length_if_within_limit(
182                        size.width as usize,
183                        size.height as usize,
184                    )
185                    .is_none()
186                {
187                    None
188                } else {
189                    Some(Snapshot::cleared(size))
190                }
191            },
192        }
193    }
194
195    fn origin_is_clean(&self) -> bool {
196        self.origin_clean.get()
197    }
198
199    fn size(&self) -> Size2D<u32> {
200        self.bitmap
201            .borrow()
202            .as_ref()
203            .map_or_else(|| self.canvas.size(), |bitmap| bitmap.size())
204    }
205
206    fn mark_as_dirty(&self) {
207        self.canvas.mark_as_dirty();
208    }
209}
210
211impl ImageBitmapRenderingContextMethods<crate::DomTypeHolder> for ImageBitmapRenderingContext {
212    /// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
213    fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
214        RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
215    }
216
217    /// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-transferfromimagebitmap>
218    fn TransferFromImageBitmap(&self, image_bitmap: Option<&ImageBitmap>) -> Fallible<()> {
219        let Some(image_bitmap) = image_bitmap else {
220            // Step 2. If bitmap is null, then run the steps to set an
221            // ImageBitmapRenderingContext's output bitmap, with
222            // bitmapContext as the context argument and no bitmap argument,
223            // then return.
224            self.set_bitmap(None);
225            self.mark_as_dirty();
226
227            return Ok(());
228        };
229
230        // Step 3. If the value of bitmap's [[Detached]] internal slot
231        // is set to true, then throw an "InvalidStateError"
232        // DOMException.
233        if image_bitmap.is_detached() {
234            return Err(Error::InvalidState(None));
235        }
236
237        // Step 4. Run the steps to set an ImageBitmapRenderingContext's
238        // output bitmap, with the context argument equal to
239        // bitmapContext, and the bitmap argument referring to bitmap's
240        // underlying bitmap data.
241        self.set_bitmap(Some(image_bitmap));
242        self.mark_as_dirty();
243
244        // Step 5. Set the value of bitmap's [[Detached]] internal slot
245        // to true.
246        // Step 6. Unset bitmap's bitmap data.
247        image_bitmap.Close();
248
249        Ok(())
250    }
251}