LibWeb: Add initial support for HTMLImageElement in createImageBitmap

This commit is contained in:
Idan Horowitz 2025-08-03 20:48:55 +03:00 committed by Jelle Raaijmakers
commit 3b8ccf4d77
Notes: github-actions[bot] 2025-08-03 19:49:16 +00:00
11 changed files with 101 additions and 5 deletions

View file

@ -242,11 +242,33 @@ GC::Ref<WebIDL::Promise> WindowOrWorkerGlobalScopeMixin::create_image_bitmap_imp
[&](CanvasImageSource const& image_source) { [&](CanvasImageSource const& image_source) {
image_source.visit( image_source.visit(
// -> img // -> img
[&](GC::Root<HTMLImageElement> const&) { [&](GC::Root<HTMLImageElement> const& image_element) {
dbgln("(STUBBED) createImageBitmap() for HTMLImageElement"); // 1. If image's media data has no natural dimensions (e.g., it's a vector graphic with no specified content size) and options's resizeWidth or options's resizeHeight is not present, then return a promise rejected with an "InvalidStateError" DOMException.
auto const error = JS::Error::create(realm, "Not Implemented: createImageBitmap() for HTMLImageElement"sv); auto const has_natural_dimensions = image_element->intrinsic_width().has_value() && image_element->intrinsic_height().has_value();
TemporaryExecutionContext const context { relevant_realm(p->promise()), TemporaryExecutionContext::CallbacksEnabled::Yes }; if (!has_natural_dimensions && (!options.has_value() || !options->resize_width.has_value() || !options->resize_width.has_value())) {
WebIDL::reject_promise(realm, *p, error); WebIDL::reject_promise(realm, *p, WebIDL::InvalidStateError::create(image_bitmap->realm(), "Image data is detached"_string));
return;
}
// 2. If image's media data has no natural dimensions (e.g., it's a vector graphic with no specified content size), it should be rendered to a bitmap of the size specified by the resizeWidth and the resizeHeight options.
// 3. Set imageBitmap's bitmap data to a copy of image's media data, cropped to the source rectangle with formatting. If this is an animated image, imageBitmap's bitmap data must only be taken from the default image of the animation (the one that the format defines is to be used when animation is not supported or is disabled), or, if there is no such image, the first frame of the animation.
// FIXME: Actually crop the image to the source rectangle with formatting: https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
RefPtr<Gfx::ImmutableBitmap> immutable_bitmap;
if (has_natural_dimensions) {
immutable_bitmap = image_element->default_image_bitmap(Gfx::IntSize { *image_element->intrinsic_width(), *image_element->intrinsic_height() });
} else {
immutable_bitmap = image_element->default_image_bitmap(Gfx::IntSize { *options->resize_width, *options->resize_height });
}
image_bitmap->set_bitmap(MUST(immutable_bitmap->bitmap()->clone()));
// FIXME: 4. If image is not origin-clean, then set the origin-clean flag of imageBitmap's bitmap to false.
// 5. Queue a global task, using the bitmap task source, to resolve promise with imageBitmap.
queue_global_task(Task::Source::BitmapTask, image_bitmap, GC::create_function(realm.heap(), [p, image_bitmap] {
auto& realm = relevant_realm(image_bitmap);
TemporaryExecutionContext const context { realm, TemporaryExecutionContext::CallbacksEnabled::Yes };
WebIDL::resolve_promise(realm, *p, image_bitmap);
}));
}, },
// -> SVG image // -> SVG image
[&](GC::Root<SVG::SVGImageElement> const&) { [&](GC::Root<SVG::SVGImageElement> const&) {

View file

@ -0,0 +1,13 @@
Harness status: OK
Found 8 tests
8 Pass
Pass createImageBitmap with Orientation 1
Pass createImageBitmap with Orientation 2
Pass createImageBitmap with Orientation 3
Pass createImageBitmap with Orientation 4
Pass createImageBitmap with Orientation 5
Pass createImageBitmap with Orientation 6
Pass createImageBitmap with Orientation 7
Pass createImageBitmap with Orientation 8

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<title>Test that createImageBitmap honors EXIF orientation</title>
<script src="../../../../../resources/testharness.js"></script>
<script src="../../../../../resources/testharnessreport.js"></script>
<style>canvas { outline: 1px solid black; margin-right: 1em; }</style>
<body>
<script>
function loadImage(src) {
return new Promise(function(resolve) {
const image = new Image();
image.addEventListener("load", () => resolve(image), { once: true });
image.src = src;
});
}
function checkColors(ctx, w, h, is_verticle, expectedColors) {
let data = ctx.getImageData(0, 0, w, h).data;
row_width = 80;
col_width = 80;
for (let [row, col, r, g, b, a] of expectedColors) {
let x = col * row_width + 10;
let y = row * col_width + 10;
let i = (x + y * w) * 4;
let expected = [r, g, b, a];
let actual = [data[i], data[i + 1], data[i + 2], data[i + 3]];
assert_array_approx_equals(actual, expected, 1, `Pixel value at (${x},${y}) ${expected} =~ ${actual}.`);
}
}
for (let orientation of [1, 2, 3, 4, 5, 6, 7, 8]) {
async_test(function(t) {
const canvas = document.createElement("canvas");
canvas.width = 160;
canvas.height = 320;
document.body.append(canvas);
const ctx = canvas.getContext("2d");
loadImage(`resources/squares_${orientation}.jpg`)
.then((image) => createImageBitmap(image, { imageOrientation: "none" }))
.then(t.step_func_done(function(imageBitmap) {
ctx.drawImage(imageBitmap, 0, 0, 160, 320);
checkColors(ctx, canvas.width, canvas.height, false, [
// row, col, r, g, b, a
[0, 0, 0, 0, 0, 255],
[0, 1, 128, 128, 128, 255],
[1, 0, 0, 0, 255, 255],
[1, 1, 128, 128, 255, 255],
[2, 0, 0, 255, 0, 255],
[2, 1, 128, 255, 128, 255],
[3, 0, 255, 0, 0, 255],
[3, 1, 255, 128, 128, 255],
]);
}));
}, `createImageBitmap with Orientation ${orientation}`);
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB