mirror of
https://github.com/RGBCube/serenity
synced 2025-05-25 22:15:06 +00:00
LibWeb: Implement CanvasRenderingContext2D.createPattern()
This is a first pass at implementing CRC2D.createPattern() and the associated CanvasPattern object. This implementation only works for a few of the required image sources [like CRC2D.drawImage()], and does not yet support transforms. Other than that it supports everything else (which is mainly the various repeat modes).
This commit is contained in:
parent
0c313c586b
commit
f74e2da875
8 changed files with 229 additions and 8 deletions
|
@ -170,6 +170,7 @@ set(SOURCES
|
|||
HTML/Canvas/CanvasPath.cpp
|
||||
HTML/Canvas/CanvasState.cpp
|
||||
HTML/CanvasGradient.cpp
|
||||
HTML/CanvasPattern.cpp
|
||||
HTML/CanvasRenderingContext2D.cpp
|
||||
HTML/CloseEvent.cpp
|
||||
HTML/CrossOrigin/AbstractOperations.cpp
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <AK/DeprecatedString.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasState.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -20,7 +21,7 @@ template<typename IncludingClass>
|
|||
class CanvasFillStrokeStyles {
|
||||
public:
|
||||
~CanvasFillStrokeStyles() = default;
|
||||
using FillOrStrokeStyleVariant = Variant<DeprecatedString, JS::Handle<CanvasGradient>>;
|
||||
using FillOrStrokeStyleVariant = Variant<DeprecatedString, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
static CanvasState::FillOrStrokeStyle to_canvas_state_fill_or_stroke_style(auto const& style)
|
||||
{
|
||||
|
@ -73,6 +74,12 @@ public:
|
|||
return CanvasGradient::create_conic(realm, start_angle, x, y);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create_pattern(CanvasImageSource const& image, StringView repetition)
|
||||
{
|
||||
auto& realm = static_cast<IncludingClass&>(*this).realm();
|
||||
return CanvasPattern::create(realm, image, repetition);
|
||||
}
|
||||
|
||||
protected:
|
||||
CanvasFillStrokeStyles() = default;
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
#import <HTML/CanvasGradient.idl>
|
||||
#import <HTML/CanvasPattern.idl>
|
||||
#import <HTML/HTMLCanvasElement.idl>
|
||||
#import <HTML/HTMLImageElement.idl>
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles
|
||||
interface mixin CanvasFillStrokeStyles {
|
||||
// FIXME: Should be `(DOMString or CanvasGradient or CanvasPattern)`
|
||||
attribute (DOMString or CanvasGradient) strokeStyle;
|
||||
// FIXME: Should be `(DOMString or CanvasGradient or CanvasPattern)`
|
||||
attribute (DOMString or CanvasGradient) fillStyle;
|
||||
attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle;
|
||||
attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle;
|
||||
CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
|
||||
CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
|
||||
CanvasGradient createConicGradient(double startAngle, double x, double y);
|
||||
// FIXME: CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition);
|
||||
// FIXME: 'image' should be a CanvasImageSource
|
||||
CanvasPattern? createPattern((HTMLImageElement or HTMLCanvasElement) image, [LegacyNullToEmptyString] DOMString repetition);
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibWeb/HTML/CanvasGradient.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -26,7 +27,7 @@ public:
|
|||
void reset();
|
||||
bool is_context_lost();
|
||||
|
||||
using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>>;
|
||||
using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
struct FillOrStrokeStyle {
|
||||
FillOrStrokeStyle(Gfx::Color color)
|
||||
|
@ -49,7 +50,7 @@ public:
|
|||
Optional<Gfx::Color> as_color() const;
|
||||
Gfx::Color to_color_but_fixme_should_accept_any_paint_style() const;
|
||||
|
||||
using JsFillOrStrokeStyle = Variant<DeprecatedString, JS::Handle<CanvasGradient>>;
|
||||
using JsFillOrStrokeStyle = Variant<DeprecatedString, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
|
||||
|
||||
JsFillOrStrokeStyle to_js_fill_or_stroke_style() const
|
||||
{
|
||||
|
|
144
Userland/Libraries/LibWeb/HTML/CanvasPattern.cpp
Normal file
144
Userland/Libraries/LibWeb/HTML/CanvasPattern.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/HTML/CanvasPattern.h>
|
||||
#include <LibWeb/HTML/CanvasRenderingContext2D.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const
|
||||
{
|
||||
// 1. Create an infinite transparent black bitmap.
|
||||
// *waves magic wand 🪄*
|
||||
// Done!
|
||||
|
||||
// 2. Place a copy of the image on the bitmap, anchored such that its top left corner
|
||||
// is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image,
|
||||
// then place repeated copies of this image horizontally to the left and right, if the repetition behavior
|
||||
// is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions
|
||||
// all over the bitmap, if the repetition behavior is "repeat".
|
||||
|
||||
// FIMXE: If the original image data is a bitmap image, then the value painted at a point in the area of
|
||||
// the repetitions is computed by filtering the original image data. When scaling up, if the imageSmoothingEnabled
|
||||
// attribute is set to false, then the image must be rendered using nearest-neighbor interpolation.
|
||||
// Otherwise, the user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor).
|
||||
// User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute
|
||||
// to guide the choice of filtering algorithm. When such a filtering algorithm requires a pixel value from outside
|
||||
// the original image data, it must instead use the value from wrapping the pixel's coordinates to the original
|
||||
// image's dimensions. (That is, the filter uses 'repeat' behavior, regardless of the value of the pattern's repetition behavior.)
|
||||
|
||||
// FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix.
|
||||
|
||||
// FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix.
|
||||
|
||||
// 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black.
|
||||
|
||||
// 6. The resulting bitmap is what is to be rendered, with the same origin and same scale.
|
||||
|
||||
auto const bitmap_width = m_bitmap->width();
|
||||
auto const bitmap_height = m_bitmap->height();
|
||||
|
||||
paint([=, this](auto point) {
|
||||
point.translate_by(physical_bounding_box.location());
|
||||
point = [&]() -> Gfx::IntPoint {
|
||||
switch (m_repetition) {
|
||||
case Repetition::NoRepeat: {
|
||||
return point;
|
||||
}
|
||||
case Repetition::Repeat: {
|
||||
return {
|
||||
point.x() % bitmap_width,
|
||||
point.y() % bitmap_height
|
||||
};
|
||||
}
|
||||
case Repetition::RepeatX: {
|
||||
return {
|
||||
point.x() % bitmap_width,
|
||||
point.y()
|
||||
};
|
||||
}
|
||||
case Repetition::RepeatY: {
|
||||
return {
|
||||
point.x(),
|
||||
point.y() % bitmap_height
|
||||
};
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
if (m_bitmap->rect().contains(point))
|
||||
return m_bitmap->get_pixel(point);
|
||||
return Gfx::Color();
|
||||
});
|
||||
}
|
||||
|
||||
CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern)
|
||||
: PlatformObject(realm)
|
||||
, m_pattern(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
CanvasPattern::~CanvasPattern() = default;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern
|
||||
WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition)
|
||||
{
|
||||
auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> {
|
||||
if (repetition == "repeat"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::Repeat;
|
||||
if (repetition == "repeat-x"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::RepeatX;
|
||||
if (repetition == "repeat-y"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::RepeatY;
|
||||
if (repetition == "no-repeat"sv)
|
||||
return CanvasPatternPaintStyle::Repetition::NoRepeat;
|
||||
return {};
|
||||
};
|
||||
|
||||
// 1. Let usability be the result of checking the usability of image.
|
||||
auto usability = TRY(check_usability_of_image(image));
|
||||
|
||||
// 2. If usability is bad, then return null.
|
||||
if (usability == CanvasImageSourceUsability::Bad)
|
||||
return JS::GCPtr<CanvasPattern> {};
|
||||
|
||||
// 3. Assert: usability is good.
|
||||
VERIFY(usability == CanvasImageSourceUsability::Good);
|
||||
|
||||
// 4. If repetition is the empty string, then set it to "repeat".
|
||||
if (repetition.is_empty())
|
||||
repetition = "repeat"sv;
|
||||
|
||||
// 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat",
|
||||
// then throw a "SyntaxError" DOMException.
|
||||
auto repetition_value = parse_repetition(repetition);
|
||||
if (!repetition_value.has_value())
|
||||
return WebIDL::SyntaxError::create(realm, "Repetition value is not valid");
|
||||
|
||||
// Note: Bitmap won't be null here, as if it were it would have "bad" usability.
|
||||
auto const& bitmap = *image.visit([](auto const& source) -> Gfx::Bitmap const* { return source->bitmap(); });
|
||||
|
||||
// 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition.
|
||||
auto pattern = CanvasPatternPaintStyle::create(bitmap, *repetition_value);
|
||||
|
||||
// FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean.
|
||||
|
||||
// 8. Return pattern.
|
||||
return MUST_OR_THROW_OOM(realm.heap().allocate<CanvasPattern>(realm, realm, *pattern));
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<void> CanvasPattern::initialize(JS::Realm& realm)
|
||||
{
|
||||
MUST_OR_THROW_OOM(Base::initialize(realm));
|
||||
set_prototype(&Bindings::ensure_web_prototype<Bindings::CanvasPatternPrototype>(realm, "CanvasPattern"));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
60
Userland/Libraries/LibWeb/HTML/CanvasPattern.h
Normal file
60
Userland/Libraries/LibWeb/HTML/CanvasPattern.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class CanvasPatternPaintStyle final : public Gfx::PaintStyle {
|
||||
public:
|
||||
enum class Repetition {
|
||||
Repeat,
|
||||
RepeatX,
|
||||
RepeatY,
|
||||
NoRepeat
|
||||
};
|
||||
|
||||
static NonnullRefPtr<CanvasPatternPaintStyle> create(Gfx::Bitmap const& bitmap, Repetition repetition)
|
||||
{
|
||||
return adopt_ref(*new CanvasPatternPaintStyle(bitmap, repetition));
|
||||
}
|
||||
|
||||
virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override;
|
||||
|
||||
private:
|
||||
CanvasPatternPaintStyle(Gfx::Bitmap const& bitmap, Repetition repetition)
|
||||
: m_bitmap(bitmap)
|
||||
, m_repetition(repetition)
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<Gfx::Bitmap const> m_bitmap;
|
||||
Repetition m_repetition { Repetition::Repeat };
|
||||
};
|
||||
|
||||
class CanvasPattern final : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(CanvasPattern, Bindings::PlatformObject);
|
||||
|
||||
public:
|
||||
static WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create(JS::Realm&, CanvasImageSource const& image, StringView repetition);
|
||||
|
||||
~CanvasPattern();
|
||||
|
||||
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style() { return m_pattern; }
|
||||
|
||||
private:
|
||||
CanvasPattern(JS::Realm&, CanvasPatternPaintStyle&);
|
||||
|
||||
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||
|
||||
NonnullRefPtr<CanvasPatternPaintStyle> m_pattern;
|
||||
};
|
||||
|
||||
}
|
5
Userland/Libraries/LibWeb/HTML/CanvasPattern.idl
Normal file
5
Userland/Libraries/LibWeb/HTML/CanvasPattern.idl
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Exposed=(Window,Worker)]
|
||||
interface CanvasPattern {
|
||||
// opaque object
|
||||
// FIXME: undefined setTransform(optional DOMMatrix2DInit transform = {});
|
||||
};
|
|
@ -65,6 +65,7 @@ libweb_js_bindings(Geometry/DOMRect)
|
|||
libweb_js_bindings(Geometry/DOMRectList)
|
||||
libweb_js_bindings(Geometry/DOMRectReadOnly)
|
||||
libweb_js_bindings(HTML/CanvasGradient)
|
||||
libweb_js_bindings(HTML/CanvasPattern)
|
||||
libweb_js_bindings(HTML/CanvasRenderingContext2D)
|
||||
libweb_js_bindings(HTML/CloseEvent)
|
||||
libweb_js_bindings(HTML/DOMParser)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue