Skip to content

Commit 62ae4e8

Browse files
gmtatcl3
authored andcommitted
LibWeb: Implement support for drawing with CanvasPattern
We already had the API, but drawing to the canvas was not affected by any created CanvasPattern. This moves CanvasPatternPaintStyle to LibGfx so we don't have to reach into LibWeb, and implements the plumbing to let Skia use images as a fill pattern.
1 parent 9753b8e commit 62ae4e8

File tree

20 files changed

+234
-164
lines changed

20 files changed

+234
-164
lines changed

Libraries/LibGfx/PaintStyle.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
3+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
@@ -13,6 +14,7 @@
1314
#include <LibGfx/Color.h>
1415
#include <LibGfx/Forward.h>
1516
#include <LibGfx/Gradients.h>
17+
#include <LibGfx/ImmutableBitmap.h>
1618
#include <LibGfx/Rect.h>
1719

1820
namespace Gfx {
@@ -78,6 +80,34 @@ class GradientPaintStyle : public PaintStyle {
7880
Optional<float> m_repeat_length;
7981
};
8082

83+
class CanvasPatternPaintStyle : public PaintStyle {
84+
public:
85+
enum class Repetition : u8 {
86+
Repeat,
87+
RepeatX,
88+
RepeatY,
89+
NoRepeat
90+
};
91+
92+
static ErrorOr<NonnullRefPtr<CanvasPatternPaintStyle>> create(RefPtr<ImmutableBitmap> image, Repetition repetition)
93+
{
94+
return adopt_nonnull_ref_or_enomem(new (nothrow) CanvasPatternPaintStyle(image, repetition));
95+
}
96+
97+
RefPtr<ImmutableBitmap> image() const { return m_image; }
98+
Repetition repetition() const { return m_repetition; }
99+
100+
private:
101+
CanvasPatternPaintStyle(RefPtr<ImmutableBitmap> image, Repetition repetition)
102+
: m_image(image)
103+
, m_repetition(repetition)
104+
{
105+
}
106+
107+
RefPtr<ImmutableBitmap> m_image;
108+
Repetition m_repetition { Repetition::Repeat };
109+
};
110+
81111
// The following paint styles implement the gradients required for the HTML canvas.
82112
// These gradients are (unlike CSS ones) not relative to the painted shape, and do not
83113
// support premultiplied alpha.

Libraries/LibGfx/PainterSkia.cpp

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,30 @@
22
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
33
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
44
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
5+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
56
*
67
* SPDX-License-Identifier: BSD-2-Clause
78
*/
89

910
#define AK_DONT_REPLACE_STD
1011

12+
#include <AK/GenericShorthands.h>
1113
#include <AK/OwnPtr.h>
1214
#include <AK/String.h>
15+
#include <AK/TypeCasts.h>
1316
#include <LibGfx/Filter.h>
1417
#include <LibGfx/ImmutableBitmap.h>
1518
#include <LibGfx/PainterSkia.h>
1619
#include <LibGfx/PathSkia.h>
1720
#include <LibGfx/SkiaUtils.h>
1821

19-
#include <AK/TypeCasts.h>
2022
#include <core/SkCanvas.h>
2123
#include <core/SkPath.h>
24+
#include <core/SkPathEffect.h>
2225
#include <effects/SkBlurMaskFilter.h>
2326
#include <effects/SkDashPathEffect.h>
2427
#include <effects/SkGradientShader.h>
28+
#include <include/core/SkImage.h>
2529

2630
namespace Gfx {
2731

@@ -43,14 +47,12 @@ struct PainterSkia::Impl {
4347
}
4448
};
4549

46-
static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
50+
static void apply_paint_style(SkPaint& paint, PaintStyle const& style)
4751
{
48-
if (is<Gfx::SolidColorPaintStyle>(style)) {
49-
auto const& solid_color = static_cast<Gfx::SolidColorPaintStyle const&>(style);
50-
paint.setColor(to_skia_color(solid_color.color()));
51-
} else if (is<Gfx::CanvasLinearGradientPaintStyle>(style)) {
52-
auto const& linear_gradient = static_cast<Gfx::CanvasLinearGradientPaintStyle const&>(style);
53-
auto const& color_stops = linear_gradient.color_stops();
52+
if (auto const& solid_color = as_if<SolidColorPaintStyle>(style)) {
53+
paint.setColor(to_skia_color(solid_color->color()));
54+
} else if (auto const& linear_gradient = as_if<Gfx::CanvasLinearGradientPaintStyle>(style)) {
55+
auto const& color_stops = linear_gradient->color_stops();
5456

5557
Vector<SkColor> colors;
5658
colors.ensure_capacity(color_stops.size());
@@ -61,16 +63,13 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
6163
positions.append(color_stop.position);
6264
}
6365

64-
Array<SkPoint, 2> points;
65-
points[0] = to_skia_point(linear_gradient.start_point());
66-
points[1] = to_skia_point(linear_gradient.end_point());
66+
Array points { to_skia_point(linear_gradient->start_point()), to_skia_point(linear_gradient->end_point()) };
6767

6868
SkMatrix matrix;
6969
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
7070
paint.setShader(shader);
71-
} else if (is<Gfx::CanvasRadialGradientPaintStyle>(style)) {
72-
auto const& radial_gradient = static_cast<Gfx::CanvasRadialGradientPaintStyle const&>(style);
73-
auto const& color_stops = radial_gradient.color_stops();
71+
} else if (auto const* radial_gradient = as_if<CanvasRadialGradientPaintStyle>(style)) {
72+
auto const& color_stops = radial_gradient->color_stops();
7473

7574
Vector<SkColor> colors;
7675
colors.ensure_capacity(color_stops.size());
@@ -81,17 +80,37 @@ static void apply_paint_style(SkPaint& paint, Gfx::PaintStyle const& style)
8180
positions.append(color_stop.position);
8281
}
8382

84-
auto start_center = radial_gradient.start_center();
85-
auto end_center = radial_gradient.end_center();
86-
auto start_radius = radial_gradient.start_radius();
87-
auto end_radius = radial_gradient.end_radius();
83+
auto start_center = radial_gradient->start_center();
84+
auto end_center = radial_gradient->end_center();
85+
auto start_radius = radial_gradient->start_radius();
86+
auto end_radius = radial_gradient->end_radius();
8887

8988
auto start_sk_point = to_skia_point(start_center);
9089
auto end_sk_point = to_skia_point(end_center);
9190

9291
SkMatrix matrix;
9392
auto shader = SkGradientShader::MakeTwoPointConical(start_sk_point, start_radius, end_sk_point, end_radius, colors.data(), positions.data(), color_stops.size(), SkTileMode::kClamp, 0, &matrix);
9493
paint.setShader(shader);
94+
} else if (auto const* canvas_pattern = as_if<CanvasPatternPaintStyle>(style)) {
95+
auto image = canvas_pattern->image();
96+
if (!image)
97+
return;
98+
auto const* sk_image = image->sk_image();
99+
100+
auto repetition = canvas_pattern->repetition();
101+
auto repeat_x = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatX);
102+
auto repeat_y = first_is_one_of(repetition, CanvasPatternPaintStyle::Repetition::Repeat, CanvasPatternPaintStyle::Repetition::RepeatY);
103+
104+
// FIXME: Implement sampling configuration.
105+
SkSamplingOptions sk_sampling_options { SkFilterMode::kLinear };
106+
107+
auto shader = sk_image->makeShader(
108+
repeat_x ? SkTileMode::kRepeat : SkTileMode::kDecal,
109+
repeat_y ? SkTileMode::kRepeat : SkTileMode::kDecal,
110+
sk_sampling_options);
111+
paint.setShader(shader);
112+
} else {
113+
dbgln("FIXME: Unsupported PaintStyle");
95114
}
96115
}
97116

