1use std::borrow::Cow;
6use std::vec::Vec;
7
8use dom_struct::dom_struct;
9use euclid::default::{Rect, Size2D};
10use js::context::JSContext;
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_and_cx};
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(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_and_cx(
143 Box::new(ImageData {
144 reflector_: Reflector::new(),
145 width,
146 height,
147 data: *data.into_box(),
148 pixel_format,
149 color_space,
150 }),
151 global,
152 proto,
153 cx,
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) -> &[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() as *const _;
180 &*ptr
181 }
182 }
183
184 #[expect(unsafe_code)]
186 pub(crate) unsafe fn get_rect(&self, rect: Rect<u32>) -> Cow<'_, [u8]> {
187 pixels::rgba8_get_rect(unsafe { self.as_slice() }, self.get_size().to_u32(), rect)
188 }
189
190 #[expect(unsafe_code)]
191 pub(crate) fn get_snapshot_rect(&self, rect: Rect<u32>) -> Snapshot {
192 Snapshot::from_vec(
193 rect.size,
194 SnapshotPixelFormat::RGBA,
195 SnapshotAlphaMode::Transparent {
196 premultiplied: false,
197 },
198 unsafe { self.get_rect(rect).into_owned() },
199 )
200 }
201
202 #[expect(unsafe_code)]
203 pub(crate) fn get_snapshot(&self) -> Snapshot {
204 Snapshot::from_vec(
205 self.get_size(),
206 SnapshotPixelFormat::RGBA,
207 SnapshotAlphaMode::Transparent {
208 premultiplied: false,
209 },
210 unsafe { self.as_slice().to_vec() },
211 )
212 }
213
214 #[expect(unsafe_code)]
215 pub(crate) fn to_shared_memory(&self) -> GenericSharedMemory {
216 GenericSharedMemory::from_bytes(unsafe { self.as_slice() })
218 }
219
220 #[expect(unsafe_code)]
221 pub(crate) fn to_vec(&self) -> Vec<u8> {
222 unsafe { self.as_slice() }.to_vec()
224 }
225}
226
227impl Serializable for ImageData {
228 type Index = ImageDataIndex;
229 type Data = SerializableImageData;
230
231 fn serialize(&self) -> Result<(ImageDataId, Self::Data), ()> {
233 let data = self.to_vec();
235
236 let serialized = SerializableImageData {
243 data,
244 width: self.width,
245 height: self.height,
246 };
247 Ok((ImageDataId::new(), serialized))
248 }
249
250 fn deserialize(
252 cx: &mut JSContext,
253 owner: &GlobalScope,
254 serialized: Self::Data,
255 ) -> Result<DomRoot<Self>, ()> {
256 ImageData::new(
262 cx,
263 owner,
264 serialized.width,
265 serialized.height,
266 Some(serialized.data),
267 )
268 .map_err(|_| ())
269 }
270
271 fn serialized_storage<'a>(
272 reader: StructuredData<'a, '_>,
273 ) -> &'a mut Option<FxHashMap<ImageDataId, Self::Data>> {
274 match reader {
275 StructuredData::Reader(r) => &mut r.image_data,
276 StructuredData::Writer(w) => &mut w.image_data,
277 }
278 }
279}
280
281impl ImageDataMethods<crate::DomTypeHolder> for ImageData {
282 fn Constructor(
284 cx: &mut JSContext,
285 global: &GlobalScope,
286 proto: Option<HandleObject>,
287 sw: u32,
288 sh: u32,
289 settings: &ImageDataSettings,
290 ) -> Fallible<DomRoot<Self>> {
291 if sw == 0 || sh == 0 {
293 return Err(Error::IndexSize(None));
294 }
295
296 pixels::compute_rgba8_byte_length_if_within_limit(sw as usize, sh as usize)
299 .ok_or(Error::IndexSize(None))?;
300
301 Self::initialize(cx, sw, sh, settings, None, None, global, proto)
304 }
305
306 fn Constructor_(
308 cx: &mut JSContext,
309 global: &GlobalScope,
310 proto: Option<HandleObject>,
311 data: CustomAutoRooterGuard<Uint8ClampedArray>,
312 sw: u32,
313 sh: Option<u32>,
314 settings: &ImageDataSettings,
315 ) -> Fallible<DomRoot<Self>> {
316 let bytes_per_pixel = match settings.pixelFormat {
318 ImageDataPixelFormat::Rgba_unorm8 => 4,
319 };
320 let length = data.len();
322 if length == 0 {
323 return Err(Error::InvalidState(None));
324 }
325 if !length.is_multiple_of(bytes_per_pixel) {
328 return Err(Error::InvalidState(None));
329 }
330 let length = length / bytes_per_pixel;
332 if sw == 0 || !length.is_multiple_of(sw as usize) {
334 return Err(Error::IndexSize(None));
335 }
336 let height = length / sw as usize;
338 if sh.is_some_and(|x| height != x as usize) {
340 return Err(Error::IndexSize(None));
341 }
342 Self::initialize(
344 cx,
345 sw,
346 height as u32,
347 settings,
348 Some(data),
349 None,
350 global,
351 proto,
352 )
353 }
354
355 fn Width(&self) -> u32 {
357 self.width
358 }
359
360 fn Height(&self) -> u32 {
362 self.height
363 }
364
365 fn GetData(&self) -> Fallible<RootedTraceableBox<HeapUint8ClampedArray>> {
367 self.data.get_typed_array().map_err(|_| Error::JSFailed)
368 }
369
370 fn PixelFormat(&self) -> ImageDataPixelFormat {
372 self.pixel_format
373 }
374
375 fn ColorSpace(&self) -> PredefinedColorSpace {
377 self.color_space
378 }
379}