/src/serenity/Userland/Libraries/LibWeb/HTML/CanvasPattern.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2023, MacDue <macdue@dueutil.tech> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibGfx/Bitmap.h> |
8 | | #include <LibWeb/Bindings/CanvasPatternPrototype.h> |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/HTML/CanvasPattern.h> |
11 | | #include <LibWeb/HTML/CanvasRenderingContext2D.h> |
12 | | #include <LibWeb/HTML/ImageBitmap.h> |
13 | | #include <LibWeb/SVG/SVGImageElement.h> |
14 | | |
15 | | namespace Web::HTML { |
16 | | |
17 | | JS_DEFINE_ALLOCATOR(CanvasPattern); |
18 | | |
19 | | void CanvasPatternPaintStyle::paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const |
20 | 0 | { |
21 | | // 1. Create an infinite transparent black bitmap. |
22 | | // *waves magic wand 🪄* |
23 | | // Done! |
24 | | |
25 | | // 2. Place a copy of the image on the bitmap, anchored such that its top left corner |
26 | | // is at the origin of the coordinate space, with one coordinate space unit per CSS pixel of the image, |
27 | | // then place repeated copies of this image horizontally to the left and right, if the repetition behavior |
28 | | // is "repeat-x", or vertically up and down, if the repetition behavior is "repeat-y", or in all four directions |
29 | | // all over the bitmap, if the repetition behavior is "repeat". |
30 | | |
31 | | // FIMXE: If the original image data is a bitmap image, then the value painted at a point in the area of |
32 | | // the repetitions is computed by filtering the original image data. When scaling up, if the imageSmoothingEnabled |
33 | | // attribute is set to false, then the image must be rendered using nearest-neighbor interpolation. |
34 | | // Otherwise, the user agent may use any filtering algorithm (for example bilinear interpolation or nearest-neighbor). |
35 | | // User agents which support multiple filtering algorithms may use the value of the imageSmoothingQuality attribute |
36 | | // to guide the choice of filtering algorithm. When such a filtering algorithm requires a pixel value from outside |
37 | | // the original image data, it must instead use the value from wrapping the pixel's coordinates to the original |
38 | | // image's dimensions. (That is, the filter uses 'repeat' behavior, regardless of the value of the pattern's repetition behavior.) |
39 | | |
40 | | // FIXME: 3. Transform the resulting bitmap according to the pattern's transformation matrix. |
41 | | |
42 | | // FIXME: 4. Transform the resulting bitmap again, this time according to the current transformation matrix. |
43 | | |
44 | | // 5. Replace any part of the image outside the area in which the pattern is to be rendered with transparent black. |
45 | | |
46 | | // 6. The resulting bitmap is what is to be rendered, with the same origin and same scale. |
47 | |
|
48 | 0 | auto const bitmap_width = m_bitmap->width(); |
49 | 0 | auto const bitmap_height = m_bitmap->height(); |
50 | |
|
51 | 0 | paint([=, this](auto point) { |
52 | 0 | point.translate_by(physical_bounding_box.location()); |
53 | 0 | point = [&]() -> Gfx::IntPoint { |
54 | 0 | switch (m_repetition) { |
55 | 0 | case Repetition::NoRepeat: { |
56 | 0 | return point; |
57 | 0 | } |
58 | 0 | case Repetition::Repeat: { |
59 | 0 | return { |
60 | 0 | point.x() % bitmap_width, |
61 | 0 | point.y() % bitmap_height |
62 | 0 | }; |
63 | 0 | } |
64 | 0 | case Repetition::RepeatX: { |
65 | 0 | return { |
66 | 0 | point.x() % bitmap_width, |
67 | 0 | point.y() |
68 | 0 | }; |
69 | 0 | } |
70 | 0 | case Repetition::RepeatY: { |
71 | 0 | return { |
72 | 0 | point.x(), |
73 | 0 | point.y() % bitmap_height |
74 | 0 | }; |
75 | 0 | } |
76 | 0 | default: |
77 | 0 | VERIFY_NOT_REACHED(); |
78 | 0 | } |
79 | 0 | }(); |
80 | 0 | if (m_bitmap->rect().contains(point)) |
81 | 0 | return m_bitmap->get_pixel(point); |
82 | 0 | return Gfx::Color(); |
83 | 0 | }); |
84 | 0 | } |
85 | | |
86 | | CanvasPattern::CanvasPattern(JS::Realm& realm, CanvasPatternPaintStyle& pattern) |
87 | 0 | : PlatformObject(realm) |
88 | 0 | , m_pattern(pattern) |
89 | 0 | { |
90 | 0 | } |
91 | | |
92 | 0 | CanvasPattern::~CanvasPattern() = default; |
93 | | |
94 | | // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-createpattern |
95 | | WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> CanvasPattern::create(JS::Realm& realm, CanvasImageSource const& image, StringView repetition) |
96 | 0 | { |
97 | 0 | auto parse_repetition = [&](auto repetition) -> Optional<CanvasPatternPaintStyle::Repetition> { |
98 | 0 | if (repetition == "repeat"sv) |
99 | 0 | return CanvasPatternPaintStyle::Repetition::Repeat; |
100 | 0 | if (repetition == "repeat-x"sv) |
101 | 0 | return CanvasPatternPaintStyle::Repetition::RepeatX; |
102 | 0 | if (repetition == "repeat-y"sv) |
103 | 0 | return CanvasPatternPaintStyle::Repetition::RepeatY; |
104 | 0 | if (repetition == "no-repeat"sv) |
105 | 0 | return CanvasPatternPaintStyle::Repetition::NoRepeat; |
106 | 0 | return {}; |
107 | 0 | }; |
108 | | |
109 | | // 1. Let usability be the result of checking the usability of image. |
110 | 0 | auto usability = TRY(check_usability_of_image(image)); |
111 | | |
112 | | // 2. If usability is bad, then return null. |
113 | 0 | if (usability == CanvasImageSourceUsability::Bad) |
114 | 0 | return JS::GCPtr<CanvasPattern> {}; |
115 | | |
116 | | // 3. Assert: usability is good. |
117 | 0 | VERIFY(usability == CanvasImageSourceUsability::Good); |
118 | | |
119 | | // 4. If repetition is the empty string, then set it to "repeat". |
120 | 0 | if (repetition.is_empty()) |
121 | 0 | repetition = "repeat"sv; |
122 | | |
123 | | // 5. If repetition is not identical to one of "repeat", "repeat-x", "repeat-y", or "no-repeat", |
124 | | // then throw a "SyntaxError" DOMException. |
125 | 0 | auto repetition_value = parse_repetition(repetition); |
126 | 0 | if (!repetition_value.has_value()) |
127 | 0 | return WebIDL::SyntaxError::create(realm, "Repetition value is not valid"_string); |
128 | | |
129 | | // Note: Bitmap won't be null here, as if it were it would have "bad" usability. |
130 | 0 | auto const& bitmap = *image.visit([](auto const& source) -> Gfx::Bitmap const* { return source->bitmap(); }); Unexecuted instantiation: CanvasPattern.cpp:Gfx::Bitmap const* Web::HTML::CanvasPattern::create(JS::Realm&, AK::Variant<JS::Handle<Web::HTML::HTMLImageElement>, JS::Handle<Web::SVG::SVGImageElement>, JS::Handle<Web::HTML::HTMLCanvasElement>, JS::Handle<Web::HTML::ImageBitmap>, JS::Handle<Web::HTML::HTMLVideoElement> > const&, AK::StringView)::$_1::operator()<JS::Handle<Web::HTML::HTMLImageElement> >(JS::Handle<Web::HTML::HTMLImageElement> const&) const Unexecuted instantiation: CanvasPattern.cpp:Gfx::Bitmap const* Web::HTML::CanvasPattern::create(JS::Realm&, AK::Variant<JS::Handle<Web::HTML::HTMLImageElement>, JS::Handle<Web::SVG::SVGImageElement>, JS::Handle<Web::HTML::HTMLCanvasElement>, JS::Handle<Web::HTML::ImageBitmap>, JS::Handle<Web::HTML::HTMLVideoElement> > const&, AK::StringView)::$_1::operator()<JS::Handle<Web::SVG::SVGImageElement> >(JS::Handle<Web::SVG::SVGImageElement> const&) const Unexecuted instantiation: CanvasPattern.cpp:Gfx::Bitmap const* Web::HTML::CanvasPattern::create(JS::Realm&, AK::Variant<JS::Handle<Web::HTML::HTMLImageElement>, JS::Handle<Web::SVG::SVGImageElement>, JS::Handle<Web::HTML::HTMLCanvasElement>, JS::Handle<Web::HTML::ImageBitmap>, JS::Handle<Web::HTML::HTMLVideoElement> > const&, AK::StringView)::$_1::operator()<JS::Handle<Web::HTML::HTMLCanvasElement> >(JS::Handle<Web::HTML::HTMLCanvasElement> const&) const Unexecuted instantiation: CanvasPattern.cpp:Gfx::Bitmap const* Web::HTML::CanvasPattern::create(JS::Realm&, AK::Variant<JS::Handle<Web::HTML::HTMLImageElement>, JS::Handle<Web::SVG::SVGImageElement>, JS::Handle<Web::HTML::HTMLCanvasElement>, JS::Handle<Web::HTML::ImageBitmap>, JS::Handle<Web::HTML::HTMLVideoElement> > const&, AK::StringView)::$_1::operator()<JS::Handle<Web::HTML::ImageBitmap> >(JS::Handle<Web::HTML::ImageBitmap> const&) const Unexecuted instantiation: CanvasPattern.cpp:Gfx::Bitmap const* Web::HTML::CanvasPattern::create(JS::Realm&, AK::Variant<JS::Handle<Web::HTML::HTMLImageElement>, JS::Handle<Web::SVG::SVGImageElement>, JS::Handle<Web::HTML::HTMLCanvasElement>, JS::Handle<Web::HTML::ImageBitmap>, JS::Handle<Web::HTML::HTMLVideoElement> > const&, AK::StringView)::$_1::operator()<JS::Handle<Web::HTML::HTMLVideoElement> >(JS::Handle<Web::HTML::HTMLVideoElement> const&) const |
131 | | |
132 | | // 6. Let pattern be a new CanvasPattern object with the image image and the repetition behavior given by repetition. |
133 | 0 | auto pattern = TRY_OR_THROW_OOM(realm.vm(), CanvasPatternPaintStyle::create(bitmap, *repetition_value)); |
134 | | |
135 | | // FIXME: 7. If image is not origin-clean, then mark pattern as not origin-clean. |
136 | | |
137 | | // 8. Return pattern. |
138 | 0 | return realm.heap().allocate<CanvasPattern>(realm, realm, *pattern); |
139 | 0 | } |
140 | | |
141 | | void CanvasPattern::initialize(JS::Realm& realm) |
142 | 0 | { |
143 | 0 | Base::initialize(realm); |
144 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(CanvasPattern); |
145 | 0 | } |
146 | | |
147 | | } |