HTML5 Translate Method, How to Reset to Default

HTML5 translate method, how to reset to default?

ctx.setTransform(1, 0, 0, 1, 0, 0);

MDN setTransform documentation

Clear entire, transformed HTML5 Canvas while preserving context transform

Keeping track of all the transformation information like you are presumably doing is what several others so far have done (like cake.js and my own library, for two). I think doing this will pretty much be an inevitability for any large canvas library.

Ilmari of cake.js even complained to mozilla:
https://bugzilla.mozilla.org/show_bug.cgi?id=408804

You could instead call save/restore around your clear method:

// I have lots of transforms right now
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
// Will always clear the right space
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.restore();
// Still have my old transforms

Won't that satisfy your case?

HTML5 canvas translate back after scale and rotation

Transformations

For the easy answer if you are not interested in the how skip to the bottom where you will find an alternative approch to your problem. It is all commented. I have made a bit of a guess as to what you wanted.

If you are interested in what I consider a simpler way to use the 2D transformation functions read the rest.

Matrix Math

When you use translate, scale, and rotate via the canvas 2D API what you are doing is multiplying the existing matrix with one created with each function.

Basically when you do

ctx.rotate(Math.PI); // rotate 180 deg

the API creates a new rotation matrix and multiplies the existing matrix with it.

Unlike normal math multiplication matrix multiplication will change the result depending on what order you multiply. In normal math multiplication A * B = B * A but this does not hold true for matrices mA * mB != mB * mA (Note the not equals)

This becomes more problematic when you need to apply several different transformations.

ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);

Does not give the same result as

ctx.scale(2,2);
ctx.translate(100,100);
ctx.rotate(Math.PI/2);

The order that you need to apply the tranforms depends on what you are trying to achieve. Using the API this way is very handy for complex linked animations. Unfortunately it is also a source of endless frustration if you are not aware of matrix math. It also forces many to use the save and restore functions to restore the default transformation, that in some situations can be very costly in GPU performance.

setTransform()

We are in luck though as the 2D API also has the function ctx.setTransform(a, b, c, d, e, f) which is all you really should ever need. This function replaces the existing transform with the one supplied. Most of the documentation is rather vague as to the meaning of the a,b,c,d,e,f but contained in them is the rotation, scale, and translation.

One handy use of the function is to set the default transform rather than use save and restore.

I see this type of thing a lot. (The Example 1, referenced further down)

// transform for image 1
ctx.save(); // save state
ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.restore(); // restore saved state

// transform for image 2
ctx.save(); // save state agian
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.restore(); // restore saved state

An easier way is to just drop the save and restores and reset the transform manually by setting it to the Identity matrix

ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform

// transform for image 2
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform

Now then I am sure you are still wondering what are these numbers being passed to setTransform and what do they mean?

The easiest way to remember them is as 2 vectors and 1 coordinate. The two vectors describe the direction and scale of a single pixel, the coordinate is simply the x,y pixel location of the origin (the location that drawing at 0,0 will be on the canvas).

A Pixel and its axis

Imagine a single pixel, this is the abstract transformed pixel that can be scaled and rotated by the current transformation. It has two axis, X and Y. To describe each axis we need two numbers ( a vector) these describes the screen (untransformed) direction and scale of the top and left side of the pixel. So for a normal pixel that matches the screen pixels the X axis is across the top from left to right and is one pixel long. The vector is (1,0) one pixel across, no pixels down. For the Y axis that goes down the screen the vector is (0,1) no pixels across, one pixel down. The origin is the top right screen pixel which is at coordinate (0,0).

Thus we get the Identity Matrix, the default matrix for the 2D API (and many other APIs) The X axis (1,0), Y axis (0,1) and the origin (0,0) which match the six arguments for setTransform(1,0,0,1,0,0).

Now say we want to scale the pixel up. All we do is increase the size of the X and Y Axis setTransform(2,0,0,2,0,0) is the same as scale(2,2) (from the default transform) Our pixel's top is now two pixels long across the top and two pixels long down the left side. To scale down setTransform(0.5,0,0,0.5,0,0) our pixel is now half a pixel across and down.

These two axis vectors (a,b) & (c,d) can point in any direction, are completely independent of each other , they don't have to be at 90 deg to each other so can skew the pixel, nor do they require that they be the same length so you can change the pixel aspect. The origin is also independent and is just the canvas absolute coordinates in pixels of the origin and can be set to anywhere on or off the canvas.

