Coverage Report

Created: 2025-08-28 06:26

/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
}