diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasImageData.h b/Libraries/LibWeb/HTML/Canvas/CanvasImageData.h index e77cde6bf85..0f5416318f7 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasImageData.h +++ b/Libraries/LibWeb/HTML/Canvas/CanvasImageData.h @@ -18,7 +18,8 @@ public: virtual WebIDL::ExceptionOr> create_image_data(int width, int height, Optional const& settings = {}) const = 0; virtual WebIDL::ExceptionOr> create_image_data(ImageData const&) const = 0; virtual WebIDL::ExceptionOr> get_image_data(int x, int y, int width, int height, Optional const& settings = {}) const = 0; - virtual void put_image_data(ImageData&, float x, float y) = 0; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y) = 0; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height) = 0; protected: CanvasImageData() = default; diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl b/Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl index afade1e2391..3bb3dae1213 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl +++ b/Libraries/LibWeb/HTML/Canvas/CanvasImageData.idl @@ -8,5 +8,5 @@ interface mixin CanvasImageData { ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {}); undefined putImageData(ImageData imageData, [EnforceRange] long dx, [EnforceRange] long dy); - [FIXME] undefined putImageData(ImageData imageData, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight); + undefined putImageData(ImageData imageData, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight); }; diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index bba79f2b067..ab2015c7f65 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -521,24 +522,95 @@ WebIDL::ExceptionOr> CanvasRenderingContext2D::get_image_data } // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-putimagedata-short -void CanvasRenderingContext2D::put_image_data(ImageData& image_data, float x, float y) +WebIDL::ExceptionOr CanvasRenderingContext2D::put_image_data(ImageData& image_data, float dx, float dy) { // The putImageData(imageData, dx, dy) method steps are to put pixels from an ImageData onto a bitmap, // given imageData, this's output bitmap, dx, dy, 0, 0, imageData's width, and imageData's height. - // FIXME: "put pixels from an ImageData onto a bitmap" is a spec algorithm. - // https://html.spec.whatwg.org/multipage/canvas.html#dom-context2d-putimagedata-common - if (auto* painter = this->painter()) { - auto dst_rect = Gfx::FloatRect(x, y, image_data.width(), image_data.height()); - painter->draw_bitmap( - dst_rect, - Gfx::ImmutableBitmap::create(image_data.bitmap(), Gfx::AlphaType::Unpremultiplied), - image_data.bitmap().rect(), - Gfx::ScalingMode::NearestNeighbor, - drawing_state().filter, - 1.0f, - Gfx::CompositingAndBlendingOperator::SourceOver); - did_draw(dst_rect); + if (auto* painter = this->painter()) + TRY(put_pixels_from_an_image_data_onto_a_bitmap(image_data, *painter, dx, dy, 0, 0, image_data.width(), image_data.height())); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-putimagedata +WebIDL::ExceptionOr CanvasRenderingContext2D::put_image_data(ImageData& image_data, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height) +{ + // The putImageData(imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) method steps are to put pixels + // from an ImageData onto a bitmap, given imageData, this's output bitmap, dx, dy, dirtyX, dirtyY, dirtyWidth, and + // dirtyHeight. + if (auto* painter = this->painter()) + TRY(put_pixels_from_an_image_data_onto_a_bitmap(image_data, *painter, x, y, dirty_x, dirty_y, dirty_width, dirty_height)); + + return {}; +} + +// https://html.spec.whatwg.org/multipage/canvas.html#dom-context2d-putimagedata-common +WebIDL::ExceptionOr CanvasRenderingContext2D::put_pixels_from_an_image_data_onto_a_bitmap(ImageData& image_data, Gfx::Painter& painter, float dx, float dy, float dirty_x, float dirty_y, float dirty_width, float dirty_height) +{ + // 1. Let buffer be imageData's data attribute value's [[ViewedArrayBuffer]] internal slot. + auto* buffer = image_data.data()->viewed_array_buffer(); + + // 2. If IsDetachedBuffer(buffer) is true, then throw an "InvalidStateError" DOMException + if (buffer->is_detached()) + return WebIDL::InvalidStateError::create(image_data.realm(), "ImageData's underlying buffer is detached"_utf16); + + // 3. If dirtyWidth is negative, then let dirtyX be dirtyX+dirtyWidth, and let dirtyWidth be equal to the + // absolute magnitude of dirtyWidth. + if (dirty_width < 0) { + dirty_x += dirty_width; + dirty_width = abs(dirty_width); } + // If dirtyHeight is negative, then let dirtyY be dirtyY+dirtyHeight, and let dirtyHeight be equal to the absolute + // magnitude of dirtyHeight. + if (dirty_height < 0) { + dirty_y += dirty_height; + dirty_height = abs(dirty_height); + } + + // 4. If dirtyX is negative, then let dirtyWidth be dirtyWidth+dirtyX, and let dirtyX be 0. + if (dirty_x < 0) { + dirty_width += dirty_x; + dirty_x = 0; + } + + // If dirtyY is negative, then let dirtyHeight be dirtyHeight+dirtyY, and let dirtyY be 0. + if (dirty_y < 0) { + dirty_height += dirty_y; + dirty_y = 0; + } + + // 5. If dirtyX+dirtyWidth is greater than the width attribute of the imageData argument, then let dirtyWidth be + // the value of that width attribute, minus the value of dirtyX. + if (dirty_x + dirty_width > image_data.width()) { + dirty_width = image_data.width() - dirty_x; + } + // If dirtyY+dirtyHeight is greater than the height attribute of the imageData argument, then let dirtyHeight be + // the value of that height attribute, minus the value of dirtyY. + if (dirty_y + dirty_height > image_data.height()) { + dirty_height = image_data.height() - dirty_y; + } + + // 6. If, after those changes, either dirtyWidth or dirtyHeight are negative or zero, then return without affecting + // any bitmaps. + if (dirty_width <= 0 || dirty_height <= 0) + return {}; + + // 7. For all integer values of x and y where dirtyX ≤ x < dirtyX+dirtyWidth and dirtyY ≤ y < dirtyY+dirtyHeight, + // set the pixel with coordinate (dx+x, dy+y) in bitmap to the color of the pixel at coordinate (x, y) in the + // imageData data structure's bitmap, converted from imageData's colorSpace to the color space of bitmap using + // 'relative-colorimetric' rendering intent. + auto dst_rect = Gfx::FloatRect { dx + dirty_x, dy + dirty_y, dirty_width, dirty_height }; + painter.draw_bitmap( + dst_rect, + Gfx::ImmutableBitmap::create(image_data.bitmap(), Gfx::AlphaType::Unpremultiplied), + Gfx::IntRect { dirty_x, dirty_y, dirty_width, dirty_height }, + Gfx::ScalingMode::NearestNeighbor, + drawing_state().filter, + 1.0f, + Gfx::CompositingAndBlendingOperator::SourceOver); + did_draw(dst_rect); + + return {}; } // https://html.spec.whatwg.org/multipage/canvas.html#reset-the-rendering-context-to-its-default-state diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h index 9a87c78018c..96b1415d2ee 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h @@ -79,7 +79,9 @@ public: virtual WebIDL::ExceptionOr> create_image_data(int width, int height, Optional const& settings = {}) const override; virtual WebIDL::ExceptionOr> create_image_data(ImageData const& image_data) const override; virtual WebIDL::ExceptionOr> get_image_data(int x, int y, int width, int height, Optional const& settings = {}) const override; - virtual void put_image_data(ImageData&, float x, float y) override; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y) override; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height) override; + WebIDL::ExceptionOr put_pixels_from_an_image_data_onto_a_bitmap(ImageData&, Gfx::Painter&, float dx, float dy, float dirty_x, float dirty_y, float dirty_width, float dirty_height); virtual void reset_to_default_state() override; diff --git a/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.cpp index db57e315b46..30a7998594f 100644 --- a/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.cpp @@ -155,10 +155,16 @@ WebIDL::ExceptionOr> OffscreenCanvasRenderingContext2D::get_i return WebIDL::NotSupportedError::create(realm(), "(STUBBED) OffscreenCanvasRenderingContext2D::get_image_data()"_utf16); } -void OffscreenCanvasRenderingContext2D::put_image_data(ImageData&, float, float) - +WebIDL::ExceptionOr OffscreenCanvasRenderingContext2D::put_image_data(ImageData&, float, float) { dbgln("(STUBBED) OffscreenCanvasRenderingContext2D::put_image_data()"); + return {}; +} + +WebIDL::ExceptionOr OffscreenCanvasRenderingContext2D::put_image_data(ImageData&, float, float, float, float, float, float) +{ + dbgln("(STUBBED) OffscreenCanvasRenderingContext2D::put_image_data()"); + return {}; } void OffscreenCanvasRenderingContext2D::reset_to_default_state() diff --git a/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.h b/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.h index 80a87f12ea4..d7d2e10138d 100644 --- a/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.h +++ b/Libraries/LibWeb/HTML/OffscreenCanvasRenderingContext2D.h @@ -84,7 +84,8 @@ public: virtual WebIDL::ExceptionOr> create_image_data(int width, int height, Optional const& settings = {}) const override; virtual WebIDL::ExceptionOr> create_image_data(ImageData const& image_data) const override; virtual WebIDL::ExceptionOr> get_image_data(int x, int y, int width, int height, Optional const& settings = {}) const override; - virtual void put_image_data(ImageData&, float x, float y) override; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y) override; + virtual WebIDL::ExceptionOr put_image_data(ImageData&, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height) override; virtual void reset_to_default_state() override; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.txt new file mode 100644 index 00000000000..8db7d9f5e40 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() handles negative-sized dirty rectangles correctly \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.txt new file mode 100644 index 00000000000..ccf58dc28d8 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() handles dirty rectangles outside the canvas correctly \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.txt new file mode 100644 index 00000000000..3f9772c9330 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() only modifies areas inside the dirty rectangle, using width and height \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.txt new file mode 100644 index 00000000000..1b7606bb748 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() only modifies areas inside the dirty rectangle, using x and y \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.txt new file mode 100644 index 00000000000..a3fe2f43b5a --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() with zero-sized dirty rectangle puts nothing \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.txt b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.txt new file mode 100644 index 00000000000..42294ec50af --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass putImageData() throws TypeError if arguments are not finite \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.html new file mode 100644 index 00000000000..f034b28c4f3 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.negative.html @@ -0,0 +1,44 @@ + + + +Canvas test: 2d.imageData.put.dirty.negative + + + + + + +