Now say we want to rotate the transform 90Deg clockwise, scale up both axes by 2 and position the origin at the center of the canvas. We want the X axis (top) of our pixel to be 2 pixels long and pointing down the screen. The vector is (0,2) 0 across and two down. We want the left side of our pixel to 2 long and point to the left of the screen (-2,0) Negative two across and none down. And the origin at the center is (canvas.width / 2, canvas.height / 2) to get the final matrix that is setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)

Rotate the other way is setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)

Easy Rotate 90deg

You may notice that rotating 90 degrees is just swapping the vectors and changing a sign.

  • The vector (x,y) rotated 90 degrees clockwise is (-y,x).
  • The vector (x,y) rotated 90 degrees anti-clockwise is (y,-x).

Swap the x, and y and negate the y for clockwise or negate the x for the anticlockwise rotation.

For 180 it is starting at 0 deg vector (1,0)

// input vector
var x = 1;
var y = 0;
// rotated vector
var rx90 = -y; // swap y to x and make it negative
var ry90 = x; // x to y as is
// rotate again same thing
var rx180 = -ry90;
var rx180 = rx90;
// Now for 270
var rx270 = -ry180; // swap y to x and make it negative
var rx270 = rx180;

Or all in terms of just x and y

  • 0 deg (x,y)
  • 90deg (-y,x)
  • 180deg (-x,-y)
  • 270deg (y,-x)
  • and back to 360 (x,y).

This is a very handy attribute of a vector that we can exploit to simplify the creation of our transformation matrix. In most situations we do not want to skew our image thus we know that the Y axis is always 90Deg clockwise from the x axis. Now we only need to describe the x axis and by applying the 90deg rotation to that vector we have the y axis.

So the vars x and y are the scale and direction of the top of our pixel (x axis), ox, oy are the location of the origin on the canvas (translation) .

var x = 1; // one pixel across
var y = 0; // none down
var ox = canvas.width / 2; // center of canvas
var oy = canvas.height / 2;

Now to create the transform is

ctx.setTransform(x, y, -y, x, ox, oy);

Note that the y axis is at 90 degs to the x axis.

Trig and the Unit vector

All well and easy when the axis are aligned to the top and sides, how do you get the vector for a axis at an arbitrary angle such as is supplied by the argument for ctx.rotate(angle) For that we need a tiny bit of trig. The Math function Math.cos(angle) returns the x component of the angle, angle and Math.sin(angle) gives us the Y component. For zero deg cos(0) = 1 and sin(0) = 0 for 90 deg (Math.PI/2 radians) cos(PI/2) = 0 and sin(PI/2) = 1.

The beauty of using sin and cos is that the two numbers that we get for our vector always give us a vector that is 1 unit (pixel) long (this is called a normalised vector or a unit vector) thus cos(a)2 + sin(a)2 = 1

Why does this matter? because it makes scaling very easy. Assuming that we always keep the aspect square we only need one number for the scale. To scale a vector you simply multiply it by the scale

var scale = 2;  // scale of 2
var ang = Math.random() * 100; // any random angle
var x = Math.cos(ang); // get the x axis as a unit vector.
var y = Math.sin(ang);
// scale the axis
x *= scale;
y *= scale;

the vector x,y is now two units long.

