LibWeb/HTML: Implement ImageData initialization closer to spec

This commit is contained in:
Lucas CHOLLET 2025-02-26 11:14:13 -07:00 committed by Sam Atkins
parent 569ebeb6a4
commit 715bf0de2c
Notes: github-actions[bot] 2025-03-05 11:36:24 +00:00
3 changed files with 57 additions and 22 deletions

View file

@ -19,28 +19,15 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(ImageData);
// https://html.spec.whatwg.org/multipage/canvas.html#dom-imagedata
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const&)
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings)
{
auto& vm = realm.vm();
// 1. If one or both of sw and sh are zero, then throw an "IndexSizeError" DOMException.
if (sw == 0 || sh == 0)
return WebIDL::IndexSizeError::create(realm, "The source width and height must be greater than zero."_string);
// 2. Initialize this given sw, sh, and settings set to settings.
// 3. Initialize the image data of this to transparent black.
//
// If the Canvas Pixel ArrayBuffer cannot be allocated, then rethrow the RangeError thrown by JavaScript, and return.
Checked<u32> size = sw;
size *= sh;
size *= sizeof(u32);
if (size.has_overflow())
return WebIDL::IndexSizeError::create(realm, "The specified image size could not created"_string);
auto data = TRY(JS::Uint8ClampedArray::create(realm, size.value()));
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(sw, sh), sw * sizeof(u32), data->data().data()));
return realm.create<ImageData>(realm, bitmap, data);
return initialize(realm, sh, sw, settings);
}
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::construct_impl(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings)
@ -49,7 +36,7 @@ WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::construct_impl(JS::Realm& rea
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-imagedata-with-data
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, GC::Root<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh, Optional<ImageDataSettings> const&)
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, GC::Root<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh, Optional<ImageDataSettings> const& settings)
{
auto& vm = realm.vm();
@ -82,9 +69,8 @@ WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, GC::
return WebIDL::IndexSizeError::create(realm, "Source height must be equal to the calculated height of the data."_string);
// 7. Initialize this given sw, sh, settings set to settings, and source set to data.
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(sw, height), sw * sizeof(u32), uint8_clamped_array_data.data().data()));
return realm.create<ImageData>(realm, bitmap, uint8_clamped_array_data);
// FIXME: This seems to be a spec issue, sh is an optional but height always have a value.
return initialize(realm, height, sw, settings, uint8_clamped_array_data);
}
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::construct_impl(JS::Realm& realm, GC::Root<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh, Optional<ImageDataSettings> const& settings)
@ -92,9 +78,53 @@ WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::construct_impl(JS::Realm& rea
return ImageData::create(realm, data, sw, move(sh), settings);
}
ImageData::ImageData(JS::Realm& realm, NonnullRefPtr<Gfx::Bitmap> bitmap, GC::Ref<JS::Uint8ClampedArray> data)
// https://html.spec.whatwg.org/multipage/canvas.html#initialize-an-imagedata-object
WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::initialize(JS::Realm& realm, u32 rows, u32 pixels_per_row, Optional<ImageDataSettings> const& settings, GC::Ptr<JS::Uint8ClampedArray> source, Optional<Bindings::PredefinedColorSpace> default_color_space)
{
auto data = TRY([&]() -> WebIDL::ExceptionOr<GC::Ref<JS::Uint8ClampedArray>> {
// 1. If source was given, then initialize the data attribute of imageData to source.
if (source) {
return GC::Ref<JS::Uint8ClampedArray> { *source };
}
Checked<u32> size = rows;
size *= pixels_per_row;
size *= sizeof(u32);
if (size.has_overflow())
return WebIDL::IndexSizeError::create(realm, "The specified image size could not created"_string);
// 2. Otherwise (source was not given), initialize the data attribute of imageData to a new Uint8ClampedArray object.
// The Uint8ClampedArray object must use a new Canvas Pixel ArrayBuffer for its storage, and must have a zero start
// offset and a length equal to the length of its storage, in bytes. The Canvas Pixel ArrayBuffer must have the
// correct size to store rows × pixelsPerRow pixels.
// 3. If the Canvas Pixel ArrayBuffer cannot be allocated, then rethrow the RangeError thrown by JavaScript, and return.
return TRY(JS::Uint8ClampedArray::create(realm, sizeof(u32) * rows * pixels_per_row));
}());
// AD-HOC: Create the bitmap backed by the Uint8ClampedArray.
auto bitmap = TRY_OR_THROW_OOM(realm.vm(), Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(pixels_per_row, rows), pixels_per_row * sizeof(u32), data->data().data()));
// 4. Initialize the width attribute of imageData to pixelsPerRow.
// 5. Initialize the height attribute of imageData to rows.
// 6. If settings was given and settings["colorSpace"] exists, then initialize the colorSpace attribute of imageData to settings["colorSpace"].
Bindings::PredefinedColorSpace color_space {};
if (settings.has_value())
color_space = settings->color_space;
// 7. Otherwise, if defaultColorSpace was given, then initialize the colorSpace attribute of imageData to defaultColorSpace.
else if (default_color_space.has_value())
color_space = *default_color_space;
// 8. Otherwise, initialize the colorSpace attribute of imageData to "srgb".
else
color_space = Bindings::PredefinedColorSpace::Srgb;
return realm.create<ImageData>(realm, move(bitmap), data, color_space);
}
ImageData::ImageData(JS::Realm& realm, NonnullRefPtr<Gfx::Bitmap> bitmap, GC::Ref<JS::Uint8ClampedArray> data, Bindings::PredefinedColorSpace color_space)
: PlatformObject(realm)
, m_bitmap(move(bitmap))
, m_color_space(color_space)
, m_data(move(data))
{
}

View file

@ -39,13 +39,18 @@ public:
JS::Uint8ClampedArray* data();
const JS::Uint8ClampedArray* data() const;
Bindings::PredefinedColorSpace color_space() const { return m_color_space; }
private:
ImageData(JS::Realm&, NonnullRefPtr<Gfx::Bitmap>, GC::Ref<JS::Uint8ClampedArray>);
[[nodiscard]] static WebIDL::ExceptionOr<GC::Ref<ImageData>> initialize(JS::Realm&, u32 rows, u32 pixels_per_row, Optional<ImageDataSettings> const&, GC::Ptr<JS::Uint8ClampedArray> = {}, Optional<Bindings::PredefinedColorSpace> = {});
ImageData(JS::Realm&, NonnullRefPtr<Gfx::Bitmap>, GC::Ref<JS::Uint8ClampedArray>, Bindings::PredefinedColorSpace);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
NonnullRefPtr<Gfx::Bitmap> m_bitmap;
Bindings::PredefinedColorSpace m_color_space;
GC::Ref<JS::Uint8ClampedArray> m_data;
};

View file

@ -13,5 +13,5 @@ interface ImageData {
readonly attribute unsigned long width;
readonly attribute unsigned long height;
readonly attribute Uint8ClampedArray data;
[FIXME] readonly attribute PredefinedColorSpace colorSpace;
readonly attribute PredefinedColorSpace colorSpace;
};