2020-04-21 23:49:51 +02:00
|
|
|
/*
|
2022-09-02 23:07:05 +02:00
|
|
|
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
|
2024-03-23 21:45:59 +01:00
|
|
|
* Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org>
|
2020-04-21 23:49:51 +02:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-04-21 23:49:51 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <LibGfx/Bitmap.h>
|
2021-05-22 23:33:26 +04:30
|
|
|
#include <LibJS/Runtime/TypedArray.h>
|
2024-04-27 12:09:58 +12:00
|
|
|
#include <LibWeb/Bindings/ImageDataPrototype.h>
|
2022-09-25 16:38:21 -06:00
|
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
2020-07-26 15:08:16 +02:00
|
|
|
#include <LibWeb/HTML/ImageData.h>
|
2024-03-26 14:31:32 +01:00
|
|
|
#include <LibWeb/WebIDL/Buffers.h>
|
2024-03-23 21:45:59 +01:00
|
|
|
#include <LibWeb/WebIDL/DOMException.h>
|
|
|
|
#include <LibWeb/WebIDL/ExceptionOr.h>
|
2020-04-21 23:49:51 +02:00
|
|
|
|
2020-07-28 18:20:36 +02:00
|
|
|
namespace Web::HTML {
|
2020-04-21 23:49:51 +02:00
|
|
|
|
2023-11-19 19:47:52 +01:00
|
|
|
JS_DEFINE_ALLOCATOR(ImageData);
|
|
|
|
|
2024-03-26 14:34:11 +01:00
|
|
|
// https://html.spec.whatwg.org/multipage/canvas.html#dom-imagedata
|
2024-03-23 21:45:59 +01:00
|
|
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::create(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const&)
|
|
|
|
{
|
|
|
|
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."_fly_string);
|
|
|
|
|
|
|
|
// 2. Initialize this given sw, sh, and settings set to settings.
|
|
|
|
// 3. Initialize the image data of this to transparent black.
|
|
|
|
auto data = TRY(JS::Uint8ClampedArray::create(realm, sw * sh * 4));
|
2024-08-02 13:15:45 +02:00
|
|
|
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()));
|
2024-03-23 21:45:59 +01:00
|
|
|
|
|
|
|
return realm.heap().allocate<ImageData>(realm, realm, bitmap, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::construct_impl(JS::Realm& realm, u32 sw, u32 sh, Optional<ImageDataSettings> const& settings)
|
2020-04-21 23:49:51 +02:00
|
|
|
{
|
2024-03-23 21:45:59 +01:00
|
|
|
return ImageData::create(realm, sw, sh, settings);
|
|
|
|
}
|
2022-08-16 00:20:49 +01:00
|
|
|
|
2024-03-26 14:31:32 +01:00
|
|
|
// https://html.spec.whatwg.org/multipage/canvas.html#dom-imagedata-with-data
|
|
|
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::create(JS::Realm& realm, JS::Handle<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh, Optional<ImageDataSettings> const&)
|
|
|
|
{
|
|
|
|
auto& vm = realm.vm();
|
|
|
|
|
|
|
|
if (!is<JS::Uint8ClampedArray>(*data->raw_object()))
|
|
|
|
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Uint8ClampedArray");
|
|
|
|
|
|
|
|
auto& uint8_clamped_array_data = static_cast<JS::Uint8ClampedArray&>(*data->raw_object());
|
|
|
|
|
|
|
|
// 1. Let length be the number of bytes in data.
|
|
|
|
auto length = uint8_clamped_array_data.byte_length().length();
|
|
|
|
|
|
|
|
// 2. If length is not a nonzero integral multiple of four, then throw an "InvalidStateError" DOMException.
|
|
|
|
if (length == 0 || length % 4 != 0)
|
|
|
|
return WebIDL::InvalidStateError::create(realm, "Source data must have a non-sero length that is a multiple of four."_fly_string);
|
|
|
|
|
|
|
|
// 3. Let length be length divided by four.
|
|
|
|
length = length / 4;
|
|
|
|
|
|
|
|
// 4. If length is not an integral multiple of sw, then throw an "IndexSizeError" DOMException.
|
|
|
|
// NOTE: At this step, the length is guaranteed to be greater than zero (otherwise the second step above would have aborted the steps),
|
|
|
|
// so if sw is zero, this step will throw the exception and return.
|
|
|
|
if (sw == 0 || length % sw != 0)
|
|
|
|
return WebIDL::IndexSizeError::create(realm, "Source width must be a multiple of source data's length."_fly_string);
|
|
|
|
|
|
|
|
// 5. Let height be length divided by sw.
|
|
|
|
auto height = length / sw;
|
|
|
|
|
|
|
|
// 6. If sh was given and its value is not equal to height, then throw an "IndexSizeError" DOMException.
|
|
|
|
if (sh.has_value() && sh.value() != height)
|
|
|
|
return WebIDL::IndexSizeError::create(realm, "Source height must be equal to the calculated height of the data."_fly_string);
|
|
|
|
|
|
|
|
// 7. Initialize this given sw, sh, settings set to settings, and source set to data.
|
LibGfx: Store alpha type information in `Gfx::Bitmap`
We use instances of `Gfx::Bitmap` to move pixel data all the way from
raw image bytes up to the Skia renderer. A vital piece of information
for correct blending of bitmaps is the alpha type, i.e. are we dealing
with premultiplied or unpremultiplied color values?
Premultiplied means that the RGB colors have been multiplied with the
associated alpha value, i.e. RGB(255, 255, 255) with an alpha of 2% is
stored as RGBA(5, 5, 5, 2%).
Unpremultiplied means that the original RGB colors are stored,
regardless of the alpha value. I.e. RGB(255, 255, 255) with an alpha of
2% is stored as RGBA(255, 255, 255, 2%).
It is important to know how the color data is stored in a
`Gfx::Bitmap`, because correct blending depends on knowing the alpha
type: premultiplied blending uses `S + (1 - A) * D`, while
unpremultiplied blending uses `A * S + (1 - A) * D`.
This adds the alpha type information to `Gfx::Bitmap` across the board.
It isn't used anywhere yet.
2024-08-02 12:52:14 +02:00
|
|
|
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Premultiplied, Gfx::IntSize(sw, height), sw * sizeof(u32), uint8_clamped_array_data.data().data()));
|
2024-03-26 14:31:32 +01:00
|
|
|
|
|
|
|
return realm.heap().allocate<ImageData>(realm, realm, bitmap, uint8_clamped_array_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::construct_impl(JS::Realm& realm, JS::Handle<WebIDL::BufferSource> const& data, u32 sw, Optional<u32> sh, Optional<ImageDataSettings> const& settings)
|
|
|
|
{
|
|
|
|
return ImageData::create(realm, data, sw, move(sh), settings);
|
|
|
|
}
|
|
|
|
|
2022-09-25 16:38:21 -06:00
|
|
|
ImageData::ImageData(JS::Realm& realm, NonnullRefPtr<Gfx::Bitmap> bitmap, JS::NonnullGCPtr<JS::Uint8ClampedArray> data)
|
|
|
|
: PlatformObject(realm)
|
2022-09-02 23:07:05 +02:00
|
|
|
, m_bitmap(move(bitmap))
|
2020-04-21 23:49:51 +02:00
|
|
|
, m_data(move(data))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:21:51 -06:00
|
|
|
ImageData::~ImageData() = default;
|
2020-04-21 23:49:51 +02:00
|
|
|
|
2023-08-07 08:41:28 +02:00
|
|
|
void ImageData::initialize(JS::Realm& realm)
|
2023-01-10 06:28:20 -05:00
|
|
|
{
|
2023-08-07 08:41:28 +02:00
|
|
|
Base::initialize(realm);
|
2024-03-16 13:13:08 +01:00
|
|
|
WEB_SET_PROTOTYPE_FOR_INTERFACE(ImageData);
|
2023-01-10 06:28:20 -05:00
|
|
|
}
|
|
|
|
|
2022-09-02 23:07:05 +02:00
|
|
|
void ImageData::visit_edges(Cell::Visitor& visitor)
|
|
|
|
{
|
|
|
|
Base::visit_edges(visitor);
|
2023-11-19 16:18:00 +13:00
|
|
|
visitor.visit(m_data);
|
2022-09-02 23:07:05 +02:00
|
|
|
}
|
|
|
|
|
2020-06-21 15:57:10 +02:00
|
|
|
unsigned ImageData::width() const
|
2020-04-21 23:49:51 +02:00
|
|
|
{
|
|
|
|
return m_bitmap->width();
|
|
|
|
}
|
|
|
|
|
2020-06-21 15:57:10 +02:00
|
|
|
unsigned ImageData::height() const
|
2020-04-21 23:49:51 +02:00
|
|
|
{
|
|
|
|
return m_bitmap->height();
|
|
|
|
}
|
|
|
|
|
|
|
|
JS::Uint8ClampedArray* ImageData::data()
|
|
|
|
{
|
2022-09-02 23:07:05 +02:00
|
|
|
return m_data;
|
2020-04-21 23:49:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const JS::Uint8ClampedArray* ImageData::data() const
|
|
|
|
{
|
2022-09-02 23:07:05 +02:00
|
|
|
return m_data;
|
2020-04-21 23:49:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|