LibWeb: Implement CanvasPattern.setTransform()

This method applies the given transformation matrix to a pattern.
This commit is contained in:
Tim Ledbetter 2025-10-26 19:06:13 +00:00 committed by Jelle Raaijmakers
parent 9312a9f86f
commit e1ff1e2095
Notes: github-actions[bot] 2025-10-27 23:42:03 +00:00
10 changed files with 87 additions and 7 deletions

View file

@ -96,6 +96,8 @@ public:
RefPtr<ImmutableBitmap> image() const { return m_image; }
Repetition repetition() const { return m_repetition; }
Optional<AffineTransform> const& transform() const { return m_transform; }
void set_transform(AffineTransform const& transform) { m_transform = transform; }
private:
CanvasPatternPaintStyle(RefPtr<ImmutableBitmap> image, Repetition repetition)
@ -106,6 +108,7 @@ private:
RefPtr<ImmutableBitmap> m_image;
Repetition m_repetition { Repetition::Repeat };
Optional<AffineTransform> m_transform;
};
// The following paint styles implement the gradients required for the HTML canvas.

View file

@ -103,12 +103,20 @@ static void apply_paint_style(SkPaint& paint, PaintStyle const& style)
// FIXME: Implement sampling configuration.
SkSamplingOptions sk_sampling_options { SkFilterMode::kLinear };
Optional<SkMatrix> transformation_matrix;
if (canvas_pattern->transform().has_value()) {
auto const& transform = canvas_pattern->transform().value();
transformation_matrix = SkMatrix::MakeAll(
transform.a(), transform.c(), transform.e(),
transform.b(), transform.d(), transform.f(),
0, 0, 1);
}
auto shader = sk_image->makeShader(
repeat_x ? SkTileMode::kRepeat : SkTileMode::kDecal,
repeat_y ? SkTileMode::kRepeat : SkTileMode::kDecal,
sk_sampling_options);
paint.setShader(shader);
sk_sampling_options, transformation_matrix.has_value() ? &transformation_matrix.value() : nullptr);
paint.setShader(move(shader));
} else {
dbgln("FIXME: Unsupported PaintStyle");
}

View file

@ -75,4 +75,21 @@ void CanvasPattern::initialize(JS::Realm& realm)
Base::initialize(realm);
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-canvaspattern-settransform
WebIDL::ExceptionOr<void> CanvasPattern::set_transform(Geometry::DOMMatrix2DInit& transform)
{
// 1. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
auto matrix = TRY(Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm(), transform));
// 2. If one or more of matrix's m11 element, m12 element, m21 element, m22 element, m41 element, or m42 element are infinite or NaN, then return.
if (!isfinite(matrix->m11()) || !isfinite(matrix->m12()) || !isfinite(matrix->m21()) || !isfinite(matrix->m22()) || !isfinite(matrix->m41()) || !isfinite(matrix->m42()))
return {};
// 3. Reset the pattern's transformation matrix to matrix.
Gfx::AffineTransform affine_transform(matrix->a(), matrix->b(), matrix->c(), matrix->d(), matrix->e(), matrix->f());
m_pattern->set_transform(affine_transform);
return {};
}
}

View file

@ -24,6 +24,7 @@ public:
~CanvasPattern();
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style() { return m_pattern; }
WebIDL::ExceptionOr<void> set_transform(Geometry::DOMMatrix2DInit& transform);
private:
CanvasPattern(JS::Realm&, Gfx::CanvasPatternPaintStyle&);

View file

@ -1,6 +1,8 @@
#import <Geometry/DOMMatrix.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspattern
[Exposed=(Window,Worker)]
interface CanvasPattern {
// opaque object
[FIXME] undefined setTransform(optional DOMMatrix2DInit transform = {});
undefined setTransform(optional DOMMatrix2DInit transform = {});
};

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<style>
* {
margin: 0;
overflow: hidden;
}
body {
background-color: white;
}
</style>
<img src="../images/canvas-pattern-transform-ref.png">

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<meta name="fuzzy" content="maxDifference=0-93; totalPixels=0-2396">
<link rel="match" href="../expected/canvas-pattern-transform-ref.html" />
<style>
* {
margin: 0;
}
body {
background-color: white;
}
canvas {
border: 2px solid black;
margin: 5px;
padding: 5px;
}
</style>
<canvas data-type="img" data-repeat="repeat" width="250" height="250"></canvas>
<canvas data-type="img" data-repeat="repeat-x" width="250" height="250"></canvas>
<canvas data-type="img" data-repeat="repeat-y" width="250" height="250"></canvas>
<canvas data-type="img" data-repeat="no-repeat" width="250" height="250"></canvas>
<script>
const img = new Image();
img.onload = () => {
for (const canvas of document.querySelectorAll('canvas[data-type=img]')) {
const ctx = canvas.getContext('2d');
const pattern = ctx.createPattern(img, canvas.getAttribute('data-repeat'));
const matrix = new DOMMatrix([1, 0.2, 0.8, 1, 0, 0]);
pattern.setTransform(matrix);
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
};
img.src = '../data/car.png';
</script>

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Canvas test: 2d.pattern.transform.identity
1 Pass
Pass Canvas test: 2d.pattern.transform.identity

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Canvas test: 2d.pattern.transform.infinity
1 Pass
Pass Canvas test: 2d.pattern.transform.infinity