mirror of
https://github.com/servo/servo.git
synced 2025-07-15 11:23:39 +01:00
I introduced snapshot in #36119 to pack raw bytes and metadata together, now we take the next step and require for user to always specify what kind of byte data they want when calling `as_bytes` or `to_vec` (basically joining transform and data). There are also valid usages when one might require just one property of bytes (textures can generally handle both RGBA and BGRA). There are also valid usages of using just raw bytes (when cropping). This PR tries to make such usages more obvious. This will make it easier to fix stuff around 2d canvas (we do not want to assume any bytes properties in abstraction). Testing: Code is covered by WPT tests. --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
771 lines
31 KiB
Rust
771 lines
31 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, Ref};
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
|
|
use base::id::{ImageBitmapId, ImageBitmapIndex};
|
|
use constellation_traits::SerializableImageBitmap;
|
|
use dom_struct::dom_struct;
|
|
use euclid::default::{Point2D, Rect, Size2D};
|
|
use pixels::{CorsStatus, PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
|
|
use script_bindings::error::Error;
|
|
use script_bindings::realms::{AlreadyInRealm, InRealm};
|
|
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{
|
|
ImageBitmapMethods, ImageBitmapOptions, ImageBitmapSource, ImageOrientation, PremultiplyAlpha,
|
|
ResizeQuality,
|
|
};
|
|
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
|
|
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
|
|
use crate::dom::bindings::root::DomRoot;
|
|
use crate::dom::bindings::serializable::Serializable;
|
|
use crate::dom::bindings::structuredclone::StructuredData;
|
|
use crate::dom::bindings::transferable::Transferable;
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::types::Promise;
|
|
use crate::script_runtime::CanGc;
|
|
|
|
#[dom_struct]
|
|
pub(crate) struct ImageBitmap {
|
|
reflector_: Reflector,
|
|
/// The actual pixel data of the bitmap
|
|
///
|
|
/// If this is `None`, then the bitmap data has been released by calling
|
|
/// [`close`](https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close)
|
|
#[no_trace]
|
|
bitmap_data: DomRefCell<Option<Snapshot>>,
|
|
origin_clean: Cell<bool>,
|
|
}
|
|
|
|
impl ImageBitmap {
|
|
fn new_inherited(bitmap_data: Snapshot) -> ImageBitmap {
|
|
ImageBitmap {
|
|
reflector_: Reflector::new(),
|
|
bitmap_data: DomRefCell::new(Some(bitmap_data)),
|
|
origin_clean: Cell::new(true),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new(
|
|
global: &GlobalScope,
|
|
bitmap_data: Snapshot,
|
|
can_gc: CanGc,
|
|
) -> DomRoot<ImageBitmap> {
|
|
reflect_dom_object(
|
|
Box::new(ImageBitmap::new_inherited(bitmap_data)),
|
|
global,
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub(crate) fn bitmap_data(&self) -> Ref<Option<Snapshot>> {
|
|
self.bitmap_data.borrow()
|
|
}
|
|
|
|
pub(crate) fn origin_is_clean(&self) -> bool {
|
|
self.origin_clean.get()
|
|
}
|
|
|
|
pub(crate) fn set_origin_clean(&self, origin_is_clean: bool) {
|
|
self.origin_clean.set(origin_is_clean);
|
|
}
|
|
|
|
/// Return the value of the [`[[Detached]]`](https://html.spec.whatwg.org/multipage/#detached)
|
|
/// internal slot
|
|
pub(crate) fn is_detached(&self) -> bool {
|
|
self.bitmap_data.borrow().is_none()
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#cropped-to-the-source-rectangle-with-formatting>
|
|
pub(crate) fn crop_and_transform_bitmap_data(
|
|
input: Snapshot,
|
|
mut sx: i32,
|
|
mut sy: i32,
|
|
sw: Option<i32>,
|
|
sh: Option<i32>,
|
|
options: &ImageBitmapOptions,
|
|
) -> Option<Snapshot> {
|
|
let input_size = input.size().to_i32();
|
|
|
|
// Step 2. If sx, sy, sw and sh are specified, let sourceRectangle be a rectangle whose corners
|
|
// are the four points (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh). Otherwise,
|
|
// let sourceRectangle be a rectangle whose corners are the four points (0, 0), (width of input, 0),
|
|
// (width of input, height of input), (0, height of input). If either sw or sh are negative,
|
|
// then the top-left corner of this rectangle will be to the left or above the (sx, sy) point.
|
|
let sw = sw.map_or(input_size.width, |width| {
|
|
if width < 0 {
|
|
sx = sx.saturating_add(width);
|
|
width.saturating_abs()
|
|
} else {
|
|
width
|
|
}
|
|
});
|
|
|
|
let sh = sh.map_or(input_size.height, |height| {
|
|
if height < 0 {
|
|
sy = sy.saturating_add(height);
|
|
height.saturating_abs()
|
|
} else {
|
|
height
|
|
}
|
|
});
|
|
|
|
let source_rect = Rect::new(Point2D::new(sx, sy), Size2D::new(sw, sh));
|
|
|
|
// Whether the byte length of the source bitmap exceeds the supported range.
|
|
// In the case the source is too large, we should fail, and that is not defined.
|
|
// <https://github.com/whatwg/html/issues/3323>
|
|
let Some(source_byte_length) = pixels::compute_rgba8_byte_length_if_within_limit(
|
|
source_rect.size.width as usize,
|
|
source_rect.size.height as usize,
|
|
) else {
|
|
log::warn!(
|
|
"Failed to allocate bitmap of size {:?}, too large",
|
|
source_rect.size
|
|
);
|
|
return None;
|
|
};
|
|
|
|
// Step 3. Let outputWidth be determined as follows:
|
|
// Step 4. Let outputHeight be determined as follows:
|
|
let output_size = match (options.resizeWidth, options.resizeHeight) {
|
|
(Some(width), Some(height)) => Size2D::new(width, height),
|
|
(Some(width), None) => {
|
|
let height =
|
|
source_rect.size.height as f64 * width as f64 / source_rect.size.width as f64;
|
|
Size2D::new(width, height.round() as u32)
|
|
},
|
|
(None, Some(height)) => {
|
|
let width =
|
|
source_rect.size.width as f64 * height as f64 / source_rect.size.height as f64;
|
|
Size2D::new(width.round() as u32, height)
|
|
},
|
|
(None, None) => source_rect.size.to_u32(),
|
|
};
|
|
|
|
// Whether the byte length of the output bitmap exceeds the supported range.
|
|
// In the case the output is too large, we should fail, and that is not defined.
|
|
// <https://github.com/whatwg/html/issues/3323>
|
|
let Some(output_byte_length) = pixels::compute_rgba8_byte_length_if_within_limit(
|
|
output_size.width as usize,
|
|
output_size.height as usize,
|
|
) else {
|
|
log::warn!(
|
|
"Failed to allocate bitmap of size {:?}, too large",
|
|
output_size
|
|
);
|
|
return None;
|
|
};
|
|
|
|
// TODO: Take into account the image orientation (such as EXIF metadata).
|
|
|
|
// Step 5. Place input on an infinite transparent black grid plane, positioned so that
|
|
// its top left corner is at the origin of the plane, with the x-coordinate increasing to the right,
|
|
// and the y-coordinate increasing down, and with each pixel in the input image data occupying a cell
|
|
// on the plane's grid.
|
|
let input_rect = Rect::new(Point2D::zero(), input_size);
|
|
|
|
let input_rect_cropped = source_rect
|
|
.intersection(&input_rect)
|
|
.unwrap_or(Rect::zero());
|
|
|
|
// Early out for empty tranformations.
|
|
if input_rect_cropped.is_empty() {
|
|
return Some(Snapshot::cleared(output_size));
|
|
}
|
|
|
|
// Step 6. Let output be the rectangle on the plane denoted by sourceRectangle.
|
|
let mut source: Snapshot = Snapshot::from_vec(
|
|
source_rect.size.cast(),
|
|
input.format(),
|
|
input.alpha_mode(),
|
|
vec![0; source_byte_length],
|
|
);
|
|
|
|
let source_rect_cropped = Rect::new(
|
|
Point2D::new(
|
|
input_rect_cropped.origin.x - source_rect.origin.x,
|
|
input_rect_cropped.origin.y - source_rect.origin.y,
|
|
),
|
|
input_rect_cropped.size,
|
|
);
|
|
|
|
pixels::copy_rgba8_image(
|
|
input.size(),
|
|
input_rect_cropped.cast(),
|
|
input.as_raw_bytes(),
|
|
source.size(),
|
|
source_rect_cropped.cast(),
|
|
source.as_raw_bytes_mut(),
|
|
);
|
|
|
|
// Step 7. Scale output to the size specified by outputWidth and outputHeight.
|
|
let mut output = if source.size() != output_size {
|
|
let quality = match options.resizeQuality {
|
|
ResizeQuality::Pixelated => pixels::FilterQuality::None,
|
|
ResizeQuality::Low => pixels::FilterQuality::Low,
|
|
ResizeQuality::Medium => pixels::FilterQuality::Medium,
|
|
ResizeQuality::High => pixels::FilterQuality::High,
|
|
};
|
|
|
|
let Some(output_data) = pixels::scale_rgba8_image(
|
|
source.size(),
|
|
source.as_raw_bytes(),
|
|
output_size,
|
|
quality,
|
|
) else {
|
|
log::warn!(
|
|
"Failed to scale the bitmap of size {:?} to required size {:?}",
|
|
source.size(),
|
|
output_size
|
|
);
|
|
return None;
|
|
};
|
|
|
|
debug_assert_eq!(output_data.len(), output_byte_length);
|
|
|
|
Snapshot::from_vec(
|
|
output_size,
|
|
source.format(),
|
|
source.alpha_mode(),
|
|
output_data,
|
|
)
|
|
} else {
|
|
source
|
|
};
|
|
|
|
// Step 8. If the value of the imageOrientation member of options is "flipY",
|
|
// output must be flipped vertically, disregarding any image orientation metadata
|
|
// of the source (such as EXIF metadata), if any.
|
|
if options.imageOrientation == ImageOrientation::FlipY {
|
|
pixels::flip_y_rgba8_image_inplace(output.size(), output.as_raw_bytes_mut());
|
|
}
|
|
|
|
// TODO: Step 9. If image is an img element or a Blob object, let val be the value
|
|
// of the colorSpaceConversion member of options, and then run these substeps:
|
|
|
|
// Step 10. Let val be the value of premultiplyAlpha member of options,
|
|
// and then run these substeps:
|
|
// TODO: Preserve the original input pixel format and perform conversion on demand.
|
|
match options.premultiplyAlpha {
|
|
PremultiplyAlpha::Default | PremultiplyAlpha::Premultiply => {
|
|
output.transform(
|
|
SnapshotAlphaMode::Transparent {
|
|
premultiplied: true,
|
|
},
|
|
SnapshotPixelFormat::BGRA,
|
|
);
|
|
},
|
|
PremultiplyAlpha::None => {
|
|
output.transform(
|
|
SnapshotAlphaMode::Transparent {
|
|
premultiplied: false,
|
|
},
|
|
SnapshotPixelFormat::BGRA,
|
|
);
|
|
},
|
|
}
|
|
|
|
// Step 11. Return output.
|
|
Some(output)
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-createimagebitmap>
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) fn create_image_bitmap(
|
|
global_scope: &GlobalScope,
|
|
image: ImageBitmapSource,
|
|
sx: i32,
|
|
sy: i32,
|
|
sw: Option<i32>,
|
|
sh: Option<i32>,
|
|
options: &ImageBitmapOptions,
|
|
can_gc: CanGc,
|
|
) -> Rc<Promise> {
|
|
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
|
|
let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
|
|
|
|
// Step 1. If either sw or sh is given and is 0, then return a promise rejected with a RangeError.
|
|
if sw.is_some_and(|w| w == 0) {
|
|
p.reject_error(
|
|
Error::Range("'sw' must be a non-zero value".to_owned()),
|
|
can_gc,
|
|
);
|
|
return p;
|
|
}
|
|
|
|
if sh.is_some_and(|h| h == 0) {
|
|
p.reject_error(
|
|
Error::Range("'sh' must be a non-zero value".to_owned()),
|
|
can_gc,
|
|
);
|
|
return p;
|
|
}
|
|
|
|
// Step 2. If either options's resizeWidth or options's resizeHeight is present and is 0,
|
|
// then return a promise rejected with an "InvalidStateError" DOMException.
|
|
if options.resizeWidth.is_some_and(|w| w == 0) {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
if options.resizeHeight.is_some_and(|h| h == 0) {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// The promise with image bitmap should be fulfilled on the the bitmap task source.
|
|
let fullfill_promise_on_bitmap_task_source =
|
|
|promise: &Rc<Promise>, image_bitmap: &ImageBitmap| {
|
|
let trusted_promise = TrustedPromise::new(promise.clone());
|
|
let trusted_image_bitmap = Trusted::new(image_bitmap);
|
|
|
|
global_scope.task_manager().bitmap_task_source().queue(
|
|
task!(resolve_promise: move || {
|
|
let promise = trusted_promise.root();
|
|
let image_bitmap = trusted_image_bitmap.root();
|
|
|
|
promise.resolve_native(&image_bitmap, CanGc::note());
|
|
}),
|
|
);
|
|
};
|
|
|
|
// The promise with "InvalidStateError" DOMException should be rejected
|
|
// on the the bitmap task source.
|
|
let reject_promise_on_bitmap_task_source = |promise: &Rc<Promise>| {
|
|
let trusted_promise = TrustedPromise::new(promise.clone());
|
|
|
|
global_scope
|
|
.task_manager()
|
|
.bitmap_task_source()
|
|
.queue(task!(reject_promise: move || {
|
|
let promise = trusted_promise.root();
|
|
|
|
promise.reject_error(Error::InvalidState, CanGc::note());
|
|
}));
|
|
};
|
|
|
|
// Step 3. Check the usability of the image argument. If this throws an exception or returns bad,
|
|
// then return a promise rejected with an "InvalidStateError" DOMException.
|
|
// Step 6. Switch on image:
|
|
match image {
|
|
ImageBitmapSource::HTMLImageElement(ref image) => {
|
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
|
if !image.is_usable().is_ok_and(|u| u) {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// If no ImageBitmap object can be constructed, then the promise
|
|
// is rejected instead.
|
|
let Some(snapshot) = image.get_raster_image_data() else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.3. Set imageBitmap's bitmap data to a copy of image's media data,
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
// Step 6.4. If image is not origin-clean, then set the origin-clean flag
|
|
// of imageBitmap's bitmap to false.
|
|
image_bitmap.set_origin_clean(image.same_origin(GlobalScope::entry().origin()));
|
|
|
|
// Step 6.5. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::HTMLVideoElement(ref video) => {
|
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
|
if !video.is_usable() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// Step 6.1. If image's networkState attribute is NETWORK_EMPTY, then return
|
|
// a promise rejected with an "InvalidStateError" DOMException.
|
|
if video.is_network_state_empty() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
|
|
let Some(snapshot) = video.get_current_frame_data() else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.2. Set imageBitmap's bitmap data to a copy of the frame at the current
|
|
// playback position, at the media resource's natural width and natural height
|
|
// (i.e., after any aspect-ratio correction has been applied),
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
// Step 6.3. If image is not origin-clean, then set the origin-clean flag
|
|
// of imageBitmap's bitmap to false.
|
|
image_bitmap.set_origin_clean(video.origin_is_clean());
|
|
|
|
// Step 6.4. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::HTMLCanvasElement(ref canvas) => {
|
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
|
if canvas.get_size().is_empty() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
|
|
let Some(snapshot) = canvas.get_image_data() else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
|
|
// as the origin-clean flag of image's bitmap.
|
|
image_bitmap.set_origin_clean(canvas.origin_is_clean());
|
|
|
|
// Step 6.3. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::ImageBitmap(ref bitmap) => {
|
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
|
if bitmap.is_detached() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
|
|
let Some(snapshot) = bitmap.bitmap_data().clone() else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
// Step 6.2. Set the origin-clean flag of imageBitmap's bitmap to the same value
|
|
// as the origin-clean flag of image's bitmap.
|
|
image_bitmap.set_origin_clean(bitmap.origin_is_clean());
|
|
|
|
// Step 6.3. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::OffscreenCanvas(ref canvas) => {
|
|
// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
|
|
if canvas.get_size().is_empty() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
// If no ImageBitmap object can be constructed, then the promise is rejected instead.
|
|
let Some(snapshot) = canvas.get_image_data() else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.1. Set imageBitmap's bitmap data to a copy of image's bitmap data,
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
// Step 6.2. Set the origin-clean flag of the imageBitmap's bitmap to the same value
|
|
// as the origin-clean flag of image's bitmap.
|
|
image_bitmap.set_origin_clean(canvas.origin_is_clean());
|
|
|
|
// Step 6.3. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::Blob(ref blob) => {
|
|
// Step 6.1. Let imageData be the result of reading image's data.
|
|
// If an error occurs during reading of the object, then queue
|
|
// a global task, using the bitmap task source, to reject promise
|
|
// with an "InvalidStateError" DOMException and abort these steps.
|
|
let Ok(bytes) = blob.get_bytes() else {
|
|
reject_promise_on_bitmap_task_source(&p);
|
|
return p;
|
|
};
|
|
|
|
// Step 6.2. Apply the image sniffing rules to determine the file
|
|
// format of imageData, with MIME type of image (as given by
|
|
// image's type attribute) giving the official type.
|
|
// Step 6.3. If imageData is not in a supported image file format
|
|
// (e.g., it's not an image at all), or if imageData is corrupted
|
|
// in some fatal way such that the image dimensions cannot be obtained
|
|
// (e.g., a vector graphic with no natural size), then queue
|
|
// a global task, using the bitmap task source, to reject promise
|
|
// with an "InvalidStateError" DOMException and abort these steps.
|
|
let Some(img) = pixels::load_from_memory(&bytes, CorsStatus::Safe) else {
|
|
reject_promise_on_bitmap_task_source(&p);
|
|
return p;
|
|
};
|
|
|
|
let size = Size2D::new(img.metadata.width, img.metadata.height);
|
|
let format = match img.format {
|
|
PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
|
|
PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
|
|
pixel_format => {
|
|
unimplemented!("unsupported pixel format ({:?})", pixel_format)
|
|
},
|
|
};
|
|
let alpha_mode = SnapshotAlphaMode::Transparent {
|
|
premultiplied: false,
|
|
};
|
|
|
|
let snapshot = Snapshot::from_vec(
|
|
size.cast(),
|
|
format,
|
|
alpha_mode,
|
|
img.first_frame().bytes.to_vec(),
|
|
);
|
|
|
|
// Step 6.4. Set imageBitmap's bitmap data to imageData, cropped
|
|
// to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
reject_promise_on_bitmap_task_source(&p);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
|
|
// Step 6.5. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::ImageData(ref image_data) => {
|
|
// Step 6.1. Let buffer be image's data attribute value's [[ViewedArrayBuffer]] internal slot.
|
|
// Step 6.2. If IsDetachedBuffer(buffer) is true, then return a promise rejected
|
|
// with an "InvalidStateError" DOMException.
|
|
if image_data.is_detached() {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
}
|
|
|
|
let alpha_mode = SnapshotAlphaMode::Transparent {
|
|
premultiplied: false,
|
|
};
|
|
|
|
let snapshot = Snapshot::from_vec(
|
|
image_data.get_size().cast(),
|
|
SnapshotPixelFormat::RGBA,
|
|
alpha_mode,
|
|
image_data.to_vec(),
|
|
);
|
|
|
|
// Step 6.3. Set imageBitmap's bitmap data to image's image data,
|
|
// cropped to the source rectangle with formatting.
|
|
let Some(bitmap_data) =
|
|
ImageBitmap::crop_and_transform_bitmap_data(snapshot, sx, sy, sw, sh, options)
|
|
else {
|
|
p.reject_error(Error::InvalidState, can_gc);
|
|
return p;
|
|
};
|
|
|
|
let image_bitmap = Self::new(global_scope, bitmap_data, can_gc);
|
|
|
|
// Step 6.4. Queue a global task, using the bitmap task source,
|
|
// to resolve promise with imageBitmap.
|
|
fullfill_promise_on_bitmap_task_source(&p, &image_bitmap);
|
|
},
|
|
ImageBitmapSource::CSSStyleValue(_) => {
|
|
// TODO: CSSStyleValue is not part of ImageBitmapSource
|
|
// <https://html.spec.whatwg.org/multipage/#imagebitmapsource>
|
|
p.reject_error(Error::NotSupported, can_gc);
|
|
},
|
|
}
|
|
|
|
// Step 7. Return promise.
|
|
p
|
|
}
|
|
}
|
|
|
|
impl Serializable for ImageBitmap {
|
|
type Index = ImageBitmapIndex;
|
|
type Data = SerializableImageBitmap;
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:serialization-steps>
|
|
fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> {
|
|
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
|
|
if !self.origin_is_clean() {
|
|
return Err(());
|
|
}
|
|
|
|
// If value has a [[Detached]] internal slot whose value is true,
|
|
// then throw a "DataCloneError" DOMException.
|
|
if self.is_detached() {
|
|
return Err(());
|
|
}
|
|
|
|
// Step 2. Set serialized.[[BitmapData]] to a copy of value's bitmap data.
|
|
let serialized = SerializableImageBitmap {
|
|
bitmap_data: self.bitmap_data.borrow().clone().unwrap(),
|
|
};
|
|
|
|
Ok((ImageBitmapId::new(), serialized))
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:deserialization-steps>
|
|
fn deserialize(
|
|
owner: &GlobalScope,
|
|
serialized: Self::Data,
|
|
can_gc: CanGc,
|
|
) -> Result<DomRoot<Self>, ()> {
|
|
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
|
|
Ok(ImageBitmap::new(owner, serialized.bitmap_data, can_gc))
|
|
}
|
|
|
|
fn serialized_storage<'a>(
|
|
data: StructuredData<'a, '_>,
|
|
) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
|
|
match data {
|
|
StructuredData::Reader(r) => &mut r.image_bitmaps,
|
|
StructuredData::Writer(w) => &mut w.image_bitmaps,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Transferable for ImageBitmap {
|
|
type Index = ImageBitmapIndex;
|
|
type Data = SerializableImageBitmap;
|
|
|
|
fn can_transfer(&self) -> bool {
|
|
if !self.origin_is_clean() || self.is_detached() {
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-steps>
|
|
fn transfer(&self) -> Result<(ImageBitmapId, SerializableImageBitmap), ()> {
|
|
// Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
|
|
if !self.origin_is_clean() {
|
|
return Err(());
|
|
}
|
|
|
|
// If value has a [[Detached]] internal slot whose value is true,
|
|
// then throw a "DataCloneError" DOMException.
|
|
if self.is_detached() {
|
|
return Err(());
|
|
}
|
|
|
|
// Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data.
|
|
// Step 3. Unset value's bitmap data.
|
|
let serialized = SerializableImageBitmap {
|
|
bitmap_data: self.bitmap_data.borrow_mut().take().unwrap(),
|
|
};
|
|
|
|
Ok((ImageBitmapId::new(), serialized))
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-receiving-steps>
|
|
fn transfer_receive(
|
|
owner: &GlobalScope,
|
|
_: ImageBitmapId,
|
|
serialized: SerializableImageBitmap,
|
|
) -> Result<DomRoot<Self>, ()> {
|
|
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
|
|
Ok(ImageBitmap::new(
|
|
owner,
|
|
serialized.bitmap_data,
|
|
CanGc::note(),
|
|
))
|
|
}
|
|
|
|
fn serialized_storage<'a>(
|
|
data: StructuredData<'a, '_>,
|
|
) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
|
|
match data {
|
|
StructuredData::Reader(r) => &mut r.transferred_image_bitmaps,
|
|
StructuredData::Writer(w) => &mut w.transferred_image_bitmaps,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-height>
|
|
fn Height(&self) -> u32 {
|
|
// Step 1. If this's [[Detached]] internal slot's value is true, then return 0.
|
|
if self.is_detached() {
|
|
return 0;
|
|
}
|
|
|
|
// Step 2. Return this's height, in CSS pixels.
|
|
self.bitmap_data
|
|
.borrow()
|
|
.as_ref()
|
|
.unwrap()
|
|
.size()
|
|
.cast()
|
|
.height
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-width>
|
|
fn Width(&self) -> u32 {
|
|
// Step 1. If this's [[Detached]] internal slot's value is true, then return 0.
|
|
if self.is_detached() {
|
|
return 0;
|
|
}
|
|
|
|
// Step 2. Return this's width, in CSS pixels.
|
|
self.bitmap_data
|
|
.borrow()
|
|
.as_ref()
|
|
.unwrap()
|
|
.size()
|
|
.cast()
|
|
.width
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close>
|
|
fn Close(&self) {
|
|
// Step 1. Set this's [[Detached]] internal slot value to true.
|
|
// Step 2. Unset this's bitmap data.
|
|
// NOTE: The existence of the bitmap data is the internal slot in our implementation
|
|
self.bitmap_data.borrow_mut().take();
|
|
}
|
|
}
|