LibWeb: Add {,de}serialization steps for ImageData

This commit is contained in:
Kenneth Myhra 2025-05-04 13:45:51 +02:00 committed by Shannon Booth
commit 6941b63890
Notes: github-actions[bot] 2025-05-08 14:13:01 +00:00
7 changed files with 131 additions and 25 deletions

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org> * Copyright (c) 2024-2025, Kenneth Myhra <kennethmyhra@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -10,6 +10,7 @@
#include <LibWeb/Bindings/ImageDataPrototype.h> #include <LibWeb/Bindings/ImageDataPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/ImageData.h> #include <LibWeb/HTML/ImageData.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/WebIDL/Buffers.h> #include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/DOMException.h> #include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h> #include <LibWeb/WebIDL/ExceptionOr.h>
@ -18,6 +19,16 @@ namespace Web::HTML {
GC_DEFINE_ALLOCATOR(ImageData); GC_DEFINE_ALLOCATOR(ImageData);
[[nodiscard]] static auto create_bitmap_backed_by_uint8_clamped_array(u32 const width, u32 const height, JS::Uint8ClampedArray& data)
{
return Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(width, height), width * sizeof(u32), data.data().data());
}
GC::Ref<ImageData> ImageData::create(JS::Realm& realm)
{
return realm.create<ImageData>(realm);
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-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& settings) WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::create(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings)
{ {
@ -102,7 +113,7 @@ WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::initialize(JS::Realm& realm,
}()); }());
// AD-HOC: Create the bitmap backed by the Uint8ClampedArray. // 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())); auto bitmap = TRY_OR_THROW_OOM(realm.vm(), create_bitmap_backed_by_uint8_clamped_array(pixels_per_row, rows, *data));
// 4. Initialize the width attribute of imageData to pixelsPerRow. // 4. Initialize the width attribute of imageData to pixelsPerRow.
// 5. Initialize the height attribute of imageData to rows. // 5. Initialize the height attribute of imageData to rows.
@ -121,6 +132,11 @@ WebIDL::ExceptionOr<GC::Ref<ImageData>> ImageData::initialize(JS::Realm& realm,
return realm.create<ImageData>(realm, move(bitmap), data, color_space); return realm.create<ImageData>(realm, move(bitmap), data, color_space);
} }
ImageData::ImageData(JS::Realm& realm)
: PlatformObject(realm)
{
}
ImageData::ImageData(JS::Realm& realm, NonnullRefPtr<Gfx::Bitmap> bitmap, GC::Ref<JS::Uint8ClampedArray> data, Bindings::PredefinedColorSpace color_space) ImageData::ImageData(JS::Realm& realm, NonnullRefPtr<Gfx::Bitmap> bitmap, GC::Ref<JS::Uint8ClampedArray> data, Bindings::PredefinedColorSpace color_space)
: PlatformObject(realm) : PlatformObject(realm)
, m_bitmap(move(bitmap)) , m_bitmap(move(bitmap))
@ -163,4 +179,57 @@ const JS::Uint8ClampedArray* ImageData::data() const
return m_data; return m_data;
} }
// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation:serialization-steps
WebIDL::ExceptionOr<void> ImageData::serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory& memory)
{
auto& vm = this->vm();
// 1. Set serialized.[[Data]] to the sub-serialization of the value of value's data attribute.
auto serialized_data = TRY(structured_serialize_internal(vm, m_data, for_storage, memory));
serialized.extend(move(serialized_data));
// 2. Set serialized.[[Width]] to the value of value's width attribute.
serialize_primitive_type(serialized, m_bitmap->width());
// 3. Set serialized.[[Height]] to the value of value's height attribute.
serialize_primitive_type(serialized, m_bitmap->height());
// 4. Set serialized.[[ColorSpace]] to the value of value's colorSpace attribute.
serialize_enum(serialized, m_color_space);
// FIXME:: 5. Set serialized.[[PixelFormat]] to the value of value's pixelFormat attribute.
return {};
}
// https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation:deserialization-steps
WebIDL::ExceptionOr<void> ImageData::deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, DeserializationMemory& memory)
{
auto& vm = this->vm();
auto& realm = this->realm();
// 1. Initialize value's data attribute to the sub-deserialization of serialized.[[Data]].
auto [value, position_after_deserialization] = TRY(structured_deserialize_internal(vm, serialized, realm, memory, position));
if (value.has_value() && value.value().is_object() && is<JS::Uint8ClampedArray>(value.value().as_object())) {
m_data = as<JS::Uint8ClampedArray>(value.release_value().as_object());
}
position = position_after_deserialization;
// 2. Initialize value's width attribute to serialized.[[Width]].
auto const width = deserialize_primitive_type<int>(serialized, position);
// 3. Initialize value's height attribute to serialized.[[Height]].
auto const height = deserialize_primitive_type<int>(serialized, position);
// 4. Initialize value's colorSpace attribute to serialized.[[ColorSpace]].
m_color_space = deserialize_primitive_type<Bindings::PredefinedColorSpace>(serialized, position);
// FIXME: 5. Initialize value's pixelFormat attribute to serialized.[[PixelFormat]].
// AD-HOC: Create the bitmap backed by the Uint8ClampedArray.
m_bitmap = TRY_OR_THROW_OOM(vm, create_bitmap_backed_by_uint8_clamped_array(width, height, *m_data));
return {};
}
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org> * Copyright (c) 2024-2025, Kenneth Myhra <kennethmyhra@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -10,6 +10,7 @@
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibWeb/Bindings/ImageDataPrototype.h> #include <LibWeb/Bindings/ImageDataPrototype.h>
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/Serializable.h>
#include <LibWeb/WebIDL/Types.h> #include <LibWeb/WebIDL/Types.h>
namespace Web::HTML { namespace Web::HTML {
@ -18,11 +19,14 @@ struct ImageDataSettings {
Bindings::PredefinedColorSpace color_space; Bindings::PredefinedColorSpace color_space;
}; };
class ImageData final : public Bindings::PlatformObject { class ImageData final
: public Bindings::PlatformObject
, public Bindings::Serializable {
WEB_PLATFORM_OBJECT(ImageData, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(ImageData, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(ImageData); GC_DECLARE_ALLOCATOR(ImageData);
public: public:
[[nodiscard]] static GC::Ref<ImageData> create(JS::Realm&);
[[nodiscard]] static WebIDL::ExceptionOr<GC::Ref<ImageData>> create(JS::Realm&, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings = {}); [[nodiscard]] static WebIDL::ExceptionOr<GC::Ref<ImageData>> create(JS::Realm&, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings = {});
[[nodiscard]] static WebIDL::ExceptionOr<GC::Ref<ImageData>> create(JS::Realm&, GC::Root<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh = {}, Optional<ImageDataSettings> const& settings = {}); [[nodiscard]] static WebIDL::ExceptionOr<GC::Ref<ImageData>> create(JS::Realm&, GC::Root<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh = {}, Optional<ImageDataSettings> const& settings = {});
@ -34,25 +38,30 @@ public:
WebIDL::UnsignedLong width() const; WebIDL::UnsignedLong width() const;
WebIDL::UnsignedLong height() const; WebIDL::UnsignedLong height() const;
Gfx::Bitmap& bitmap() { return m_bitmap; } Gfx::Bitmap& bitmap() { return *m_bitmap; }
Gfx::Bitmap const& bitmap() const { return m_bitmap; } Gfx::Bitmap const& bitmap() const { return *m_bitmap; }
JS::Uint8ClampedArray* data(); JS::Uint8ClampedArray* data();
const JS::Uint8ClampedArray* data() const; const JS::Uint8ClampedArray* data() const;
Bindings::PredefinedColorSpace color_space() const { return m_color_space; } Bindings::PredefinedColorSpace color_space() const { return m_color_space; }
virtual StringView interface_name() const override { return "ImageData"sv; }
virtual WebIDL::ExceptionOr<void> serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, DeserializationMemory&) override;
private: private:
[[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> = {}); [[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> = {});
explicit ImageData(JS::Realm&);
ImageData(JS::Realm&, NonnullRefPtr<Gfx::Bitmap>, GC::Ref<JS::Uint8ClampedArray>, Bindings::PredefinedColorSpace); ImageData(JS::Realm&, NonnullRefPtr<Gfx::Bitmap>, GC::Ref<JS::Uint8ClampedArray>, Bindings::PredefinedColorSpace);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
NonnullRefPtr<Gfx::Bitmap> m_bitmap; RefPtr<Gfx::Bitmap> m_bitmap;
Bindings::PredefinedColorSpace m_color_space; Bindings::PredefinedColorSpace m_color_space { Bindings::PredefinedColorSpace::Srgb };
GC::Ref<JS::Uint8ClampedArray> m_data; GC::Ptr<JS::Uint8ClampedArray> m_data;
}; };
} }

View file

@ -46,6 +46,7 @@
#include <LibWeb/Geometry/DOMQuad.h> #include <LibWeb/Geometry/DOMQuad.h>
#include <LibWeb/Geometry/DOMRect.h> #include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectReadOnly.h> #include <LibWeb/Geometry/DOMRectReadOnly.h>
#include <LibWeb/HTML/ImageData.h>
#include <LibWeb/HTML/MessagePort.h> #include <LibWeb/HTML/MessagePort.h>
#include <LibWeb/HTML/StructuredSerialize.h> #include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/WebIDL/DOMException.h> #include <LibWeb/WebIDL/DOMException.h>
@ -1069,6 +1070,8 @@ private:
return Crypto::CryptoKey::create(realm); return Crypto::CryptoKey::create(realm);
if (interface_name == "DOMQuad"sv) if (interface_name == "DOMQuad"sv)
return Geometry::DOMQuad::create(realm); return Geometry::DOMQuad::create(realm);
if (interface_name == "ImageData"sv)
return ImageData::create(realm);
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -29,3 +29,10 @@ CryptoKey.algorithm: {"name":"PBKDF2"}
CryptoKey.usages: ["deriveKey","deriveBits"] CryptoKey.usages: ["deriveKey","deriveBits"]
instanceOf DOMException: true instanceOf DOMException: true
DOMException: Index out of bounds - IndexSizeError DOMException: Index out of bounds - IndexSizeError
instanceOf ImageData: true
ImageData.width: 2
ImageData.height: 2
255, 0, 0, 255
0, 255, 0, 255
0, 0, 255, 255
255, 255, 0, 255

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 150 tests Found 150 tests
134 Pass 140 Pass
15 Fail 9 Fail
1 Optional Feature Unsupported 1 Optional Feature Unsupported
Pass primitive undefined Pass primitive undefined
Pass primitive null Pass primitive null
@ -109,12 +109,12 @@ Pass File basic
Pass FileList empty Pass FileList empty
Pass Array FileList object, FileList empty Pass Array FileList object, FileList empty
Pass Object FileList object, FileList empty Pass Object FileList object, FileList empty
Fail ImageData 1x1 transparent black Pass ImageData 1x1 transparent black
Fail ImageData 1x1 non-transparent non-black Pass ImageData 1x1 non-transparent non-black
Fail Array ImageData object, ImageData 1x1 transparent black Pass Array ImageData object, ImageData 1x1 transparent black
Fail Array ImageData object, ImageData 1x1 non-transparent non-black Pass Array ImageData object, ImageData 1x1 non-transparent non-black
Fail Object ImageData object, ImageData 1x1 transparent black Pass Object ImageData object, ImageData 1x1 transparent black
Fail Object ImageData object, ImageData 1x1 non-transparent non-black Pass Object ImageData object, ImageData 1x1 non-transparent non-black
Pass Array sparse Pass Array sparse
Pass Array with non-index property Pass Array with non-index property
Pass Object with index property and length Pass Object with index property and length

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 150 tests Found 150 tests
134 Pass 140 Pass
15 Fail 9 Fail
1 Optional Feature Unsupported 1 Optional Feature Unsupported
Pass primitive undefined Pass primitive undefined
Pass primitive null Pass primitive null
@ -109,12 +109,12 @@ Pass File basic
Pass FileList empty Pass FileList empty
Pass Array FileList object, FileList empty Pass Array FileList object, FileList empty
Pass Object FileList object, FileList empty Pass Object FileList object, FileList empty
Fail ImageData 1x1 transparent black Pass ImageData 1x1 transparent black
Fail ImageData 1x1 non-transparent non-black Pass ImageData 1x1 non-transparent non-black
Fail Array ImageData object, ImageData 1x1 transparent black Pass Array ImageData object, ImageData 1x1 transparent black
Fail Array ImageData object, ImageData 1x1 non-transparent non-black Pass Array ImageData object, ImageData 1x1 non-transparent non-black
Fail Object ImageData object, ImageData 1x1 transparent black Pass Object ImageData object, ImageData 1x1 transparent black
Fail Object ImageData object, ImageData 1x1 non-transparent non-black Pass Object ImageData object, ImageData 1x1 non-transparent non-black
Pass Array sparse Pass Array sparse
Pass Array with non-index property Pass Array with non-index property
Pass Object with index property and length Pass Object with index property and length

View file

@ -66,6 +66,24 @@
println(`instanceOf DOMException: ${domException instanceof DOMException}`); println(`instanceOf DOMException: ${domException instanceof DOMException}`);
println(`DOMException: ${domException.message} - ${domException.name}`); println(`DOMException: ${domException.message} - ${domException.name}`);
const data = new Uint8ClampedArray([
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 255, 0, 255
]);
const imageData = structuredClone(new ImageData(data, 2, 2));
println(`instanceOf ImageData: ${imageData instanceof ImageData}`);
println(`ImageData.width: ${imageData.width}`);
println(`ImageData.height: ${imageData.height}`);
for (let i = 0; i < imageData.data.length; i += 4) {
const r = imageData.data[i];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const a = imageData.data[i + 3];
println(`${r}, ${g}, ${b}, ${a}`);
}
done(); done();
}); });
</script> </script>