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;
19use servo_base::generic_channel::GenericSharedMemory;
20use servo_base::id::{ImageDataId, ImageDataIndex};
21use servo_constellation_traits::SerializableImageData;
22
23use crate::dom::bindings::buffer_source::{
24 HeapBufferSource, create_buffer_source, create_heap_buffer_source_with_length,
25};
26use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
27 ImageDataMethods, ImageDataPixelFormat, ImageDataSettings, PredefinedColorSpace,
28};
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::bindings::serializable::Serializable;
32use crate::dom::bindings::structuredclone::StructuredData;
33use crate::dom::globalscope::GlobalScope;
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 cx: &mut JSContext,
50 global: &GlobalScope,
51 width: u32,
52 height: u32,
53 mut data: Option<Vec<u8>>,
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 rooted!(&in(cx) let mut js_object = std::ptr::null_mut::<JSObject>());
70 let _buffer_source =
71 create_buffer_source::<ClampedU8>(cx, &d[..], js_object.handle_mut())
72 .map_err(|_| Error::JSFailed)?;
73 auto_root!(&in(cx) let data = TypedArray::<ClampedU8, *mut JSObject>::from(js_object.get()).map_err(|_| Error::JSFailed)?);
74
75 Self::Constructor_(cx, global, None, data, width, Some(height), &settings)
76 } else {
77 Self::Constructor(cx, global, None, width, height, &settings)
78 }
79 }
80
81 #[allow(clippy::too_many_arguments)]
82 fn initialize(
84 cx: &mut JSContext,
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 ) -> Fallible<DomRoot<ImageData>> {
93 let data = if let Some(source) = source {
95 if !matches!(settings.pixelFormat, ImageDataPixelFormat::Rgba_unorm8) {
100 return Err(Error::InvalidState(None));
102 }
103 HeapBufferSource::<ClampedU8>::from_view(cx, source)
105 } else {
106 match settings.pixelFormat {
108 ImageDataPixelFormat::Rgba_unorm8 => {
109 create_heap_buffer_source_with_length(cx, 4 * rows * pixels_per_row)?
117 },
118 }
125 };
126 let width = pixels_per_row;
128 let height = rows;
130 let pixel_format = settings.pixelFormat;
132 let color_space = settings
135 .colorSpace
136 .or(default_color_space)
139 .unwrap_or(PredefinedColorSpace::Srgb);
141
142 Ok(reflect_dom_object_with_proto(
143 cx,
144 Box::new(ImageData {
145 reflector_: Reflector::new(),
146 width,
147 height,
148 data: *data.into_box(),
149 pixel_format,
150 color_space,
151 }),
152 global,
153 proto,
154 ))
155 }
156
157 pub(crate) fn is_detached(&self, cx: &mut JSContext) -> bool {
158 self.data.is_detached_buffer(cx)
159 }
160
161 pub(crate) fn get_size(&self) -> Size2D<u32> {
162 Size2D::new(self.Width(), self.Height())
163 }
164
165 #[expect(unsafe_code)]
167 pub(crate) unsafe fn as_slice(&self, no_gc: &NoGC) -> &[u8] {
168 assert!(self.data.is_initialized());
169 let internal_data = self
170 .data
171 .get_typed_array()
172 .expect("Failed to get Data from ImageData.");
173 unsafe {
179 let ptr: *const [u8] = internal_data.as_slice_safe(no_gc).unwrap_or(&[]) as *const _;
180 &*ptr
181 }
182 }
183
184 #[expect(unsafe_code)]
186 pub(crate) unsafe fn get_rect(&self, no_gc: &NoGC, rect: Rect<u32>) -> Cow<'_, [u8]> {
187 pixels::rgba8_get_rect(
188 unsafe { self.as_slice(no_gc) },
189 self.get_size().to_u32(),
190 rect,
191 )
192 }
193
194 #[expect(unsafe_code)]
195 pub(crate) fn get_snapshot_rect(&self, no_gc: &NoGC, rect: Rect<u32>) -> Snapshot {
196 Snapshot::from_vec(
197 rect.size,
198 SnapshotPixelFormat::RGBA,
199 SnapshotAlphaMode::Transparent {
200 premultiplied: false,
201 },
202 unsafe { self.get_rect(no_gc, rect).into_owned() },
203 )
204 }
205
206 #[expect(unsafe_code)]
207 pub(crate) fn get_snapshot(&self, no_gc: &NoGC) -> Snapshot {
208 Snapshot::from_vec(
209 self.get_size(),
210 SnapshotPixelFormat::RGBA,
211 SnapshotAlphaMode::Transparent {
212 premultiplied: false,
213 },
214 unsafe { self.as_slice(no_gc).to_vec() },
215 )
216 }
217
218 #[expect(unsafe_code)]
219 pub(crate) fn to_shared_memory(&self, no_gc: &NoGC) -> GenericSharedMemory {
220 GenericSharedMemory::from_bytes(unsafe { self.as_slice(no_gc) })
222 }
223
224 #[expect(unsafe_code)]
225 pub(crate) fn to_vec(&self, no_gc: &NoGC) -> Vec<u8> {
226 unsafe { self.as_slice(no_gc) }.to_vec()
228 }
229}
230
231impl Serializable for ImageData {
232 type Index = ImageDataIndex;
233 type Data = SerializableImageData;
234
235 fn serialize(&self, no_gc: &NoGC) -> Result<(ImageDataId, Self::Data), ()> {
237 let data = self.to_vec(no_gc);
239
240 let serialized = SerializableImageData {
247 data,
248 width: self.width,
249 height: self.height,
250 };
251 Ok((ImageDataId::new(), serialized))
252 }
253
254 fn deserialize(
256 cx: &mut JSContext,
257 owner: &GlobalScope,
258 serialized: Self::Data,
259 ) -> Result<DomRoot<Self>, ()> {
260 ImageData::new(
266 cx,
267 owner,
268 serialized.width,
269 serialized.height,
270 Some(serialized.data),
271 )
272 .map_err(|_| ())
273 }
274
275 fn serialized_storage<'a>(
276 reader: StructuredData<'a, '_>,
277 ) -> &'a mut Option<FxHashMap<ImageDataId, Self::Data>> {
278 match reader {
279 StructuredData::Reader(r) => &mut r.image_data,
280 StructuredData::Writer(w) => &mut w.image_data,
281 }
282 }
283}
284
285impl ImageDataMethods<crate::DomTypeHolder> for ImageData {
286 fn Constructor(
288 cx: &mut JSContext,
289 global: &GlobalScope,
290 proto: Option<HandleObject>,
291 sw: u32,
292 sh: u32,
293 settings: &ImageDataSettings,
294 ) -> Fallible<DomRoot<Self>> {
295 if sw == 0 || sh == 0 {
297 return Err(Error::IndexSize(None));
298 }
299
300 pixels::compute_rgba8_byte_length_if_within_limit(sw as usize, sh as usize)
303 .ok_or(Error::IndexSize(None))?;
304
305 Self::initialize(cx, sw, sh, settings, None, None, global, proto)
308 }
309
310 fn Constructor_(
312 cx: &mut JSContext,
313 global: &GlobalScope,
314 proto: Option<HandleObject>,
315 data: CustomAutoRooterGuard<Uint8ClampedArray>,
316 sw: u32,
317 sh: Option<u32>,
318 settings: &ImageDataSettings,
319 ) -> Fallible<DomRoot<Self>> {
320 let bytes_per_pixel = match settings.pixelFormat {
322 ImageDataPixelFormat::Rgba_unorm8 => 4,
323 };
324 let length = data.len();
326 if length == 0 {
327 return Err(Error::InvalidState(None));
328 }
329 if !length.is_multiple_of(bytes_per_pixel) {
332 return Err(Error::InvalidState(None));
333 }
334 let length = length / bytes_per_pixel;
336 if sw == 0 || !length.is_multiple_of(sw as usize) {
338 return Err(Error::IndexSize(None));
339 }
340 let height = length / sw as usize;
342 if sh.is_some_and(|x| height != x as usize) {
344 return Err(Error::IndexSize(None));
345 }
346 Self::initialize(
348 cx,
349 sw,
350 height as u32,
351 settings,
352 Some(data),
353 None,
354 global,
355 proto,
356 )
357 }
358
359 fn Width(&self) -> u32 {
361 self.width
362 }
363
364 fn Height(&self) -> u32 {
366 self.height
367 }
368
369 fn GetData(&self) -> Fallible<RootedTraceableBox<HeapUint8ClampedArray>> {
371 self.data.get_typed_array().map_err(|_| Error::JSFailed)
372 }
373
374 fn PixelFormat(&self) -> ImageDataPixelFormat {
376 self.pixel_format
377 }
378
379 fn ColorSpace(&self) -> PredefinedColorSpace {
381 self.color_space
382 }
383}