javascript - Why is there no way to rotate in canvas.getContext('2d').setTransform(a,b,c,d,e,f) and what is the
I'm playing around with HTML5 canvas, and I am trying to implement a way of moving an image around on a canvas using translation, scaling, and rotation.
I have got translation and scaling working using setTransform:
canvas.getContext('2d').setTransform(a,b,c,d,e,f)
Which is handy as it discards previous transforms applied, then applies new ones, so there is no need to remember previous state when scaling etc.
On W3 schools is states that the 2nd and 3rd params are skewY and skewX, which I at first assumed to be rotate x and y. However after applying a transform passing some values to these params, it seems it doesn't rotate - it skews the canvas! (strange I know :-D).
Can anyone tell me why there is not rotate in set transform (I'm interested as it seems strange, and skew seems pretty useless to me), and also what is the best way to do a rotate around the center of a canvas along with using setTransform
at the same time?
I'm playing around with HTML5 canvas, and I am trying to implement a way of moving an image around on a canvas using translation, scaling, and rotation.
I have got translation and scaling working using setTransform:
canvas.getContext('2d').setTransform(a,b,c,d,e,f)
Which is handy as it discards previous transforms applied, then applies new ones, so there is no need to remember previous state when scaling etc.
On W3 schools is states that the 2nd and 3rd params are skewY and skewX, which I at first assumed to be rotate x and y. However after applying a transform passing some values to these params, it seems it doesn't rotate - it skews the canvas! (strange I know :-D).
Can anyone tell me why there is not rotate in set transform (I'm interested as it seems strange, and skew seems pretty useless to me), and also what is the best way to do a rotate around the center of a canvas along with using setTransform
at the same time?
2 Answers
Reset to default 10setTransform is based on a 2D Matrix (3x3). These kinds of matrices are used for 2D/3D projections and are typically handled by game engines these days, rather than the programmers who make games.
These things are a little bit linear-algebra and a little bit calculus (for the rotation).
You're not going to like this a whole lot, but here's what you're looking at doing:
function degs_to_rads (degs) { return degs / (180/Math.PI); }
function rads_to_degs (rads) { return rads * (180/Math.PI); }
Start with these helper functions, because while we think well in degrees, puters and math systems work out better in radians.
Then you want to start with calculating your rotation:
var rotation_degs = 45,
rotation_rads = degs_to_rads(rotation_degs),
angle_sine = Math.sin(rotation_rads),
angle_cosine = Math.cos(rotation_rads);
Then, based on the layout of the parameters:
ctx.setTransform(scaleX, skewY, skewX, scaleY, posX, posY);
in the following order, when rearranged into a transform matrix:
//| scaleX, skewX, posX |
//| skewY, scaleY, posY |
//| 0, 0, 1 |
...you'd want to submit the following values:
ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);
// where x and y are now the "centre" of the rotation
This should get you rotation clockwise.
The marginal-benefit being that you should then be able to multiply everything by the scale that you initially wanted (don't multiply the posX and posY, though).
I have do some tests Based on the answer of #Norguard.
The following is the whole process of drawing a sprite on the canvas with translate, scale, rotate(at the center of rotation) and alpha(opacity):
var width = sprite.width;
var height = sprite.height;
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
// get the sin and cos value of rotate degree
var radian = sprite.rotate / 180 * Math.PI;
var sin = Math.sin(radian);
var cos = Math.cos(radian);
ctx.setTransform(
cos * sprite.scaleX,
sin * sprite.scaleX,
-sin * sprite.scaleY,
cos * sprite.scaleY,
sprite.x + toX,
sprite.y + toY
);
ctx.globalAlpha = sprite.alpha;
ctx.fillStyle = sprite.color;
ctx.fillRect(-toX, -toY, width, height);
And I made an interactive showcase you can play with:
// prepare the context
var myCanvas = document.getElementById('myCanvas');
var ctx = myCanvas.getContext('2d');
// say we have a sprite looks like this
var sprite = {
x: 50,
y: 50,
width: 50,
height: 100,
transformOriginX: 0.5, // the center of sprite width
transformOriginY: 0.5, // the center of sprite height
scaleX: 1.5,
scaleY: 1,
rotate: 45,
alpha: 0.5, // opacity
color: 'red'
};
function drawSprite() {
var width = sprite.width;
var height = sprite.height;
var scaleX = sprite.scaleX;
var scaleY = sprite.scaleY;
// get the transform-origin value
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
// get the sin and cos value of rotate degree
var radian = sprite.rotate / 180 * Math.PI;
var sin = Math.sin(radian);
var cos = Math.cos(radian);
ctx.setTransform(
cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
sprite.x + toX,
sprite.y + toY
);
ctx.globalAlpha = sprite.alpha;
ctx.fillStyle = sprite.color;
ctx.fillRect(-toX, -toY, width, height);
if (toShowInfo) {
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(-toX + width / 2, -toY + height / 2);
ctx.lineTo(-toX + width / 2, -toY);
ctx.strokeStyle = 'lime';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-toX + width / 2, -toY + height / 2);
ctx.lineTo(-toX + width, -toY + height / 2);
ctx.strokeStyle = 'yellow';
ctx.stroke();
}
}
function draw() { // main launcher
// rest the ctx
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
ctx.fillStyle = 'white';
ctx.font = '12px Arial';
ctx.textAlign = 'end';
ctx.textBaseline = 'hanging';
ctx.fillText('made by Rex Hsu', 395, 5);
// draw sprite
drawSprite();
// draw info
if (toShowInfo) { drawInfo(); };
}
function drawInfo() {
var x = sprite.x;
var y = sprite.y;
var width = sprite.width;
var height = sprite.height;
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.arc(x + toX, y + toY, 3, 0, Math.PI * 2);
ctx.fillStyle = 'lime';
ctx.fill();
ctx.font = '12px Arial';
ctx.textAlign = 'start';
ctx.textBaseline = 'middle';
ctx.fillText('center of rotation', x + toX + 10, y + toY + 0);
ctx.beginPath();
ctx.rect(x, y, width, height);
ctx.strokeStyle = 'lime';
ctx.stroke();
}
function modifySprite() {
var name = this.id;
var value = this.value;
if (name !== 'color') {
value *= 1;
}
sprite[name] = value;
draw();
}
// init
var toShowInfo = true;
document.getElementById('checkbox').onchange = function() {
toShowInfo = !toShowInfo;
draw();
};
var propsDom = document.getElementById('props');
for (var i in sprite) {
var div = document.createElement('div');
var span = document.createElement('span');
var input = document.createElement('input');
span.textContent = i + ':';
input.id = i;
input.value = sprite[i];
input.setAttribute('type', 'text');
input.addEventListener('keyup', modifySprite.bind(input));
div.appendChild(span);
div.appendChild(input);
propsDom.appendChild(div);
}
draw();
body {
font-family: monospace;
}
canvas {
float: left;
background-color: black;
}
div {
float: left;
margin: 0 0 5px 5px;
}
div > div {
float: initial;
}
span {
font-size: 16px;
}
input[type="text"] {
margin: 0 0 5px 5px;
color: #999;
border-width: 0 0 1px 0;
}
<canvas id="myCanvas" width="400" height="400"></canvas>
<div id="props" style="float: left; width: calc(100% - 400px - 5px);">
<div style="float: initial;">
<input type="checkbox" id="checkbox" checked><span>Show origin-shape and the center of rotation</span>
</div>
</div>
- 2020年或诞生超级计算机:相当5000万台笔记本
- How to remove internal padding to the right of SwiftUI Picker - Stack Overflow
- delphi - How to set up the properties of the ScrollBars of a TStringGrid? - Stack Overflow
- python - How to change the color of an individual bar in a MatPlot FuncAnimation? - Stack Overflow
- Jetpack Compose TextField keyboard dismisses immediately after typing first letter - Stack Overflow
- R CatBoost support for incremental training - Stack Overflow
- typescript - tag a href download filename nextjs - Stack Overflow
- amazon web services - azure pipeline ec2 inventory creation - Stack Overflow
- sqlite - invalid call to function 'connect' in base 'NAtiveScript'. expected 3 arguments in godo
- Fixing Crashing on load of 3D model on React Native - Stack Overflow
- flutter - Error handshake exception: handshake error in client(os error: certificate _verify_failed: unable to get local issuer
- C equivalent of C++ inline - Stack Overflow
- javascript - typeorm trying to delete relation table data when there is no change - Stack Overflow
- python - Having issues getting django-simple-captcha to work on my Contact Us page - Stack Overflow
- segmentation fault - PHP-FPM sub-process dead with signal 11 when I submit a edit in MediaWiki - Stack Overflow
- Assigning Latitude and Longitude to Turtles in NetLogo from a GIS Map - Stack Overflow
- Dbeaver customizing column and table name colors - Stack Overflow