mirror of
https://github.com/servo/servo
synced 2026-05-11 17:37:21 +02:00
I noticed that the transferFromImageBitmap() was running without errors but the canvas stayed blank and this was becacuse of two issues: The mark_as_dirty was empty meaning the canvas never knew it needed to repaint The BitmapRenderer context had no rendering pipeline as such no ImageKey was allocated and update_rendering always returned false, so the way I fixed it is by 1. Implemented mark_as_dirty to call self.canvas.mark_as_dirty() 2. Allocated an ImageKey for BitmapRenderer contexts 3.Implemented set_image_key and update_rendering that sends the bitmap's pixel data to WebRender via the paint API 4, Calling mark_as_dirty after set_bitmap in TransferFromImageBitmap Now the reason we are setting an ImageKey on the ImageBitmaRenderingContext is because it needs one to render to the screen and without it the canvas was always blank Also I noticed that for some reasons I don't understand yet, the createImageBitmap hangs or is slow to render but after loading servo on an open tab, if I then go ahead and open another tab and paste the file link there, the previous tab which had the bitmap image now renders properly Fixes: #43565 <img width="383" height="238" alt="Screenshot from 2026-04-06 13-44-28" src="https://github.com/user-attachments/assets/4c5096ba-ee84-4e7a-a3c6-673cc12276c4" /> <img width="389" height="292" alt="Screenshot from 2026-04-06 15-38-55" src="https://github.com/user-attachments/assets/43b45e60-61d8-48de-b53e-6da5ab988768" /> Signed-off-by: Messi002 <rostandmessi2@gmail.com>
252 lines
9.7 KiB
Rust
252 lines
9.7 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::cell::Cell;
|
|
|
|
use dom_struct::dom_struct;
|
|
use euclid::default::Size2D;
|
|
use paint_api::SerializableImageData;
|
|
use pixels::Snapshot;
|
|
use servo_base::Epoch;
|
|
use webrender_api::units::DeviceIntSize;
|
|
use webrender_api::{ImageDescriptor, ImageFormat, ImageKey};
|
|
|
|
use crate::canvas_context::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods;
|
|
use crate::dom::bindings::codegen::Bindings::ImageBitmapRenderingContextBinding::ImageBitmapRenderingContextMethods;
|
|
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
|
|
use crate::dom::bindings::error::{Error, Fallible};
|
|
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
|
|
use crate::dom::bindings::root::DomRoot;
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
|
|
use crate::dom::imagebitmap::ImageBitmap;
|
|
use crate::dom::node::node::NodeTraits;
|
|
use crate::script_runtime::CanGc;
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext>
|
|
#[dom_struct]
|
|
pub(crate) struct ImageBitmapRenderingContext {
|
|
reflector_: Reflector,
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
|
|
canvas: HTMLCanvasElementOrOffscreenCanvas,
|
|
/// Represents both the [output bitmap] and the [bitmap mode] of the context.
|
|
/// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-output-bitmap>
|
|
/// <https://html.spec.whatwg.org/multipage/#concept-imagebitmaprenderingcontext-bitmap-mode>
|
|
#[no_trace]
|
|
bitmap: DomRefCell<Option<Snapshot>>,
|
|
origin_clean: Cell<bool>,
|
|
#[no_trace]
|
|
image_key: Cell<Option<ImageKey>>,
|
|
/// Whether the image has been added to WebRender (first render uses add_image,
|
|
/// subsequent renders use update_image).
|
|
image_added: Cell<bool>,
|
|
}
|
|
|
|
impl ImageBitmapRenderingContext {
|
|
/// <https://html.spec.whatwg.org/multipage/#imagebitmaprenderingcontext-creation-algorithm>
|
|
#[cfg_attr(crown, expect(crown::unrooted_must_root))]
|
|
fn new_inherited(canvas: HTMLCanvasElementOrOffscreenCanvas) -> ImageBitmapRenderingContext {
|
|
ImageBitmapRenderingContext {
|
|
reflector_: Reflector::new(),
|
|
canvas,
|
|
bitmap: DomRefCell::new(None),
|
|
origin_clean: Cell::new(true),
|
|
image_key: Cell::new(None),
|
|
image_added: Cell::new(false),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new(
|
|
global: &GlobalScope,
|
|
canvas: &RootedHTMLCanvasElementOrOffscreenCanvas,
|
|
can_gc: CanGc,
|
|
) -> DomRoot<ImageBitmapRenderingContext> {
|
|
reflect_dom_object(
|
|
Box::new(ImageBitmapRenderingContext::new_inherited(
|
|
HTMLCanvasElementOrOffscreenCanvas::from(canvas),
|
|
)),
|
|
global,
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn set_image_key(&self, image_key: ImageKey) {
|
|
self.image_key.set(Some(image_key));
|
|
}
|
|
|
|
pub(crate) fn update_rendering(&self, epoch: Epoch) -> bool {
|
|
let Some(image_key) = self.image_key.get() else {
|
|
return false;
|
|
};
|
|
|
|
let Some(snapshot) = self.bitmap.borrow().as_ref().cloned() else {
|
|
return false;
|
|
};
|
|
|
|
let size = snapshot.size();
|
|
let format = match snapshot.format() {
|
|
pixels::SnapshotPixelFormat::RGBA => ImageFormat::RGBA8,
|
|
pixels::SnapshotPixelFormat::BGRA => ImageFormat::BGRA8,
|
|
};
|
|
let shared = snapshot.to_shared();
|
|
let descriptor = ImageDescriptor {
|
|
format,
|
|
size: DeviceIntSize::new(size.width as i32, size.height as i32),
|
|
stride: None,
|
|
offset: 0,
|
|
flags: webrender_api::ImageDescriptorFlags::empty(),
|
|
};
|
|
let data = SerializableImageData::Raw(shared.shared_memory());
|
|
|
|
// Here we get the paint API from the canvas element's window.
|
|
if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) = self.canvas {
|
|
let canvas: &HTMLCanvasElement = canvas;
|
|
let doc = canvas.owner_document();
|
|
let paint_api = doc.window().paint_api();
|
|
if self.image_added.get() {
|
|
paint_api.update_image(image_key, descriptor, data, Some(epoch));
|
|
} else {
|
|
paint_api.add_image(image_key, descriptor, data, false);
|
|
self.image_added.set(true);
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#set-an-imagebitmaprenderingcontext's-output-bitmap>
|
|
fn set_bitmap(&self, image_bitmap: Option<&ImageBitmap>) {
|
|
match image_bitmap {
|
|
Some(image_bitmap) => {
|
|
// Step 2.1. Set context's bitmap mode to valid.
|
|
// Step 2.2. Set context's output bitmap to refer to the same
|
|
// underlying bitmap data as bitmap, without making a copy.
|
|
*self.bitmap.borrow_mut() = image_bitmap.bitmap_data().clone();
|
|
|
|
// The origin-clean flag of bitmap is included in the bitmap
|
|
// data to be referenced by context's output bitmap.
|
|
self.origin_clean.set(image_bitmap.origin_is_clean());
|
|
},
|
|
None => {
|
|
// Step 1.1. Set context's bitmap mode to blank.
|
|
// Step 1.2. Let canvas be the canvas element to which context is bound.
|
|
// Step 1.3. Set context's output bitmap to be transparent black
|
|
// with a natural width equal to the numeric value of canvas's
|
|
// width attribute and a natural height equal to the numeric
|
|
// value of canvas's height attribute, those values being
|
|
// interpreted in CSS pixels.
|
|
*self.bitmap.borrow_mut() = None;
|
|
|
|
// Step 1.4. Set the output bitmap's origin-clean flag to true.
|
|
self.origin_clean.set(true);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CanvasContext for ImageBitmapRenderingContext {
|
|
type ID = ();
|
|
|
|
fn context_id(&self) -> Self::ID {}
|
|
|
|
fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
|
|
Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-canvas-element:concept-canvas-bitmaprenderer>
|
|
fn resize(&self) {
|
|
// The absence of the bitmap is the context's blank bitmap mode so the
|
|
// steps to set output bitmap could be omitted.
|
|
}
|
|
|
|
fn reset_bitmap(&self) {
|
|
// The newly created bitmap should be of the same dimensions as the
|
|
// previous bitmap if the context's bitmap mode is valid.
|
|
if self.bitmap.borrow().is_none() {
|
|
return;
|
|
}
|
|
|
|
let size = self.bitmap.borrow().as_ref().unwrap().size();
|
|
*self.bitmap.borrow_mut() = Some(Snapshot::cleared(size));
|
|
}
|
|
|
|
fn get_image_data(&self) -> Option<Snapshot> {
|
|
match self.bitmap.borrow().as_ref() {
|
|
Some(bitmap) => Some(bitmap.clone()),
|
|
None => {
|
|
let size = self.canvas.size();
|
|
if size.is_empty() ||
|
|
pixels::compute_rgba8_byte_length_if_within_limit(
|
|
size.width as usize,
|
|
size.height as usize,
|
|
)
|
|
.is_none()
|
|
{
|
|
None
|
|
} else {
|
|
Some(Snapshot::cleared(size))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn origin_is_clean(&self) -> bool {
|
|
self.origin_clean.get()
|
|
}
|
|
|
|
fn size(&self) -> Size2D<u32> {
|
|
self.bitmap
|
|
.borrow()
|
|
.as_ref()
|
|
.map_or_else(|| self.canvas.size(), |bitmap| bitmap.size())
|
|
}
|
|
|
|
fn mark_as_dirty(&self) {
|
|
self.canvas.mark_as_dirty();
|
|
}
|
|
}
|
|
|
|
impl ImageBitmapRenderingContextMethods<crate::DomTypeHolder> for ImageBitmapRenderingContext {
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-canvas>
|
|
fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
|
|
RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmaprenderingcontext-transferfromimagebitmap>
|
|
fn TransferFromImageBitmap(&self, image_bitmap: Option<&ImageBitmap>) -> Fallible<()> {
|
|
let Some(image_bitmap) = image_bitmap else {
|
|
// Step 2. If bitmap is null, then run the steps to set an
|
|
// ImageBitmapRenderingContext's output bitmap, with
|
|
// bitmapContext as the context argument and no bitmap argument,
|
|
// then return.
|
|
self.set_bitmap(None);
|
|
self.mark_as_dirty();
|
|
|
|
return Ok(());
|
|
};
|
|
|
|
// Step 3. If the value of bitmap's [[Detached]] internal slot
|
|
// is set to true, then throw an "InvalidStateError"
|
|
// DOMException.
|
|
if image_bitmap.is_detached() {
|
|
return Err(Error::InvalidState(None));
|
|
}
|
|
|
|
// Step 4. Run the steps to set an ImageBitmapRenderingContext's
|
|
// output bitmap, with the context argument equal to
|
|
// bitmapContext, and the bitmap argument referring to bitmap's
|
|
// underlying bitmap data.
|
|
self.set_bitmap(Some(image_bitmap));
|
|
self.mark_as_dirty();
|
|
|
|
// Step 5. Set the value of bitmap's [[Detached]] internal slot
|
|
// to true.
|
|
// Step 6. Unset bitmap's bitmap data.
|
|
image_bitmap.Close();
|
|
|
|
Ok(())
|
|
}
|
|
}
|