最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why is there no way to rotate in canvas.getContext('2d').setTransform(a,b,c,d,e,f) and what is the

programmeradmin11浏览0评论

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>

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论