1use std::borrow::Cow;
6use std::vec::Vec;
7
8use base::generic_channel::GenericSharedMemory;
9use base::id::{ImageDataId, ImageDataIndex};
10use constellation_traits::SerializableImageData;
11use dom_struct::dom_struct;
12use euclid::default::{Rect, Size2D};
13use js::gc::CustomAutoRooterGuard;
14use js::jsapi::JSObject;
15use js::rust::HandleObject;
16use js::typedarray::{ClampedU8, HeapUint8ClampedArray, TypedArray, Uint8ClampedArray};
17use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
18use rustc_hash::FxHashMap;
19use script_bindings::trace::RootedTraceableBox;
20
21use crate::dom::bindings::buffer_source::{
22 HeapBufferSource, create_buffer_source, create_heap_buffer_source_with_length,
23};
24use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
25 ImageDataMethods, ImageDataPixelFormat, ImageDataSettings, PredefinedColorSpace,
26};
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
29use crate::dom::bindings::root::DomRoot;
30use crate::dom::bindings::serializable::Serializable;
31use crate::dom::bindings::structuredclone::StructuredData;
32use crate::dom::globalscope::GlobalScope;
33use crate::script_runtime::CanGc;
34
35#[dom_struct]
36pub(crate) struct ImageData {
37 reflector_: Reflector,
38 width: u32,
39 height: u32,
40 #[ignore_malloc_size_of = "mozjs"]
42 data: HeapBufferSource<ClampedU8>,
43 pixel_format: ImageDataPixelFormat,
44 color_space: PredefinedColorSpace,
45}
46
47impl ImageData {
48 pub(crate) fn new(
49 global: &GlobalScope,
50 width: u32,
51 height: u32,
52 mut data: Option<Vec<u8>>,
53 can_gc: CanGc,
54 ) -> Fallible<DomRoot<ImageData>> {
55 let len =
56 pixels::compute_rgba8_byte_length_if_within_limit(width as usize, height as usize)
57 .ok_or(Error::Range(
58 c"The requested image size exceeds the supported range".to_owned(),
59 ))?;
60
61 let settings = ImageDataSettings {
62 colorSpace: Some(PredefinedColorSpace::Srgb),
63 pixelFormat: ImageDataPixelFormat::Rgba_unorm8,
64 };
65
66 if let Some(ref mut d) = data {
67 d.resize(len as usize, 0);
68
69 let cx = GlobalScope::get_cx();
70 rooted!(in(*cx) let mut js_object = std::ptr::null_mut::<JSObject>());
71 let _buffer_source =
72 create_buffer_source::<ClampedU8>(cx, &d[..], js_object.handle_mut(), can_gc)
73 .map_err(|_| Error::JSFailed)?;
74 auto_root!(&in(cx) let data = TypedArray::<ClampedU8, *mut JSObject>::from(js_object.get()).map_err(|_| Error::JSFailed)?);
75
76 Self::Constructor_(global, None, can_gc, data, width, Some(height), &settings)
77 } else {
78 Self::Constructor(global, None, can_gc, width, height, &settings)
79 }
80 }
81
82 #[allow(clippy::too_many_arguments)]
83 fn initialize(
85 pixels_per_row: u32,
86 rows: u32,
87 settings: &ImageDataSettings,
88 source: Option<CustomAutoRooterGuard<Uint8ClampedArray>>,
89 default_color_space: Option<PredefinedColorSpace>,
90 global: &GlobalScope,
91 proto: Option<HandleObject>,
92 can_gc: CanGc,
93 ) -> Fallible<DomRoot<ImageData>> {
94 let data = if let Some(source) = source {
96 if !matches!(settings.pixelFormat, ImageDataPixelFormat::Rgba_unorm8) {
101 return Err(Error::InvalidState(None));
103 }
104 HeapBufferSource::<ClampedU8>::from_view(source)
106 } else {
107 match settings.pixelFormat {
109 ImageDataPixelFormat::Rgba_unorm8 => {
110 create_heap_buffer_source_with_length(
118 GlobalScope::get_cx(),
119 4 * rows * pixels_per_row,
120 can_gc,
121 )?
122 },
123 }
130 };
131 let width = pixels_per_row;
133 let height = rows;
135 let pixel_format = settings.pixelFormat;
137 let color_space = settings
140 .colorSpace
141 .or(default_color_space)
144 .unwrap_or(PredefinedColorSpace::Srgb);
146
147 Ok(reflect_dom_object_with_proto(
148 Box::new(ImageData {
149 reflector_: Reflector::new(),
150 width,
151 height,
152 data,
153 pixel_format,
154 color_space,
155 }),
156 global,
157 proto,
158 can_gc,
159 ))
160 }
161
162 pub(crate) fn is_detached(&self) -> bool {
163 self.data.is_detached_buffer(GlobalScope::get_cx())
164 }
165
166 pub(crate) fn get_size(&self) -> Size2D<u32> {
167 Size2D::new(self.Width(), self.Height())
168 }
169
170 #[expect(unsafe_code)]
172 pub(crate) unsafe fn as_slice(&self) -> &[u8] {
173 assert!(self.data.is_initialized());
174 let internal_data = self
175 .data
176 .get_typed_array()
177 .expect("Failed to get Data from ImageData.");
178 unsafe {
184 let ptr: *const [u8] = internal_data.as_slice() as *const _;
185 &*ptr
186 }
187 }
188
189 #[expect(unsafe_code)]
191 pub(crate) unsafe fn get_rect(&self, rect: Rect<u32>) -> Cow<'_, [u8]> {
192 pixels::rgba8_get_rect(unsafe { self.as_slice() }, self.get_size().to_u32(), rect)
193 }
194
195 #[expect(unsafe_code)]
196 pub(crate) fn get_snapshot_rect(&self, rect: Rect<u32>) -> Snapshot {
197 Snapshot::from_vec(
198 rect.size,
199 SnapshotPixelFormat::RGBA,
200 SnapshotAlphaMode::Transparent {
201 premultiplied: false,
202 },
203 unsafe { self.get_rect(rect).into_owned() },
204 )
205 }
206
207 #[expect(unsafe_code)]
208 pub(crate) fn to_shared_memory(&self) -> GenericSharedMemory {
209 GenericSharedMemory::from_bytes(unsafe { self.as_slice() })
211 }
212
213 #[expect(unsafe_code)]
214 pub(crate) fn to_vec(&self) -> Vec<u8> {
215 unsafe { self.as_slice() }.to_vec()
217 }
218}
219
220impl Serializable for ImageData {
221 type Index = ImageDataIndex;
222 type Data = SerializableImageData;
223
224 fn serialize(&self) -> Result<(ImageDataId, Self::Data), ()> {
226 let data = self.to_vec();
228
229 let serialized = SerializableImageData {
236 data,
237 width: self.width,
238 height: self.height,
239 };
240 Ok((ImageDataId::new(), serialized))
241 }
242
243 fn deserialize(
245 owner: &GlobalScope,
246 serialized: Self::Data,
247 can_gc: CanGc,
248 ) -> Result<DomRoot<Self>, ()> {
249 ImageData::new(
255 owner,
256 serialized.width,
257 serialized.height,
258 Some(serialized.data),
259 can_gc,
260 )
261 .map_err(|_| ())
262 }
263
264 fn serialized_storage<'a>(
265 reader: StructuredData<'a, '_>,
266 ) -> &'a mut Option<FxHashMap<ImageDataId, Self::Data>> {
267 match reader {
268 StructuredData::Reader(r) => &mut r.image_data,
269 StructuredData::Writer(w) => &mut w.image_data,
270 }
271 }
272}
273
274impl ImageDataMethods<crate::DomTypeHolder> for ImageData {
275 fn Constructor(
277 global: &GlobalScope,
278 proto: Option<HandleObject>,
279 can_gc: CanGc,
280 sw: u32,
281 sh: u32,
282 settings: &ImageDataSettings,
283 ) -> Fallible<DomRoot<Self>> {
284 if sw == 0 || sh == 0 {
286 return Err(Error::IndexSize(None));
287 }
288
289 pixels::compute_rgba8_byte_length_if_within_limit(sw as usize, sh as usize)
292 .ok_or(Error::IndexSize(None))?;
293
294 Self::initialize(sw, sh, settings, None, None, global, proto, can_gc)
297 }
298
299 fn Constructor_(
301 global: &GlobalScope,
302 proto: Option<HandleObject>,
303 can_gc: CanGc,
304 data: CustomAutoRooterGuard<Uint8ClampedArray>,
305 sw: u32,
306 sh: Option<u32>,
307 settings: &ImageDataSettings,
308 ) -> Fallible<DomRoot<Self>> {
309 let bytes_per_pixel = match settings.pixelFormat {
311 ImageDataPixelFormat::Rgba_unorm8 => 4,
312 };
313 let length = data.len();
315 if length == 0 {
316 return Err(Error::InvalidState(None));
317 }
318 if length % bytes_per_pixel != 0 {
321 return Err(Error::InvalidState(None));
322 }
323 let length = length / bytes_per_pixel;
325 if sw == 0 || length % sw as usize != 0 {
327 return Err(Error::IndexSize(None));
328 }
329 let height = length / sw as usize;
331 if sh.is_some_and(|x| height != x as usize) {
333 return Err(Error::IndexSize(None));
334 }
335 Self::initialize(
337 sw,
338 height as u32,
339 settings,
340 Some(data),
341 None,
342 global,
343 proto,
344 can_gc,
345 )
346 }
347
348 fn Width(&self) -> u32 {
350 self.width
351 }
352
353 fn Height(&self) -> u32 {
355 self.height
356 }
357
358 fn GetData(
360 &self,
361 _: script_bindings::script_runtime::JSContext,
362 ) -> Fallible<RootedTraceableBox<HeapUint8ClampedArray>> {
363 self.data.get_typed_array().map_err(|_| Error::JSFailed)
364 }
365
366 fn PixelFormat(&self) -> ImageDataPixelFormat {
368 self.pixel_format
369 }
370
371 fn ColorSpace(&self) -> PredefinedColorSpace {
373 self.color_space
374 }
375}