Libraries/LibGfx/PainterSkia.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#pragma once
88

99
#include <AK/NonnullOwnPtr.h>
10-
#include <LibGfx/Bitmap.h>
1110
#include <LibGfx/CompositingAndBlendingOperator.h>
1211
#include <LibGfx/Painter.h>
1312
#include <LibGfx/PaintingSurface.h>

Libraries/LibGfx/SkiaUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
33
* Copyright (c) 2024, Lucien Fiorini <lucienfiorini@gmail.com>
4+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
45
*
56
* SPDX-License-Identifier: BSD-2-Clause
67
*/
@@ -11,6 +12,7 @@
1112
#include <LibGfx/Bitmap.h>
1213
#include <LibGfx/CompositingAndBlendingOperator.h>
1314
#include <LibGfx/Filter.h>
15+
#include <LibGfx/PaintStyle.h>
1416
#include <LibGfx/PathSkia.h>
1517
#include <LibGfx/ScalingMode.h>
1618
#include <LibGfx/WindingRule.h>
@@ -20,7 +22,6 @@
2022
#include <core/SkImageFilter.h>
2123
#include <core/SkPaint.h>
2224
#include <core/SkPath.h>
23-
#include <core/SkPathEffect.h>
2425
#include <core/SkSamplingOptions.h>
2526