Better than using save, restore, rotate, scale, translate... :(

Now put it all together to create a matrix with an arbitrary rotation, scale and translation (origin)

// ctx is the 2D context, 
// originX, and originY is the origin, same as ctx.translate(originX,originY)
// rotation is the angle in radians same as ctx.rotate(rotation)
// scale is the scale of x and y axis same as ctx.scale(scale,scale)
function createTransform(ctx,originX,originY,rotation,scale){
var x, y;
x = Math.cos(rotation) * scale;
y = Math.sin(rotation) * scale;
ctx.setTransform(x, y, -y, x, originX, originY);
}

Now to apply that to the example (1) given above

// dont need ctx.save();  // save state
// dont need ctx.scale(2,2);
// dont need ctx.rotate(Math.PI/2);
// dont need ctx.translate(100,100);
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image normally
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
// dont need ctx.restore(); // restore saved state

// transform for image 2
// dont need ctx.save(); // save state agian
// dont need ctx.scale(1,1);
// dont need ctx.rotate(Math.PI);
// dont need ctx.translate(100,100);
// we don't have to reset the default transform because
// ctx.setTransform completely replaces the current transform
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
// dont need ctx.restore(); // restore saved state

And that is how you use setTransform to simplify transforming the canvas, rather than guessing, trial and error, scale, rotates, and translates back and forth within a sea of save and restores.

Using that to simplify your code

The answer

And now to your question

I am not entirely sure what you are after, I presume you a dont mind scaling the canvas to accommodate the image, that the image is always in the center and that the aspect remains the same. As the rotations are aligned to the screen I will set the transforms manualy

    // this is in set up code
const MAX_SIZE_WIDTH = 640;
const MAX_SIZE_HEIGHT = 480;
orientationData = [];
orientationData[6] = Math.PI/2; // xAxis pointing down
orientationData[8] = -Math.PI/2; // xAxis pointing up
orientationData[3] = Math.PI; //xAxis pointing to left

// in your code
var orient,w,h,iw,ih,scale,ax,ay; // w and h are canvas size

// assume image is the loaded image
iw = image.width; // get the image width and height so I dont have to type as much.
ih = image.height;

if(orientation != 1){
var orient = orientationData[orientation];
if(orient === undefined){
return; // bad data so return
}

// get scale and resize canvas to suit

// is the image on the side
if(orientation === 6 || orientation === 8){
// on side so swap width and height
// get the height and width scales for the image, dont scale
// if the image is smaller than the dimension
scale = Math.min(1,
MAX_SIZE_WIDTH / ih,
MAX_SIZE_HEIGHT / iw
);
w = canvas.width = scale * ih;
h = canvas.height = scale * iw;
}else{
// for normal orientation
scale = Math.min(1,
MAX_SIZE_WIDTH / iw,
MAX_SIZE_HEIGHT / ih
);
h = canvas.height = scale * ih;
w = canvas.width = scale * iw;
}

// Do you really need to clear the canvas if the image is filling it??
// ensure that the default transform is set
ctx.setTransform(1, 0, 0, 1, 0, 0);
// clear the canvas
ctx.clearRect(0, 0, w, h);

// now create the transformation matrix to
// position the image in the center of the screen

// first get the xAxis and scale it
ax = Math.cos(orient) * scale;
ay = Math.sin(orient) * scale;
// now set the transform, the origin is always the canvas center
// and the Y axis is 90 deg clockwise from the xAxis and same scale
ctx.setTransform(ax, ay, -ay, ax, w / 2, h / 2);

// now draw the image offset by half its width and height
// so that it is centered on the canvas

ctx.drawImage(image,-iw / 2, -ih / 2);

// restore the default transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
} // done.

HTML Canvas reset drawing point after transform and rotate

You need to save the canvas state before rotating and translating, and then restore the state when the transformation is done.

var file, canvas, ctx, image, fileURL, rotation = 90;
function fileUpload(files) { file = files[0] fileURL = URL.createObjectURL(file) canvas = document.getElementById('myCanvas') canvas.style.backgroundColor = "blue" ctx = canvas.getContext('2d') image = new Image()
image.onload = function() { canvas.width = 500 canvas.height = (500 * this.height) / this.width ctx.drawImage(image, 0, 0, canvas.width, canvas.height) } image.src = fileURL}
function rotate() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); //save canvas state ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(rotation * Math.PI / 180); ctx.translate(-canvas.width / 2, -canvas.height / 2); ctx.drawImage(image, 0, 0, canvas.width, canvas.height); rotation += 90; ctx.restore(); //restore canvas state}
canvas {border: 1px solid red}
<canvas id="myCanvas"></canvas><br/><input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera"><br/><br/><button onclick="rotate()">Rotate</button>

HTML5 Canvas save() and restore() performance

Animation and Game performance tips.

Avoid save restore

Use setTransform as that will negate the need for save and restore.

There are many reasons that save an restore will slow things down and these are dependent on the current GPU && 2D context state. If you have the current fill and/or stroke styles set to a large pattern, or you have a complex font / gradient, or you are using filters (if available) then the save and restore process can take longer than rendering the image.

When writing for animations and games performance is everything, for me it is about sprite counts. The more sprites I can draw per frame (60th second) the more FX I can add, the more detailed the environment, and the better the game.

I leave the state open ended, that is I do not keep a detailed track of the current 2D context state. This way I never have to use save and restore.

ctx.setTransform rather than ctx.transform

Because the transforms functions transform, rotate, scale, translate multiply the current transform, they are seldom used, as i do not know what the transform state is.

To deal with the unknown I use setTransform that completely replaces the current transformation matrix. This also allows me to set the scale and translation in one call without needing to know what the current state is.

ctx.setTransform(scaleX,0,0,scaleY,posX,posY); // scale and translate in one call

