admin管理员组

文章数量:1297005

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?

Share asked Jul 22, 2012 at 19:41 jcvandanjcvandan 14.3k19 gold badges68 silver badges104 bronze badges 0
Add a ment  | 

2 Answers 2

Reset to default 10

setTransform 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>

本文标签: