Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 13 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/canvas/element/manual/draw-element-image/canvas-transform-update.tentative.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<html>
<head>
<title>Canvas Transform: Automatic update via drawElementImage and DrawElementImageOptions</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
</head>
<body style="margin: 0;">
<canvas id="canvas" style="position: absolute; left: 0; top: 0; width: 400px; height: 400px;" width="400" height="400" content=drawable>
<div drawable id="target" style="position: absolute; left: 0; top: 0; width: 100px; height: 50px; transform-origin: 0 0;"></div>
</canvas>
<script>
'use strict';
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
assert_true(canvas.getElementTransform(target).isIdentity, 'Initial canvas transform is identity');
assert_equals(target.getBoundingClientRect().left, 0);
assert_equals(target.getBoundingClientRect().top, 0);
// Default options ({preserveElementGeometry: false}).
ctx.drawElementImage(target, 30, 40);
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translate(30, 40).toFloat64Array(),
'target canvas transform matches drawn position');
const rect = target.getBoundingClientRect();
assert_equals(rect.left, 30);
assert_equals(rect.top, 40);
}, 'drawElementImage() automatically updates element canvas transform by default');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 50, 60, {preserveElementGeometry: false});
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translate(50, 60).toFloat64Array(),
'target canvas transform matches drawn position with preserveElementGeometry: false');
const rect = target.getBoundingClientRect();
assert_equals(rect.left, 50);
assert_equals(rect.top, 60);
}, 'drawElementImage() updates element canvas transform with explicit {preserveElementGeometry: false}');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
canvas.updateElementGeometry(target, {canvasTransform: new DOMMatrix().translate(10, 20)});
const initialTransform = canvas.getElementTransform(target);
const initialRect = target.getBoundingClientRect();
ctx.drawElementImage(target, 100, 120, {preserveElementGeometry: true});
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(), initialTransform.toFloat64Array(),
'target canvas transform is unchanged when preserveElementGeometry is true');
const rect = target.getBoundingClientRect();
assert_equals(rect.left, initialRect.left);
assert_equals(rect.top, initialRect.top);
assert_equals(rect.width, initialRect.width);
assert_equals(rect.height, initialRect.height);
}, 'drawElementImage() does not update element canvas transform with {preserveElementGeometry: true}');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
// Overload: (element, dx, dy, dwidth, dheight, [options])
ctx.drawElementImage(target, 20, 30, 200, 100);
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translate(20, 30).scale(2, 2).toFloat64Array());
let rect = target.getBoundingClientRect();
assert_equals(rect.left, 20);
assert_equals(rect.top, 30);
assert_equals(rect.width, 200);
assert_equals(rect.height, 100);
const prevTransform = canvas.getElementTransform(target);
ctx.drawElementImage(target, 80, 90, 300, 150, {preserveElementGeometry: true});
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(), prevTransform.toFloat64Array());
rect = target.getBoundingClientRect();
assert_equals(rect.left, 20);
assert_equals(rect.top, 30);
assert_equals(rect.width, 200);
assert_equals(rect.height, 100);
}, 'drawElementImage() with destination size overload (4 arguments)');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
// Overload: (element, sx, sy, swidth, sheight, dx, dy, [options])
ctx.drawElementImage(target, 0, 0, 100, 50, 40, 50);
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translate(40, 50).toFloat64Array());
let rect = target.getBoundingClientRect();
assert_equals(rect.left, 40);
assert_equals(rect.top, 50);
const prevTransform = canvas.getElementTransform(target);
ctx.drawElementImage(target, 0, 0, 100, 50, 80, 90, {preserveElementGeometry: true});
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(), prevTransform.toFloat64Array());
rect = target.getBoundingClientRect();
assert_equals(rect.left, 40);
assert_equals(rect.top, 50);
}, 'drawElementImage() with source rect overload (6 arguments)');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => canvas.clearElementGeometry(target));
await waitForCanvasPaint(canvas);
// Overload: (element, sx, sy, swidth, sheight, dx, dy, dwidth, dheight, [options])
ctx.drawElementImage(target, 0, 0, 100, 50, 15, 25, 200, 100);
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translate(15, 25).scale(2, 2).toFloat64Array());
let rect = target.getBoundingClientRect();
assert_equals(rect.left, 15);
assert_equals(rect.top, 25);
assert_equals(rect.width, 200);
assert_equals(rect.height, 100);
const prevTransform = canvas.getElementTransform(target);
ctx.drawElementImage(target, 0, 0, 100, 50, 70, 80, 300, 150, {preserveElementGeometry: true});
assert_array_equals(canvas.getElementTransform(target).toFloat64Array(), prevTransform.toFloat64Array());
rect = target.getBoundingClientRect();
assert_equals(rect.left, 15);
assert_equals(rect.top, 25);
assert_equals(rect.width, 200);
assert_equals(rect.height, 100);
}, 'drawElementImage() with source and destination rect overload (8 arguments)');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
ctx.reset();
canvas.clearElementGeometry(target);
});
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 10, 5, 50, 25, 17, -9, 100, 50);
const expected = new DOMMatrix()
.translateSelf(17, -9)
.scaleSelf(2, 2)
.translateSelf(-10, -5);
assert_array_approx_equals(
canvas.getElementTransform(target).toFloat64Array(),
expected.toFloat64Array(), 0.001);
}, 'drawElementImage() accounts for non-zero sx and sy offset');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
target.style.transform = '';
ctx.reset();
canvas.clearElementGeometry(target);
});
target.style.transform = 'rotate(45deg)';
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 0, 0);
assert_array_equals(
canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().toFloat64Array());
}, 'drawElementImage() element transform does not include CSS transform on the element');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
target.style.transformOrigin = '0 0';
ctx.reset();
canvas.clearElementGeometry(target);
});
await waitForCanvasPaint(canvas);
ctx.translate(42, 17);
ctx.drawElementImage(target, 0, 0);
assert_array_equals(
canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translateSelf(42, 17).toFloat64Array());
ctx.reset();
target.style.transformOrigin = '5px 5px';
await waitForCanvasPaint(canvas);
ctx.rotate(Math.PI / 4);
ctx.drawElementImage(target, 0, 0);
assert_array_approx_equals(
canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().rotateSelf(45).toFloat64Array(), 0.001);
}, 'drawElementImage() accounts for CTM and ignores element transform-origin');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
canvas.style.zoom = '';
ctx.reset();
canvas.clearElementGeometry(target);
});
canvas.style.zoom = '1.5';
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 7, 11);
assert_array_equals(
canvas.getElementTransform(target).toFloat64Array(),
new DOMMatrix().translateSelf(7, 11).toFloat64Array());
}, 'drawElementImage() element transform is not affected by zoom on the canvas');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
canvas.width = 400;
canvas.height = 400;
canvas.clearElementGeometry(target);
});
canvas.width = 800;
canvas.height = 200;
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 7, 11);
const expected = new DOMMatrix()
.scaleSelf(0.5, 2)
.translateSelf(7, 11)
.scaleSelf(2, 0.5);
assert_array_equals(
canvas.getElementTransform(target).toFloat64Array(),
expected.toFloat64Array());
}, 'drawElementImage() element transform accounts for canvas grid pixel scale');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
canvas.style.width = '400px';
canvas.style.height = '400px';
ctx.reset();
canvas.clearElementGeometry(target);
});
canvas.style.width = '200px';
canvas.style.height = '800px';
await waitForCanvasPaint(canvas);
ctx.drawElementImage(target, 7, 11);
const expected = new DOMMatrix()
.scaleSelf(0.5, 2)
.translateSelf(7, 11)
.scaleSelf(2, 0.5);
assert_array_equals(
canvas.getElementTransform(target).toFloat64Array(),
expected.toFloat64Array());
}, 'drawElementImage() element transform accounts for canvas CSS pixel scale');
promise_test(async (t) => {
const canvas = document.getElementById('canvas');
const target = document.getElementById('target');
const ctx = canvas.getContext('2d');
t.add_cleanup(() => {
canvas.width = 400;
canvas.height = 400;
canvas.style.zoom = '';
target.style.transformOrigin = '0 0';
ctx.reset();
canvas.clearElementGeometry(target);
});
canvas.width = 800;
canvas.height = 200;
canvas.style.zoom = '1.5';
target.style.transformOrigin = '5px 5px';
await waitForCanvasPaint(canvas);
ctx.translate(42, 17);
ctx.rotate(Math.PI / 4);
ctx.drawElementImage(target, 13, -9);
const expected = new DOMMatrix()
.scaleSelf(0.5, 2)
.translateSelf(42, 17)
.rotateSelf(45)
.translateSelf(13, -9)
.scaleSelf(2, 0.5);
assert_array_approx_equals(
canvas.getElementTransform(target).toFloat64Array(),
expected.toFloat64Array(), 0.001);
}, 'drawElementImage() element transform accounts for canvas pixel scale, zoom, CTM, and dx/dy');
</script>
</body>
</html>