// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use deno_core::GarbageCollected; use deno_core::WebIDL; use deno_core::op2; use deno_core::v8; use deno_core::webidl::ContextFn; use deno_core::webidl::IntOptions; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::webidl::WebIdlErrorKind; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum ImageDataError { #[class(inherit)] #[error(transparent)] WebIDL(#[from] WebIdlError), #[class("DOMExceptionInvalidStateError")] #[error("Failed to construct 'ImageData': the input data has zero elements")] ZeroElements, #[class("DOMExceptionInvalidStateError")] #[error( "Failed to construct 'ImageData': the input data length is not a multiple of 4, received {0}" )] NotMultipleOfFour(usize), #[class("DOMExceptionIndexSizeError")] #[error( "Failed to construct 'ImageData': the source width is zero or not a number" )] ZeroWidth, #[class("DOMExceptionIndexSizeError")] #[error( "Failed to construct 'ImageData': the source height is zero or not a number" )] ZeroHeight, #[class("DOMExceptionIndexSizeError")] #[error( "Failed to construct 'ImageData': the input data length is not a multiple of (4 * width)" )] NotMultipleOfRow, #[class("DOMExceptionIndexSizeError")] #[error( "Failed to construct 'ImageData': the input data length is not equal to (4 * width * height)" )] WrongLength, #[class("DOMExceptionInvalidStateError")] #[error( "Failed to construct 'ImageData': Uint8ClampedArray must use rgba-unorm8 pixelFormat." )] Uint8ClampedNeedsUnorm, #[class("DOMExceptionInvalidStateError")] #[error( "Failed to construct 'ImageData': Float16Array must use rgba-float16 pixelFormat." )] Float16NeedsFloat16, #[class(generic)] #[error("Failed to allocate ImageData backing store")] AllocationFailed, } #[derive(WebIDL, Debug, Clone, Copy, PartialEq, Eq)] #[webidl(enum)] pub enum PredefinedColorSpace { #[webidl(rename = "srgb")] Srgb, #[webidl(rename = "display-p3")] DisplayP3, } impl PredefinedColorSpace { fn name(self) -> &'static str { match self { Self::Srgb => "srgb", Self::DisplayP3 => "display-p3", } } } #[derive(WebIDL, Debug, Clone, Copy, PartialEq, Eq)] #[webidl(enum)] pub enum ImageDataPixelFormat { #[webidl(rename = "rgba-unorm8")] RgbaUnorm8, #[webidl(rename = "rgba-float16")] RgbaFloat16, } impl ImageDataPixelFormat { fn name(self) -> &'static str { match self { Self::RgbaUnorm8 => "rgba-unorm8", Self::RgbaFloat16 => "rgba-float16", } } } #[derive(WebIDL, Debug)] #[webidl(dictionary)] pub struct ImageDataSettings { #[webidl(default = None)] color_space: Option, #[webidl(default = ImageDataPixelFormat::RgbaUnorm8)] pixel_format: ImageDataPixelFormat, } pub struct ImageData { width: u32, height: u32, pixel_format: ImageDataPixelFormat, color_space: PredefinedColorSpace, data: v8::TracedReference, } // SAFETY: we're sure `ImageData` can be GCed. unsafe impl GarbageCollected for ImageData { fn trace(&self, visitor: &mut v8::cppgc::Visitor) { visitor.trace(&self.data); } fn get_name(&self) -> &'static std::ffi::CStr { c"ImageData" } } #[inline] fn convert_unsigned_long<'a>( scope: &mut v8::PinScope<'a, '_>, value: v8::Local<'a, v8::Value>, context: &'static str, ) -> Result { u32::convert( scope, value, Cow::Borrowed("Failed to construct 'ImageData'"), ContextFn::new_borrowed(&|| Cow::Borrowed(context)), &IntOptions::default(), ) } #[inline] fn convert_settings<'a>( scope: &mut v8::PinScope<'a, '_>, value: v8::Local<'a, v8::Value>, context: &'static str, ) -> Result { ImageDataSettings::convert( scope, value, Cow::Borrowed("Failed to construct 'ImageData'"), ContextFn::new_borrowed(&|| Cow::Borrowed(context)), &Default::default(), ) } #[inline] fn alloc_typed_array<'a>( scope: &mut v8::PinScope<'a, '_>, pixel_format: ImageDataPixelFormat, width: u32, height: u32, ) -> Result, ImageDataError> { let pixel_count = (width as usize) .checked_mul(height as usize) .and_then(|v| v.checked_mul(4)) .ok_or(ImageDataError::AllocationFailed)?; match pixel_format { ImageDataPixelFormat::RgbaUnorm8 => { let buffer = v8::ArrayBuffer::new(scope, pixel_count); v8::Uint8ClampedArray::new(scope, buffer, 0, pixel_count) .map(Into::into) .ok_or(ImageDataError::AllocationFailed) } ImageDataPixelFormat::RgbaFloat16 => { let byte_length = pixel_count .checked_mul(2) .ok_or(ImageDataError::AllocationFailed)?; let buffer = v8::ArrayBuffer::new(scope, byte_length); let arr = v8::Float16Array::new(scope, buffer, 0, pixel_count) .ok_or(ImageDataError::AllocationFailed)?; // SAFETY: a Float16Array is a v8::Object (transmute is the existing // workaround until rusty_v8 implements `Into`). Ok(unsafe { std::mem::transmute::, v8::Local>( arr, ) }) } } } #[op2] impl ImageData { #[constructor] #[reentrant] #[required(2)] #[cppgc] fn constructor<'a>( scope: &mut v8::PinScope<'a, '_>, #[varargs] args: Option<&v8::FunctionCallbackArguments<'a>>, ) -> Result { // `#[required(2)]` ensures `args` has at least 2 entries. let args = args.expect("constructor requires arguments"); let arg_count = args.length(); let arg0 = args.get(0); let arg1 = args.get(1); let arg2 = if arg_count >= 3 { Some(args.get(2)) } else { None }; let arg3 = if arg_count >= 4 { Some(args.get(3)) } else { None }; let arg0_typed_array = v8::Local::::try_from(arg0).ok(); let arg0_is_uint8_clamped = if arg0.is_object() { arg0.cast::().is_uint8_clamped_array() } else { false }; let arg0_is_float16 = if arg0.is_object() { arg0.cast::().is_float16_array() } else { false }; if arg_count > 3 || arg0_is_uint8_clamped || arg0_is_float16 { // Overload: new ImageData(data, sw [, sh [, settings ] ]) let data = arg0_typed_array.ok_or_else(|| { WebIdlError::new( Cow::Borrowed("Failed to construct 'ImageData'"), ContextFn::new_borrowed(&|| Cow::Borrowed("Argument 1")), WebIdlErrorKind::ConvertToConverterType("ArrayBufferView"), ) })?; let source_width = convert_unsigned_long(scope, arg1, "Argument 2")?; let source_height = match arg2 { Some(v) if !v.is_undefined() => { Some(convert_unsigned_long(scope, v, "Argument 3")?) } _ => None, }; let settings_value = arg3.unwrap_or_else(|| v8::undefined(scope).into()); let settings = convert_settings(scope, settings_value, "Argument 4")?; // Match `TypedArrayPrototypeGetLength` semantics: element count, not // byte count. let data_length = data.length(); if data_length == 0 { return Err(ImageDataError::ZeroElements); } if data_length % 4 != 0 { return Err(ImageDataError::NotMultipleOfFour(data_length)); } if source_width == 0 { return Err(ImageDataError::ZeroWidth); } if let Some(h) = source_height && h == 0 { return Err(ImageDataError::ZeroHeight); } let pixel_count = data_length / 4; if pixel_count % source_width as usize != 0 { return Err(ImageDataError::NotMultipleOfRow); } let derived_height = (pixel_count / source_width as usize) as u32; if let Some(h) = source_height && h != derived_height { return Err(ImageDataError::WrongLength); } if arg0_is_uint8_clamped && !matches!(settings.pixel_format, ImageDataPixelFormat::RgbaUnorm8) { return Err(ImageDataError::Uint8ClampedNeedsUnorm); } if arg0_is_float16 && !matches!(settings.pixel_format, ImageDataPixelFormat::RgbaFloat16) { return Err(ImageDataError::Float16NeedsFloat16); } let color_space = settings.color_space.unwrap_or(PredefinedColorSpace::Srgb); let height = source_height.unwrap_or(derived_height); let data_obj: v8::Local = data.into(); Ok(ImageData { width: source_width, height, pixel_format: settings.pixel_format, color_space, data: v8::TracedReference::new(scope, data_obj), }) } else { // Overload: new ImageData(sw, sh [, settings]) let source_width = convert_unsigned_long(scope, arg0, "Argument 1")?; let source_height = convert_unsigned_long(scope, arg1, "Argument 2")?; let settings_value = arg2.unwrap_or_else(|| v8::undefined(scope).into()); let settings = convert_settings(scope, settings_value, "Argument 3")?; if source_width == 0 { return Err(ImageDataError::ZeroWidth); } if source_height == 0 { return Err(ImageDataError::ZeroHeight); } let data_obj = alloc_typed_array( scope, settings.pixel_format, source_width, source_height, )?; let color_space = settings.color_space.unwrap_or(PredefinedColorSpace::Srgb); Ok(ImageData { width: source_width, height: source_height, pixel_format: settings.pixel_format, color_space, data: v8::TracedReference::new(scope, data_obj), }) } } #[fast] #[getter] fn width(&self) -> u32 { self.width } #[fast] #[getter] fn height(&self) -> u32 { self.height } #[getter] fn data<'a>( &self, scope: &mut v8::PinScope<'a, '_>, ) -> v8::Local<'a, v8::Object> { self.data.get(scope).unwrap() } #[getter] #[string] fn pixel_format(&self) -> &'static str { self.pixel_format.name() } #[getter] #[string] fn color_space(&self) -> &'static str { self.color_space.name() } }