I could also add the rotation but the javascript code to find the x,y axis vectors (the first 4 numbers in setTransform) is slower than rotate.

Sprites and rendering them

Below is an expanded sprite function. It draws a sprite from a sprite sheet, the sprite has x & y scale, position, and center, and as I always use alpha so set alpha as well

// image is the image. Must have an array of sprites
// image.sprites = [{x:0,y:0,w:10,h:10},{x:20,y:0,w:30,h:40},....]
// where the position and size of each sprite is kept
// spriteInd is the index of the sprite
// x,y position on sprite center
// cx,cy location of sprite center (I also have that in the sprite list for some situations)
// sx,sy x and y scales
// r rotation in radians
// a alpha value
function drawSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}

On just an average machine you can render 1000 +sprites at full frame rate with that function. On Firefox (at time of writing) I am getting 2000+ for that function (sprites are randomly selected sprites from a 1024 by 2048 sprite sheet) max sprite size 256 * 256

But I have well over 15 such functions, each with the minimum functionality to do what I want. If it is never rotated, or scaled (ie for UI) then

function drawSprite(image, spriteInd, x, y, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(1,0,0,1,x,y); // set scale and position
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,0,0,w,h); // render the subimage
}

Or the simplest play sprite, particle, bullets, etc

function drawSprite(image, spriteInd, x, y,s,r,a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(s,0,0,s,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-w/2,-h/2,w,h); // render the subimage
}

if it is a background image

function drawSprite(image){
var s = Math.max(image.width / canvasWidth, image.height / canvasHeight); // canvasWidth and height are globals
ctx.setTransform(s,0,0,s,0,0); // set scale and position
ctx.globalAlpha = 1;
ctx.drawImage(image,0,0); // render the subimage
}

It is common that the playfield can be zoomed, panned, and rotated. For this I maintain a closure transform state (all globals above are closed over variables and part of the render object)

// all coords are relative to the global transfrom
function drawGlobalSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
// m1 to m6 are the global transform
ctx.setTransform(m1,m2,m3,m4,m5,m6); // set playfield
ctx.transform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a * globalAlpha; (a real global alpha)
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}

All the above are about as fast as you can get for practical game sprite rendering.

General tips

Never use any of the vector type rendering methods (unless you have the spare frame time) like, fill, stroke, filltext, arc, rect, moveTo, lineTo as they are an instant slowdown. If you need to render text create a offscreen canvas, render once to that, and display as a sprite or image.

Image sizes and GPU RAM

When creating content, always use the power rule for image sizes. GPU handle images in sizes that are powers of 2. (2,4,8,16,32,64,128....) so the width and height have to be a power of two. ie 1024 by 512, or 2048 by 128 are good sizes.

When you do not use these sizes the 2D context does not care, what it does is expand the image to fit the closest power. So if I have an image that is 300 by 300 to fit that on the GPU the image has to be expanded to the closest power, which is 512 by 512. So the actual memory footprint is over 2.5 times greater than the pixels you are able to display. When the GPU runs out of local memory it will start switching memory from mainboard RAM, when this happens your frame rate drops to unusable.

Ensuring that you size images so that you do not waste RAM will mean you can pack a lot more into you game before you hit the RAM wall (which for smaller devices is not much at all).

GC is a major frame theef

One last optimisation is to make sure that the GC (garbage collector) has little to nothing to do. With in the main loop, avoid using new (reuse and object rather than dereference it and create another), avoid pushing and popping from arrays (keep their lengths from falling) keep a separate count of active items. Create a custom iterator and push functions that are item context aware (know if an array item is active or not). When you push you don't push a new item unless there are no inactive items, when an item becomes inactive, leave it in the array and use it later if one is needed.

There is a simple strategy that I call a fast stack that is beyond the scope of this answer but can handle 1000s of transient (short lived) gameobjects with ZERO GC load. Some of the better game engines use a similar approch (pool arrays that provide a pool of inactive items).

GC should be less than 5% of your game activity, if not you need to find where you are needlessly creating and dereferencing.

Html5 canvas - Translate function behaving weirdly

The problem is that after each translation in Circle.draw(), the context is not restored to its original state. Future translate(this.x, this.y); calls keep moving the context right and downward relative to the previous transformation endlessly.

Use ctx.save() and ctx.restore() at the beginning and end of your draw() function to move the context back to its original location after drawing.

