Flickering images in canvas animation - javascript

window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
});
The issue is when i try to loop the image to create a ring shape and animate it by using setInterval, clearing and redrawing the images on the canvas results to flicker.
Is there a way to smoothen the orbit animation? or a better way to create an orbit animation?
Codepen

Canvas animations.
When animating on a browser always use requestAnimationFrame, it gives far better results than setInterval and setTimeout by only moving new pixel data to the display in sync with the refresh time.
Why the flicker
It's complicated...
The basic reason is that you were loading the image each time you wanted to use it. The onload will not fire until after your code has stopped running. When your code stops running the canvas content will be presented to the display and it will be empty because you just cleared it (no event will fire until you exit the draw function). Then the onload events will start to fire, each drawing the image on the canvas. When the event exits the browser thinks "ok you want to put that to the display!" and the whole canvas is again presented to the display. Because this happens much faster than the screen refresh rate you get you weird shearing, flickering, jumping effect.
Another reason is the use of setInterval The Answer from DominicValenciana still uses setInterval and if you look carefully you will see that it is not a smooth as using requestAnimationFrame. Good animation should be smooth as silk (well as close a possible)
Some tips below the demo.
This answer will also help explain image loading and drawing.
(function () {
const oneDeg = Math.PI / 180;
const D1 = Math.PI / 180; // again for lazy programmers
const centerX = 400;
const centerY = 400;
const radius = 300;
// create and load image
const base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
// create size and add to DOM a canvas
const c = document.createElement("canvas");
c.width = 800;
c.height = 800;
document.body.appendChild(c);
const ctx = c.getContext("2d"); // get context
var step = 0; // the animation step
function draw() { // the main draw function
var x,y,i,angle;
if(base_image.complete){ // has the image loaded
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0, 0, c.width, c.height);
for (i = 0; i < Math.PI * 2; i += D1 * 10) { // 360 in steps of 10
angle = step + i; // angle in radians
x = centerX + Math.sin(angle) * radius;
y = centerY + Math.cos(angle) * radius;
ctx.setTransform(1, 0, 0, 1, x, y); // set translate and scale no need for save restore
ctx.rotate(angle); // set angle
ctx.drawImage(base_image, 0, 0); //draw image
}
step += 1* D1; // step 1 deg per frame
}
requestAnimationFrame(draw);
}
draw();
})();
Angles, PI, and Trig
Your calculation for the position of the sprite
x = Math.sin(angle);
y = Math.cos(angle);
has zero (angle = 0) at the top of the display (the 12 oclock position). To match the same angle as used by the canvas rotate function use
x = Math.cos(angle);
y = Math.sin(angle);
this has zero at the right of the screen (the 3oclock position)
If you want to keep creating animations and computer graphics. Forget about 360 deg. Circles are 2PI around not 360, PI is half a turn 180, PI/2 is a quarter turn 90, PI/30 is a minute hands movement per minute, the distance around a circle is 2*PI*radius, around half a circle PI * radius. Every trig function in JS uses radians (not to be annoying, or obtuse, but because it is far simpler to use), You will always need to use PI at some stage if you work in deg, so just drop the unnecessary overhead.
Constants and variables
Only initialize variables once if you never intend to change it. Better yet if a variable reference never needs to be changed make it a constant const myVar = 10; if you try and change it in any way it will throw an error myVar = 10; ERROR!!!!!
Animation where performance is everything
When animating it is important to keep performance at maximum so you can invest more into the quality of the animation. When setting the transform it is far easier to use ctx.setTransform(scale,0,0,scale,x,y); ctx.rotate(angle); as you do not need to save and restore the full canvas state just for transforms. If you wish to get the default transform use ctx.setTransform(1,0,0,1,0,0);
Don't support the dead
No need for the moz,webkit prefixed requestAnimationFrame is everywhere. and as far as I know if a browser does not support requestAnimationFrame it does not support canvas, and we should not support as browser that does. Also requestAnimationFrame is not just for canvas, it should be used for any DOM javascript based animations you create.