2627
namespace Gfx {

Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.cpp

Lines changed: 57 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,94 @@
11
/*
22
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
3+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

78
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
89
#include <LibWeb/HTML/ImageBitmap.h>
9-
#include <LibWeb/SVG/SVGImageElement.h>
1010

1111
namespace Web::HTML {
1212

13-
static void default_source_size(CanvasImageSource const& image, float& source_width, float& source_height)
13+
Gfx::IntSize canvas_image_source_dimensions(CanvasImageSource const& image)
1414
{
15-
image.visit(
16-
[&source_width, &source_height](GC::Root<HTMLImageElement> const& source) {
17-
if (source->immutable_bitmap()) {
18-
source_width = source->immutable_bitmap()->width();
19-
source_height = source->immutable_bitmap()->height();
20-
} else {
21-
// FIXME: This is very janky and not correct.
22-
source_width = source->width();
23-
source_height = source->height();
24-
}
15+
return image.visit(
16+
[](GC::Root<HTMLImageElement> const& source) -> Gfx::IntSize {
17+
if (auto immutable_bitmap = source->immutable_bitmap())
18+
return immutable_bitmap->size();
19+
20+
// FIXME: This is very janky and not correct.
21+
return { source->width(), source->height() };
22+
},
23+
[](GC::Root<SVG::SVGImageElement> const& source) -> Gfx::IntSize {
24+
if (auto immutable_bitmap = source->current_image_bitmap())
25+
return immutable_bitmap->size();
26+
27+
// FIXME: This is very janky and not correct.
28+
return { source->width()->anim_val()->value(), source->height()->anim_val()->value() };
29+
},
30+
[](GC::Root<HTMLCanvasElement> const& source) -> Gfx::IntSize {
31+
if (auto painting_surface = source->surface())
32+
return painting_surface->size();
33+
return { source->width(), source->height() };
2534
},
26-
[&source_width, &source_height](GC::Root<SVG::SVGImageElement> const& source) {
27-
if (source->current_image_bitmap()) {
28-
source_width = source->current_image_bitmap()->width();
29-
source_height = source->current_image_bitmap()->height();
30-
} else {
31-
// FIXME: This is very janky and not correct.
32-
source_width = source->width()->anim_val()->value();
33-
source_height = source->height()->anim_val()->value();
34-
}
35+
[](GC::Root<ImageBitmap> const& source) -> Gfx::IntSize {
36+
if (auto* bitmap = source->bitmap())
37+
return bitmap->size();
38+
return { source->width(), source->height() };
3539
},
36-
[&source_width, &source_height](GC::Root<HTML::HTMLVideoElement> const& source) {
37-
if (auto const bitmap = source->bitmap(); bitmap) {
38-
source_width = bitmap->width();
39-
source_height = bitmap->height();
40-
} else {
41-
source_width = source->video_width();
42-
source_height = source->video_height();
43-
}
40+
[](GC::Root<OffscreenCanvas> const& source) -> Gfx::IntSize {
41+
if (auto bitmap = source->bitmap())
42+
return bitmap->size();
43+
return {};
4444
},
45-
[&source_width, &source_height](GC::Root<OffscreenCanvas> const& source) {
46-
auto const bitmap = source->bitmap();
45+
[](GC::Root<HTMLVideoElement> const& source) -> Gfx::IntSize {
46+
if (auto bitmap = source->bitmap())
47+
return bitmap->size();
48+
return { source->video_width(), source->video_height() };
49+
});
50+
}
4751

48-
if (!bitmap) {
49-
source_width = 0;
50-
source_height = 0;
51-
return;
52-
}
53-
source_width = bitmap->width();
54-
source_height = bitmap->height();
52+
RefPtr<Gfx::ImmutableBitmap> canvas_image_source_bitmap(CanvasImageSource const& image)
53+
{
54+
return image.visit(
55+
[](OneOf<GC::Root<HTMLImageElement>, GC::Root<SVG::SVGImageElement>> auto const& element) {
56+
return element->default_image_bitmap();
5557
},
56-
[&source_width, &source_height](GC::Root<HTMLCanvasElement> const& source) {
57-
if (source->surface()) {
58-
source_width = source->surface()->size().width();
59-
source_height = source->surface()->size().height();
60-
} else {
61-
source_width = source->width();
62-
source_height = source->height();
63-
}
58+
[](GC::Root<HTMLCanvasElement> const& canvas) -> RefPtr<Gfx::ImmutableBitmap> {
59+
auto surface = canvas->surface();
60+
if (!surface)
61+
return Gfx::ImmutableBitmap::create(*canvas->get_bitmap_from_surface());
62+
return Gfx::ImmutableBitmap::create_snapshot_from_painting_surface(*surface);
6463
},
65-
[&source_width, &source_height](auto const& source) {
66-
if (source->bitmap()) {
67-
source_width = source->bitmap()->width();
68-
source_height = source->bitmap()->height();
69-
} else {
70-
source_width = source->width();
71-
source_height = source->height();
72-
}
64+
[](OneOf<GC::Root<ImageBitmap>, GC::Root<OffscreenCanvas>, GC::Root<HTMLVideoElement>> auto const& source) -> RefPtr<Gfx::ImmutableBitmap> {
65+
auto bitmap = source->bitmap();
66+
if (!bitmap)
67+
return {};
68+
return Gfx::ImmutableBitmap::create(*bitmap);
7369
});
7470
}
7571

76-
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y)
72+
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(CanvasImageSource const& image, float destination_x, float destination_y)
7773
{
7874
// If not specified, the dw and dh arguments must default to the values of sw and sh, interpreted such that one CSS pixel in the image is treated as one unit in the output bitmap's coordinate space.
7975
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
8076
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
8177
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
82-
float source_width;
83-
float source_height;
84-
default_source_size(image, source_width, source_height);
85-
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, source_width, source_height);
78+
auto size = canvas_image_source_dimensions(image);
79+
return draw_image_internal(image, 0, 0, size.width(), size.height(), destination_x, destination_y, size.width(), size.height());
8680
}
8781

88-
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y, float destination_width, float destination_height)
82+
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(CanvasImageSource const& image, float destination_x, float destination_y, float destination_width, float destination_height)
8983
{
9084
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
9185
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
9286
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
93-
float source_width;
94-
float source_height;
95-
default_source_size(image, source_width, source_height);
96-
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
87+
auto size = canvas_image_source_dimensions(image);
88+
return draw_image_internal(image, 0, 0, size.width(), size.height(), destination_x, destination_y, destination_width, destination_height);
9789
}
9890

99-
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height)
91+
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(CanvasImageSource const& image, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height)
10092
{
10193
return draw_image_internal(image, source_x, source_y, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
10294
}

Libraries/LibWeb/HTML/Canvas/CanvasDrawImage.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
/*
22
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
3+
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

78
#pragma once
89

10+
#include <LibGfx/Size.h>
911
#include <LibWeb/Forward.h>
1012
#include <LibWeb/HTML/HTMLCanvasElement.h>
1113
#include <LibWeb/HTML/HTMLImageElement.h>
1214
#include <LibWeb/HTML/HTMLVideoElement.h>
1315
#include <LibWeb/HTML/OffscreenCanvas.h>
16+
#include <LibWeb/SVG/SVGImageElement.h>
1417
#include <LibWeb/WebIDL/ExceptionOr.h>
1518

1619
namespace Web::HTML {
@@ -19,6 +22,9 @@ namespace Web::HTML {
1922
// NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly.
2023
using CanvasImageSource = Variant<GC::Root<HTMLImageElement>, GC::Root<SVG::SVGImageElement>, GC::Root<HTMLCanvasElement>, GC::Root<ImageBitmap>, GC::Root<OffscreenCanvas>, GC::Root<HTMLVideoElement>>;
2124

25+
Gfx::IntSize canvas_image_source_dimensions(CanvasImageSource const&);
26+
RefPtr<Gfx::ImmutableBitmap> canvas_image_source_bitmap(CanvasImageSource const&);
27+
2228
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawimage
2329
class CanvasDrawImage {
2430
public:

Libraries/LibWeb/HTML/Canvas/CanvasFillStrokeStyles.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <LibWeb/HTML/Canvas/CanvasState.h>
1616
#include <LibWeb/HTML/CanvasGradient.h>
1717
#include <LibWeb/HTML/CanvasPattern.h>
18+
#include <LibWeb/HTML/HTMLCanvasElement.h>
1819

1920
namespace Web::HTML {
2021

0 commit comments

Comments
 (0)