1use std::borrow::Cow;
6use std::vec::Vec;
7
8use dom_struct::dom_struct;
9use euclid::default::{Rect, Size2D};
10use js::context::{JSContext, NoGC};
11use js::gc::CustomAutoRooterGuard;
12use js::jsapi::JSObject;
13use js::rust::HandleObject;
14use js::typedarray::{ClampedU8, HeapUint8ClampedArray, TypedArray, Uint8ClampedArray};
15use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
16use rustc_hash::FxHashMap;
17use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
18use script_bindings::trace::RootedTraceableBox;
19#[cfg(feature = "webgl")]
20use servo_base::generic_channel::GenericSharedMemory;
21use servo_base::id::{ImageDataId, ImageDataIndex};
22use servo_constellation_traits::SerializableImageData;
23
24use crate::dom::bindings::buffer_source::{
25 HeapBufferSource, create_buffer_source, create_heap_buffer_source_with_length,
26};
27use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
28 ImageDataMethods, ImageDataPixelFormat, ImageDataSettings, PredefinedColorSpace,
29};
30use crate::dom::bindings::error::{Error, Fallible};
31use crate::dom::bindings::root::DomRoot;
32use crate::dom::bindings::serializable::Serializable;
33use crate::dom::bindings::structuredclone::StructuredData;
34use crate::dom::globalscope::GlobalScope;
35
36#[dom_struct]
37pub(crate) struct ImageData {
38 reflector_: Reflector,
39 width: u32,
40 height: u32,
41 #[ignore_malloc_size_of = "mozjs"]
43 data: HeapBufferSource<ClampedU8>,
44 pixel_format: ImageDataPixelFormat,
45 color_space: PredefinedColorSpace,
46}
47
48impl ImageData {
49 pub(crate) fn new(
50 cx: &mut JSContext,
51 global: &GlobalScope,
52 width: u32,
53 height: u32,
54 mut data: Option<Vec<u8>>,
55 ) -> Fallible<DomRoot<ImageData>> {
56 let len =
57 pixels::compute_rgba8_byte_length_if_within_limit(width as usize, height as usize)
58 .ok_or(Error::Range(
59 c"The requested image size exceeds the supported range".to_owned(),
60 ))?;
61
62 let settings = ImageDataSettings {
63 colorSpace: Some(PredefinedColorSpace::Srgb),
64 pixelFormat: ImageDataPixelFormat::Rgba_unorm8,
65 };
66
67 if let Some(ref mut d) = data {
68 d.resize(len as usize, 0);
69
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())
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_(cx, global, None, data, width, Some(height), &settings)
77 } else {
78 Self::Constructor(cx, global, None, width, height, &settings)
79 }
80 }
81
82 #[allow(clippy::too_many_arguments)]
83 fn initialize(
85 cx: &mut JSContext,
86 pixels_per_row: u32,
87 rows: u32,
88 settings: &ImageDataSettings,
89 source: Option<CustomAutoRooterGuard<Uint8ClampedArray>>,
90 default_color_space: Option<PredefinedColorSpace>,
91 global: &GlobalScope,
92 proto: Option<HandleObject>,
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(cx, source)
106 } else {
107 match settings.pixelFormat {
109 ImageDataPixelFormat::Rgba_unorm8 => {
110 create_heap_buffer_source_with_length(cx, 4 * rows * pixels_per_row)?
118 },
119 }
126 };
127 let width = pixels_per_row;
129 let height = rows;
131 let pixel_format = settings.pixelFormat;
133 let color_space = settings
136 .colorSpace
137 .or(default_color_space)
140 .unwrap_or(PredefinedColorSpace::Srgb);
142
143 Ok(reflect_dom_object_with_proto(
144 cx,
145 Box::new(ImageData {
146 reflector_: Reflector::new(),
147 width,
148 height,
149 data: *data.into_box(),
150 pixel_format,
151 color_space,
152 }),
153 global,
154 proto,
155 ))
156 }
157
158 pub(crate) fn is_detached(&self, cx: &mut JSContext) -> bool {
159 self.data.is_detached_buffer(cx)
160 }
161
162 pub(crate) fn get_size(&self) -> Size2D<u32> {
163 Size2D::new(self.Width(), self.Height())
164 }
165
166 #[expect(unsafe_code)]
168 pub(crate) unsafe fn as_slice(&self, no_gc: &NoGC) -> &[u8] {
169 assert!(self.data.is_initialized());
170 let internal_data = self
171 .data
172 .get_typed_array()
173 .expect("Failed to get Data from ImageData.");
174 unsafe {
180 let ptr: *const [u8] = internal_data.as_slice_safe(no_gc).unwrap_or(&[]) as *const _;
181 &*ptr
182 }
183 }
184
185 #[expect(unsafe_code)]
187 pub(crate) unsafe fn get_rect(&self, no_gc: &NoGC, rect: Rect<u32>) -> Cow<'_, [u8]> {
188 pixels::rgba8_get_rect(
189 unsafe { self.as_slice(no_gc) },
190 self.get_size().to_u32(),
191 rect,
192 )
193 }
194
195 #[expect(unsafe_code)]
196 pub(crate) fn get_snapshot_rect(&self, no_gc: &NoGC, 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(no_gc, rect).into_owned() },
204 )
205 }
206
207 #[expect(unsafe_code)]
208 pub(crate) fn get_snapshot(&self, no_gc: &NoGC) -> Snapshot {
209 Snapshot::from_vec(
210 self.get_size(),
211 SnapshotPixelFormat::RGBA,
212 SnapshotAlphaMode::Transparent {
213 premultiplied: false,
214 },
215 unsafe { self.as_slice(no_gc).to_vec() },
216 )
217 }
218
219 #[expect(unsafe_code)]
220 #[cfg(feature = "webgl")]
221 pub(crate) fn to_shared_memory(&self, no_gc: &NoGC) -> GenericSharedMemory {
222 GenericSharedMemory::from_bytes(unsafe { self.as_slice(no_gc) })
224 }
225
226 #[expect(unsafe_code)]
227 pub(crate) fn to_vec(&self, no_gc: &NoGC) -> Vec<u8> {
228 unsafe { self.as_slice(no_gc) }.to_vec()
230 }
231}
232
233impl Serializable for ImageData {
234 type Index = ImageDataIndex;
235 type Data = SerializableImageData;
236
237 fn serialize(&self, no_gc: &NoGC) -> Result<(ImageDataId, Self::Data), ()> {
239 let data = self.to_vec(no_gc);
241
242 let serialized = SerializableImageData {
249 data,
250 width: self.width,
251 height: self.height,
252 };
253 Ok((ImageDataId::new(), serialized))
254 }
255
256 fn deserialize(
258 cx: &mut JSContext,
259 owner: &GlobalScope,
260 serialized: Self::Data,
261 ) -> Result<DomRoot<Self>, ()> {
262 ImageData::new(
268 cx,
269 owner,
270 serialized.width,
271 serialized.height,
272 Some(serialized.data),
273 )
274 .map_err(|_| ())
275 }
276
277 fn serialized_storage<'a>(
278 reader: StructuredData<'a, '_>,
279 ) -> &'a mut Option<FxHashMap<ImageDataId, Self::Data>> {
280 match reader {
281 StructuredData::Reader(r) => &mut r.image_data,
282 StructuredData::Writer(w) => &mut w.image_data,
283 }
284 }
285}
286
287impl ImageDataMethods<crate::DomTypeHolder> for ImageData {
288 fn Constructor(
290 cx: &mut JSContext,
291 global: &GlobalScope,
292 proto: Option<HandleObject>,
293 sw: u32,
294 sh: u32,
295 settings: &ImageDataSettings,
296 ) -> Fallible<DomRoot<Self>> {
297 if sw == 0 || sh == 0 {
299 return Err(Error::IndexSize(None));
300 }
301
302 pixels::compute_rgba8_byte_length_if_within_limit(sw as usize, sh as usize)
305 .ok_or(Error::IndexSize(None))?;
306
307 Self::initialize(cx, sw, sh, settings, None, None, global, proto)
310 }
311
312 fn Constructor_(
314 cx: &mut JSContext,
315 global: &GlobalScope,
316 proto: Option<HandleObject>,
317 data: CustomAutoRooterGuard<Uint8ClampedArray>,
318 sw: u32,
319 sh: Option<u32>,
320 settings: &ImageDataSettings,
321 ) -> Fallible<DomRoot<Self>> {
322 let bytes_per_pixel = match settings.pixelFormat {
324 ImageDataPixelFormat::Rgba_unorm8 => 4,
325 };
326 let length = data.len();
328 if length == 0 {
329 return Err(Error::InvalidState(None));
330 }
331 if !length.is_multiple_of(bytes_per_pixel) {
334 return Err(Error::InvalidState(None));
335 }
336 let length = length / bytes_per_pixel;
338 if sw == 0 || !length.is_multiple_of(sw as usize) {
340 return Err(Error::IndexSize(None));
341 }
342 let height = length / sw as usize;
344 if sh.is_some_and(|x| height != x as usize) {
346 return Err(Error::IndexSize(None));
347 }
348 Self::initialize(
350 cx,
351 sw,
352 height as u32,
353 settings,
354 Some(data),
355 None,
356 global,
357 proto,
358 )
359 }
360
361 fn Width(&self) -> u32 {
363 self.width
364 }
365
366 fn Height(&self) -> u32 {
368 self.height
369 }
370
371 fn GetData(&self) -> Fallible<RootedTraceableBox<HeapUint8ClampedArray>> {
373 self.data.get_typed_array().map_err(|_| Error::JSFailed)
374 }
375
376 fn PixelFormat(&self) -> ImageDataPixelFormat {
378 self.pixel_format
379 }
380
381 fn ColorSpace(&self) -> PredefinedColorSpace {
383 self.color_space
384 }
385}