The problem is that a new instance of the image is being reloaded each time they are rendered in the circle. The network delay causes this flashing that you see. By loading the image first and then reusing the data I have removed the flashing.
Here is a working version:
http://codepen.io/anon/pen/JRVJRJ
I have moved the loading of base_image out side of the make_base function and made it a global variable for reuse in the make_base function. The causes the image to be loaded once and reapplied multiple times.
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
}
});

Related

Clear canvas on window resize

I am trying to reset my progress ring, which is drawn with canvas on resize.
Currently, when I resize my window the function is run again as expected, but instead of the canvas being reset another progress ring is being drawn within the same canvas, please see screenshot below:
I have found clearRect() found in an answer here: How to clear the canvas for redrawing and similar solutions but it doesn't seem to resolve my issue.
Please find my codepen with the code: https://codepen.io/MayhemBliz/pen/OJQyLbN
Javascript:
// Progress ring
function progressRing() {
const percentageRings = document.querySelectorAll('.percentage-ring');
for (const percentageRing of Array.from(percentageRings)) {
console.log(percentageRing.offsetWidth)
var percentageRingOptions = {
percent: percentageRing.dataset.percent,
size: percentageRing.offsetWidth,
lineWidth: 30,
rotate: 0
}
var canvas = document.querySelector('.percentage-ring canvas');
var span = document.querySelector('.percentage-ring span');
span.textContent = percentageRingOptions.percent + '%';
if (typeof (G_vmlCanvasManager) !== 'undefined') {
G_vmlCanvasManager.initElement(canvas);
}
var ctx = canvas.getContext('2d');
//clear canvas for resize
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
// end clear canvas
canvas.width = canvas.height = percentageRingOptions.size;
percentageRing.appendChild(span);
percentageRing.appendChild(canvas);
ctx.translate(percentageRingOptions.size / 2, percentageRingOptions.size / 2); // change center
ctx.rotate((-1 / 2 + percentageRingOptions.rotate / 180) * Math.PI); // rotate -90 deg
//imd = ctx.getImageData(0, 0, 240, 240);
var radius = (percentageRingOptions.size - percentageRingOptions.lineWidth) / 2;
var drawCircle = function (color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
//ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth
ctx.stroke();
};
drawCircle('#efefef', percentageRingOptions.lineWidth, 100 / 100);
var i = 0; var int = setInterval(function () {
i++;
drawCircle('#555555', percentageRingOptions.lineWidth, i / 100);
span.textContent = i + "%";
if (i >= percentageRingOptions.percent) {
clearInterval(int);
}
}, 50);
}
}
window.addEventListener('load', progressRing);
window.addEventListener('resize', progressRing);
HTML:
<div class="percentage-ring" data-percent="88">
<span>88%</span>
<canvas></canvas>
</div>
Your help would be greatly appreciated.
Thanks in advance
1. Clear size problem
var canvas = document.querySelector('.percentage-ring canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
You have to specify canvas size with (canvas.width, canvas.height) instead of ctx.canvas.width, ctx.canvas.height
ctx.clearRect(0, 0, canvas.width, canvas.height);
2. Interval timer usage problem
If progressRing() is called again after setInterval() without clearInterval(), e.g., due to resizing, it will continue to run with the previous interval execution surviving. This will cause the ring to be drawn twice, thrice and more.
Place var int = 0; outside functions. This initializes the int to 0 first.
And modify var int = setInterval( to int = setInterval(
Then place the following code at the beginning of progressRing()
if (int) {
clearInterval(int);
int = 0;
}
And also place int = 0; immediately after the clearInterval() call that was already there.

Make a shape move up on a canvas

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !
You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines
Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

Making something travel in a circle in Javascript using canvas

This is my first time using canvasses, and my first time using Javascript to make a full on game-type thing, so forgive the beginner confusion!
I am trying to make a sort of circular Flappy Bird type thing where you travel around a circle, are drawn into the circle in a sort of gravity like way, and clicking makes you rise up. Go too high and you die, go too low and you die. However, I am falling down at the first hurdles...
The two main problems I have right now, starting with the smallest, is with the .clearRect function. I currently have it vaguely clearing behind the ball as it moves, but this is a little imperfect and cuts into the middle circle if it gets too close. If I set the whole window to clear on each frame, everything flashes horribly; is there a better way of doing this?
The second problem is just making the ball go in a circle... I have tried a lot of solutions, and I know that this is very likely to be a huge failing of my maths skills, but I can't make it orbit the main circle at all. I can make it move, and I have made it bounce off of the walls so that I can at least see what it's doing, but it just isn't circling. I imagine I'll be asking about the other aspects of this game at some point in the future if I have the same difficulty, but, as I said, I'm trying to learn!
In the dx and dy variable, 'sun' represents the middle point of the circle that I am trying to get the planet to orbit around. The rest of the variable is based on one answer I found elsewhere but I haven't managed to get it to work either.
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
sun = 300,
sunRadius = sun / 2,
x = sun +110,
y = sun -110,
angle = 0,
distance = 10,
dx = sun + sunRadius * Math.cos(angle*Math.PI/180),
dy = sun + sunRadius * Math.sin(angle*Math.PI/180),
planetRadius = 10;
document.body.appendChild(canvas);
canvas.width = canvas.height = sun * 2;
function makeSun() {
context.beginPath();
context.strokeStyle = "yellow";
context.arc(sun, sun, 60, 0, 2 * Math.PI);
context.fillStyle = "yellow";
context.fill();
context.closePath();
}
function makePlanet() {
context.beginPath();
context.arc(x, y, planetRadius, 0, Math.PI * 2);
context.fillStyle = "green";
context.fill();
context.strokeStyle = "green";
context.stroke();
}
function draw() {
context.clearRect(x + -20, y - 15, 40, 40);
makePlanet();
if (x + dx > canvas.width - planetRadius || x + dx < planetRadius) {
dx = -dx;
}
if (y + dy > canvas.height - planetRadius || y + dy < planetRadius) {
dy = -dy;
}
x += dx;
y += dy;
}
setInterval(makeSun, 10);
setInterval(draw, 10);
Ok, so
first :
the screen is flashing because you are setting two different intervals for makesun and draw. javascript makes zero promises that it won't update the screen between the time it cals makesun and draw. the best solution I think is to effectively clear the whole rect then redraw your sun ( the graphics are simple enough so you won't have performance issues).
second :
to animate the ball, you want to get the current time and decide how fast (in rotations / sec) you want it to go. As others suggested, requestAnimationFrame is better than setInterval, as it passes you a timestamp.
example
I've adapted your code to make the planet spin. I also added movement to the sun so you see the planet is actually relative to it. Have a look and please ask if there's something you don't understand.
const canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
checkbox = document.getElementById("moveSunBx"),
sun = 300,
sunRadius = sun / 2,
controllables = {
movesun : false,
rotationspeed : 1/2 // half a turn every second (timestamps are in milliseconds)
},
distancePtoS = 110,//distance from planet to sun
planetRadius = 10;
var planetAngle = 0.0,// Starting angles
sunAngle = 0.0,
lastTimestamp;
var gui = new dat.GUI();
gui.add(controllables, 'movesun').name("move sun");
gui.add(controllables, 'rotationspeed', -6, 6).name("planet speed");// in turns per second
function makeSun(x, y) {
context.beginPath();
context.strokeStyle = "yellow";
context.arc(x, y, 60, 0, 2 * Math.PI);
context.fillStyle = "yellow";
context.fill();
context.closePath();
}
function makePlanet(x, y) {
context.beginPath();
context.arc(x, y, planetRadius, 0, Math.PI * 2);
context.fillStyle = "green";
context.fill();
context.strokeStyle = "green";
context.stroke();
}
function draw(timestamp) {
requestAnimationFrame(draw);//immediately ask for next frame
if(!lastTimestamp){
lastTimestamp = timestamp;
return;
}
var speed = Math.PI * 2.0 * controllables.rotationspeed / 1000, // convert speed from turns per second to radian per millisec
timestep = timestamp - lastTimestamp;
lastTimestamp = timestamp;//we save the stamp
planetAngle += timestep * speed;//we update the angle depending on the currentspeed and the timestep
//angle = angle % Math.PI / 2;// this is for better comprehension, Math.cos and sin do the clamping for us
debugger;
//let's make the sun move!!!
if(controllables.movesun){
sunAngle += timestep * speed;
}
var sunx = sunRadius + Math.cos(sunAngle /2) * distancePtoS;// <- sin of 2 angle gives a beautiful infinity
var suny = sunRadius + Math.sin(sunAngle) * distancePtoS / 2;
//x and y don't need to be kept, I made them local to draw()
var planetx = sunx + Math.cos(planetAngle) * distancePtoS;
var planety = suny + Math.sin(planetAngle) * distancePtoS;
context.clearRect(0, 0, canvas.width, canvas.height);
makeSun(sunx, suny);
makePlanet(planetx, planety);
}
document.body.appendChild(canvas);
canvas.width = canvas.height = sun * 2;
draw();//we start the cycle
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
Use requestAnimationFrame() instead of the set intervals and you shouldn't have the flashing afterwards and the equation for a semicircle is y = ±√(r^2-x^2)
Use a variable to determine if it should be positive or negative and change the x variable of the orbiting object each draw to move it in a circle

Canvas disappear with soft keyboard on android

i've made a little script where a canvas is drawn dynamically according to user input(type:number), it needs work but the issue is that on android devices, when the user hide the soft keyboard, the canvas is erased. is it a way to prevent that ? Thx for any help
here is the code
var form = document.querySelector("form"),
canvas = document.getElementById("canvas"),
positions = [];
if (!canvas) {
alert("Impossible de récupérer le canvas");
}
window.addEventListener("resize", function () {
if (window.innerWidth < 500) {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerWidth * 0.8;
} else {
canvas.width = 600;
canvas.height = 600;
}
});
var context = canvas.getContext("2d");
if (!context) {
alert("Impossible de récupérer le contexte");
}
form.addEventListener("input", function () {
var table = form.table.value,
modulo = form.modulo.value,
centerX = canvas.width / 2,
centerY = canvas.height / 2,
rayon = (canvas.width - 5) / 2;
context.lineWidth = 2;
context.strokeStyle = "#21A8A3";
context.clearRect(0, 0, canvas.width, canvas.width);
for (var i = modulo; i >= 0; i -= 1) {
var angle = (2 * Math.PI / modulo) * i - (Math.PI / 2);
positions[i] = {
x: centerX + rayon * Math.cos(angle),
y: centerY + rayon * Math.sin(angle)
};
}
context.beginPath();
context.arc(centerX, centerY, rayon, 0, Math.PI * 2);
var j = positions.length - 1;
for (j; j >= 0; j -= 1) {
var next = j * table;
context.moveTo(positions[j].x, positions[j].y);
context.lineTo(positions[next % modulo].x, positions[next % modulo].y);
}
context.stroke();
});
and the url :http://multiplier.hyperion-web.com/
I didn't tried on an Android device, but your problem is more likely due to your bad resize event listener.
This handler do resize your canvas at each resize event, but it doesn't call again your drawing functions, which leaves you with an empty canvas, since modifying the width and height of a canvas resets its context (it does clear it, but also resets any properties like fillStyle, transformation matrix, clipping area etc).
Calling and hiding the soft keyboard will fire a resize event.
So the solution for you is to fix your event handler so that it does redraw everything after it has set the new dimensions of your canvas.
For this you'll have to store your drawing function in a separate variable, then attach it to your input event, and make a call to it in your resize event listener.
Ps : even setting the width or height attributes to the same value they were will reset the whole context

How to rotate image in Canvas

I have built a canvas project with https://github.com/petalvlad/angular-canvas-ext
<canvas width="640" height="480" ng-show="activeateCanvas" ap-canvas src="src" image="image" zoomable="true" frame="frame" scale="scale" offset="offset"></canvas>
I am successfully able to zoom and pan the image using following code
scope.zoomIn = function() {
scope.scale *= 1.2;
}
scope.zoomOut = function() {
scope.scale /= 1.2;
}
Additionally I want to rotate the image. any help i can get with which library i can use and how can i do it inside angularjs.
You can rotate an image using context.rotate function in JavaScript.
Here is an example of how to do this:
var canvas = null;
var ctx = null;
var angleInDegrees = 0;
var image;
var timerid;
function imageLoaded() {
image = document.createElement("img");
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
image.onload = function() {
ctx.drawImage(image, canvas.width / 2 - image.width / 2, canvas.height / 2 - image.height / 2);
};
image.src = "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcT7vR66BWT_HdVJpwxGJoGBJl5HYfiSKDrsYrzw7kqf2yP6sNyJtHdaAQ";
}
function drawRotated(degrees) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(degrees * Math.PI / 180);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.restore();
}
<button onclick="imageLoaded();">Load Image</button>
<div>
<canvas id="canvas" width=360 height=360></canvas><br>
<button onclick="javascript:clearInterval(timerid);
timerid = setInterval(function() {
angleInDegrees += 2;
drawRotated(angleInDegrees);
}, 100);">
Rotate Left
</button>
<button onclick="javascript:clearInterval(timerid);
timerid = setInterval(function() {
angleInDegrees -= 2;
drawRotated(angleInDegrees);
}, 100);">
Rotate Right
</button>
</div>
With curtesy to this page!
Once you can get your hands on the canvas context:
// save the context's co-ordinate system before
// we screw with it
context.save();
// move the origin to 50, 35 (for example)
context.translate(50, 35);
// now move across and down half the
// width and height of the image (which is 128 x 128)
context.translate(64, 64);
// rotate around this point
context.rotate(0.5);
// then draw the image back and up
context.drawImage(logoImage, -64, -64);
// and restore the co-ordinate system to its default
// top left origin with no rotation
context.restore();
To do it in a single state change. The ctx transformation matrix has 6 parts. ctx.setTransform(a,b,c,d,e,f); (a,b) represent the x,y direction and scale the top of the image will be drawn along. (c,d) represent the x,y direction and scale the side of the image will be drawn along. (e,f) represent the x,y location the image will be draw.
The default matrix (identity matrix) is ctx.setTransform(1,0,0,1,0,0) draw the top in the direction (1,0) draw the side in the direction (0,1) and draw everything at x = 0, y = 0.
Reducing state changes improves the rendering speed. When its just a few images that are draw then it does not matter that much, but if you want to draw 1000+ images at 60 frames a second for a game you need to minimise state changes. You should also avoid using save and restore if you can.
The function draws an image rotated and scaled around its center point that will be at x,y. Scale less than 1 makes the images smaller, greater than one makes it bigger. ang is in radians with 0 having no rotation, Math.PI is 180deg and Math.PI*0.5 Math.PI*1.5 are 90 and 270deg respectively.
function drawImage(ctx, img, x, y, scale, ang){
var vx = Math.cos(ang) * scale; // create the vector along the image top
var vy = Math.sin(ang) * scale; //
// this provides us with a,b,c,d parts of the transform
// a = vx, b = vy, c = -vy, and d = vx.
// The vector (c,d) is perpendicular (90deg) to (a,b)
// now work out e and f
var imH = -(img.Height / 2); // get half the image height and width
var imW = -(img.Width / 2);
x += imW * vx + imH * -vy; // add the rotated offset by mutliplying
y += imW * vy + imH * vx; // width by the top vector (vx,vy) and height by
// the side vector (-vy,vx)
// set the transform
ctx.setTransform(vx, vy, -vy, vx, x, y);
// draw the image.
ctx.drawImage(img, 0, 0);
// if needed to restore the ctx state to default but should only
// do this if you don't repeatably call this function.
ctx.setTransform(1, 0, 0, 1, 0, 0); // restores the ctx state back to default
}

Categories

Resources