class Circle {  constructor(x, y, r) {    this.x = x;    this.y = y;    this.r = r;  }
draw() { ctx.save(); ctx.strokeStyle = "white"; ctx.translate(this.x, this.y); ctx.beginPath(); ctx.arc(0, 0, this.r, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); ctx.restore(); }}
let canvas;let ctx;let circle;
(function init() { canvas = document.querySelector("canvas"); canvas.width = innerWidth; canvas.height = innerHeight; ctx = canvas.getContext("2d"); circle = new Circle(canvas.width / 2, canvas.height / 2, 30); loop();})();
function loop() { ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); circle.draw(); requestAnimationFrame(loop);}
body {  margin: 0;  height: 100vh;}
<canvas></canvas>

Translating a html5 canvas

You can apply the transforms and call drawImage passing in the canvas itself.

ctx.save();
ctx.translate(0, 5);
ctx.drawImage(canvas, 0, 0);
ctx.restore();

When doing that, the original contents will still be below.
Depending on the effect you're trying to accomplish, setting the globalCompositeOperation may help you with that.

But it's likely you'll need to use drawImage to first copy to a second canvas, clear the current, apply the transform and draw from the copy.

translate() method not working in jCanvaScript for HTML5 canvas?

try like this example..

<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
#myCanvas {
border: 1px solid #9C9898;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var rectWidth = 150;
var rectHeight = 75;

// translate context to center of canvas
context.translate(canvas.width / 2, canvas.height / 2);

context.fillStyle = 'blue';
context.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);
</script>
</body>
</html>

How to implement rotate method for html5 canvas

Here's the pacman rotating clockwise about the direction angle.

update

Now showing transform error in purple. Rotation looks correct but translation is off.

update 2

I think my previous test was bad. The transform looks good and there was only some minor adjustment of to_radians. The new test shows the context ranslation/rotation followed by the Transform class.

var can = document.getElementById('can');var context = can.getContext('2d');var to_rad = Math.PI / 180; // to radians
function main() { context.fillStyle="black"; context.fillRect(0, 0, can.width, can.height);
var pm = { direction: 0, posX: 50, posY: 100, size: 20, startAngle: 45, endAngle: 315 };
var i = 0; function loopTest() { pm.direction = i * 90; pm.posX = 50 * (i+1); renderContent(pm); setTimeout(function(){ renderContent2(pm); }, 1000); i++; if (i < 4) { setTimeout(loopTest, 2000); } }
loopTest();

}
function renderContent(pm) { context.save(); context.beginPath(); context.fillStyle = "Yellow"; context.strokeStyle = "Yellow"; context.translate(pm.posX, pm.posY); context.rotate(pm.direction * to_rad); context.translate(-pm.posX, -pm.posY); context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad); context.lineTo(pm.posX, pm.posY); context.stroke(); context.fill();
context.fillStyle="red"; context.font="16px Arial"; context.textAlign="center"; context.fillText(pm.direction, pm.posX,pm.posY+pm.size); context.restore();}

function renderContent2(pm) { var t = new Transform(); context.save(); context.beginPath(); context.fillStyle = "#990099"; context.strokeStyle = "#990099"; t.translate(pm.posX, pm.posY); t.rotate(pm.direction * to_rad); t.translate(-pm.posX, -pm.posY); context.transform.apply(context, t.m); context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad); context.lineTo(pm.posX, pm.posY); context.stroke(); context.fill(); context.fillStyle="White"; context.font="16px Arial"; context.textAlign="center"; context.fillText(pm.direction, pm.posX,pm.posY+pm.size); context.restore();}

function Transform() { this.identity();}Transform.prototype.identity = function () { this.m = [1, 0, 0, 1, 0, 0];};Transform.prototype.rotate = function (rad) { var c = Math.cos(rad); var s = Math.sin(rad); var m11 = this.m[0] * c + this.m[2] * s; var m12 = this.m[1] * c + this.m[3] * s; var m21 = this.m[0] * -s + this.m[2] * c; var m22 = this.m[1] * -s + this.m[3] * c; this.m[0] = m11; this.m[1] = m12; this.m[2] = m21; this.m[3] = m22;};Transform.prototype.translate = function (x, y) { this.m[4] += this.m[0] * x + this.m[2] * y; this.m[5] += this.m[1] * x + this.m[3] * y;};
Transform.prototype.scale = function (sx, sy) { this.m[0] *= sx; this.m[1] *= sx; this.m[2] *= sy; this.m[3] *= sy;};

main();
canvas {  border:3px solid red;}
<canvas id="can" width="300" height="200"></canvas>


Related Topics



Leave a reply



Submit