2d.imageData.put.dirty.negative

+

putImageData() handles negative-sized dirty rectangles correctly

+ + +

Actual output:

+

FAIL (fallback content)

+

Expected output:

+

    + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.html new file mode 100644 index 00000000000..ba1e007b4e4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.outside.html @@ -0,0 +1,46 @@ + + + +Canvas test: 2d.imageData.put.dirty.outside + + + + + + +

    2d.imageData.put.dirty.outside

    +

    putImageData() handles dirty rectangles outside the canvas correctly

    + + +

    Actual output:

    +

    FAIL (fallback content)

    +

    Expected output:

    +

      + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.html new file mode 100644 index 00000000000..8d1d6d24d54 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect1.html @@ -0,0 +1,44 @@ + + + +Canvas test: 2d.imageData.put.dirty.rect1 + + + + + + +

      2d.imageData.put.dirty.rect1

      +

      putImageData() only modifies areas inside the dirty rectangle, using width and height

      + + +

      Actual output:

      +

      FAIL (fallback content)

      +

      Expected output:

      +

        + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.html new file mode 100644 index 00000000000..538b9e96e9f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.rect2.html @@ -0,0 +1,44 @@ + + + +Canvas test: 2d.imageData.put.dirty.rect2 + + + + + + +

        2d.imageData.put.dirty.rect2

        +

        putImageData() only modifies areas inside the dirty rectangle, using x and y

        + + +

        Actual output:

        +

        FAIL (fallback content)

        +

        Expected output:

        +

          + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.html new file mode 100644 index 00000000000..396d1d420c0 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.dirty.zero.html @@ -0,0 +1,33 @@ + + + +Canvas test: 2d.imageData.put.dirty.zero + + + + + + +

          2d.imageData.put.dirty.zero

          +

          putImageData() with zero-sized dirty rectangle puts nothing

          + + +

          Actual output:

          +

          FAIL (fallback content)

          +

          Expected output:

          +

            + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.html b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.html new file mode 100644 index 00000000000..5a737481bb2 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/canvas/element/pixel-manipulation/2d.imageData.put.nonfinite.html @@ -0,0 +1,109 @@ + + + +Canvas test: 2d.imageData.put.nonfinite + + + + + + +

            2d.imageData.put.nonfinite

            +

            putImageData() throws TypeError if arguments are not finite

            + +

            Defined in "Web IDL" (draft) +

            Actual output:

            +

            FAIL (fallback content)

            + +
              + +