Using canvas to display a picture pixel by pixel within secs - javascript

For learning purpose, I am trying to display an image pixel by pixel in a canvas within a few seconds, below is the code I write
var timeStamps = [];
var intervals = [];
var c = document.getElementById('wsk');
var ctx = c.getContext("2d"),
img = new Image(),
i;
img.onload = init;
img.src = "http://placehold.it/100x100/000000";
var points = [];
function init(){
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
for (i=0; i<img.width*img.height; i++) {
points.push(i);
}
window.m = points.length;
var sec = 10; //animation duration
function animate(t) {
timeStamps.push(t);
var pointsPerFrame = Math.floor(img.width*img.height/sec/60)+1;
var start = Date.now();
for (j=0; j<pointsPerFrame; j++) {
var i = Math.floor(Math.random()*m--); //Pick a point
temp = points[i];
points[i] = points[m];
points[m] = temp; //swap the point with the last element of the points array
var point = new Point(i%img.width,Math.floor(i/img.width)); //get(x,y)
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.globalCompositeOperation = "source-over";
ctx.fillRect(point.x,point.y,1,1); //DRAW DOZENS OF POINTS WITHIN ONE FRAME
}
ctx.globalCompositeOperation = "source-in";//Only display the overlapping part of the new content and old cont
ctx.drawImage(img,0,0); //image could be with transparent areas itself, so only draw the image on those points that are already on screen, exluding points that don't overlap with the image.
var time = Date.now()-start;
intervals.push(time);
if( m > 0 ) requestAnimationFrame(animate);
}
animate();
}
function Point(x,y) {
this.x = x;
this.y = y;
}
Live test: www.weiwei-tv.com/test.php.
I was expecting the dots would appear total randomly and eventually fill out the whole 100*100 canvas. What real happens is every time only the upper half of the picture gets displayed but many dots in the lower half are missed. I guess the problem is with the technique I use to randomly pick up dots, I get it from this page, but I can't find anything wrong in it.
Another thing I notice is that the intervals are mostly 1ms or 0ms, which means javascript takes very little time draw the 100*100/10/60 dots and draw image upon it within every frame. However, the differences between timeStamps are mostly 30~50ms, which should be about 16ms(1000/60). I am not sure if this also plays a part in the failure of my code.

The problem is that you are using the index of the points array to compute the point coordinates. You need to use the value of the chosen point (which is moved to the m-th position).
So, change
var point = new Point(i%img.width,Math.floor(i/img.width));
To
var point = new Point(points[m]%img.width,Math.floor(points[m]/img.width));

Related

Using Canvas to position different sized squares

I'm trying to create a background that is inspired by this art work http://www.artinthepicture.com/paintings/Paul_Klee/Flora-on-the-Sand/
This is my code so far https://github.com/jessicacgu/jessicacgu.github.io/blob/faces/index.html
I'm using javascript instead of CSS because I wanted each square to have a facial expression that blinks (changes between happy and excited) (if there's something cooler to do, please let me know!)
Right now I have all the squares as the same size and equally spaced between each other. I want it to be more like the art piece by having different shaped squares.
My problem is that if I draw a square at 0,0 to be 15x15, how do I figure out that the other squares cannot overlap the first square? Is this possible?
If not, I can get rid of the expressions and just use CSS (I mainly want to imitiate that artwork :( ).
^Also, what's the right way to showcase large pieces of code on stackoverflow? Should I copy and paste or post link to github or put in codepen?
EDIT: The code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="application/javascript">
// Draws faces onto all of the canvas. Puts 10px distance between each
// face
function draw_faces() {
var canvas = document.getElementById("faces_bkgd");
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
// Setting a background color
ctx.fillStyle = "rgb(244,191,175)";
ctx.fillRect(0,0,canvas.width,canvas.height);
// Creating the shape of the face
var shape = new Path2D();
shape.rect(0,0,30,30);
// Making the Happy Expression
var happy_expr = new Path2D();
// Making the left eye (as a carrot shape)
happy_expr.moveTo(4,13);
happy_expr.lineTo(8,5);
happy_expr.lineTo(12,13);
// Making the right eye (as a carrot shape)
happy_expr.moveTo(18,13);
happy_expr.lineTo(22,5);
happy_expr.lineTo(26,13);
// Making the mouth (a thin line)
happy_expr.moveTo(8,22);
happy_expr.lineTo(22,22);
// Making the Excited Expression
var excited_expr = new Path2D();
// Making the left eye (90 deg clockwise carrot)
excited_expr.moveTo(3,5);
excited_expr.lineTo(11,9);
excited_expr.lineTo(3,13);
// Making the right eye (90 deg counter clockwise carrot)
excited_expr.moveTo(26,5);
excited_expr.lineTo(18, 9);
excited_expr.lineTo(26, 13);
// Making the mouth (a W shape)
excited_expr.moveTo(7,17);
excited_expr.lineTo(11,25);
excited_expr.lineTo(15,17);
excited_expr.lineTo(19,25);
excited_expr.lineTo(23,17);
// Colors for the faces. Along with the background creates a palette
// that was taken from
// http://www.colourlovers.com/palette/1959912/L_i_n_d_a
var colors = ["rgb(211,210,164)", "rgb(198,223,194)",
"rgb(217,194,152)"];
// Expressions for the faces
var exprs = [happy_expr, excited_expr];
// Keeps track of the colors for each face drawn onto the canvas
var faces_color = [];
// Keeps track of the expressions for each face drawn onto the
// canvas
var faces_expr = [];
// Scaling the faces by 2x. Adjusting for new number of faces on
// canvas
ctx.scale(2, 2);
var num_width = canvas.width/70;
var num_height = canvas.height/70;
// Does actual drawing of face onto the canvas
function draw_face(x, y, color, shape, expr) {
ctx.save();
ctx.translate(x, y);
// Drawing the shape of the face
ctx.fillStyle = color
ctx.fill(shape);
// Giving the face a border
ctx.lineWidth = 3;
ctx.strokeStyle = "rgb(176,144,132)";
ctx.strokeRect(0,0,30,30);
// Drawing the expression
ctx.lineWidth = 1;
ctx.stroke(expr);
ctx.restore();
}
// Setting up the colors and initial expressions for each face
for (var i = 0; i < num_width ; i += 1) {
for (var j = 0; j < num_height ; j += 1) {
// Math.floor(Math.random()*100) chooses a random number from an
// even distribution of the integers[0,100)
var c = Math.floor(Math.random()*100)%colors.length;
var e = Math.floor(Math.random()*100)%exprs.length;
// Draw the initial face on the canvas
draw_face(i*40,j*40, colors[c], shape, exprs[e]);
// Keeps track of each faces's color and expression
faces_color.push(c);
faces_expr.push(e);
}
}
// Changes the expressions of a few random faces at a time
function change_facial_expr() {
var face_num = 0;
// Chooses a random face to change
var blink = Math.floor(Math.random() * faces_color.length);
for (var i = 0; i < num_width; i += 1) {
for (var j = 0; j < num_height ; j += 1) {
// Determines if the face is to be changed
if (face_num == blink) {
faces_expr[face_num] = (faces_expr[face_num]+1)%exprs.length
}
draw_face(i*40, j*40, colors[faces_color[face_num]], shape,
exprs[faces_expr[face_num]]);
face_num += 1;
}
}
}
window.setInterval(change_facial_expr, 1000);
}
}
</script>
</head>
<body onload="draw_faces();">
<canvas id="faces_bkgd"></canvas>
</body>
</html>

Html5 canvas pattern drawing delay

i draw a canvas(aka canvas 1) with image() then rotate it 25 degree. then i take rotated canvas to make a pattern for another canvas(aka canvas 2). then i draw this . and fill the fillstyle with newly created pattern. i noticed if alert in the middle of below code
finalsleeve_ctx.globalCompositeOperation = "source-atop";
/*****************************************
alert(sleeve.toDataURL('image/png'));
*****************************************/
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
then firefox gives a correct output but if i dont do alert it does not give me correct output. crome not showing me correct output.
do i need to delay ?
here is what i have tried.
HTML
<div >
<canvas id="sleeve" width=436 height=567></canvas>
<canvas id="finalsleeve" width=436 height=567 ></canvas>
</div>
JS
var sleeve = document.getElementById('sleeve');
var sleeve_ctx = sleeve.getContext('2d');
var finalsleeve = document.getElementById('finalsleeve');
var finalsleeve_ctx = finalsleeve.getContext('2d');
function rotator2(var2,var3)
{
sleeve.width=sleeve.width;
var imageObj_rotator2 = new Image();
imageObj_rotator2.onload = function ()
{
var pattern_rotator2 = sleeve_ctx.createPattern(imageObj_rotator2, "repeat");
sleeve_ctx.fillStyle = pattern_rotator2;
sleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
sleeve_ctx.rotate(var3 * Math.PI/180);
sleeve_ctx.fill();
}
imageObj_rotator2.src = var2;
}
function drawSleeve()
{
finalsleeve.width = finalsleeve.width;
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
finalsleeve_ctx.drawImage(imgsleeve,0,0);
finalsleeve_ctx.globalCompositeOperation = "source-atop";
alert(sleeve.toDataURL('image/png'));
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
finalsleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
finalsleeve_ctx.fillStyle = pattern;
finalsleeve_ctx.fill();
finalsleeve_ctx.globalAlpha = .10;
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
}
rotator2('http://i.stack.imgur.com/fvpMN.png','25');
drawSleeve();
Here is fiddle.
http://jsfiddle.net/EbBHz/
EDITED
Sorry, I completely misunderstood your question. I just now went back and saw the last question you posted and the goal you are trying to achieve.
To get the functionality you desire you can just create one function, you don't need two. Instead of using a second canvas in the HTML I created a temporary one using javascript.
Here is the simplified and functional code
<canvas id="sleeve" width='436' height='567'></canvas>
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = 500;
pattern.height = 500;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created
pctx.fillRect(0, 0, pattern.width, pattern.height);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(0, 0, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '45')
The technique works alright, but only for certain angles. Here is the demo set to 45 degrees. As you can see, there is a problem: part of the sleeve is whited out. However, if you change the degree to 15 like this it works just fine. This is because when the image is being rotated in the created canvas it leaves white space before repeating. To see this issue first hand, change the width and the height of the created canvas to 30 (the default width/height of the image) like this
Note: You may have to click run once the jsfiddle tab is open, canvases don't like generating content when another tab is focused
I tried problem solving the issue including
Making the generated canvas really large (which works but KILLS load
time/crashes page sometimes)
Translating the picture in the generated canvas after rotating it
which didn't work like I had hoped
Coming up with a function to change the width/height to cover the
entire first canvas based on the rotated second-canvas-dimensions, which is by far the most promising, but I don't have the time or desire to work out a good solution
All that being said if the angle HAS to be dynamic you can work on a function for it. Otherwise just use a workaround angle/generated canvas dimensions
final result> Here is a working solution for fill rotated pattern without white at any angle
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
var x =0;
var y=0;
//pattern size should be grater than height and width of object so that white space does't appear.
var patternSize = sleeve.width+ sleeve.height;
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = sleeve.width;
pattern.height = sleeve.height;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
//moving rotation point to center of target object.
pctx.translate(x+ sleeve.width/2, y+sleeve.height/2);
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created and expanding size from center of rotated angle
pctx.fillRect(-patternSize/2, -patternSize/2, patternSize, patternSize);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "no-repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(x, y, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '50')

HTML5 canvas multiply effect - jagged edges

Our company website features a "random shard generator", built in Flash, which creates a number of overlapping coloured shard graphics at random just below the site header.
http://www.clarendonmarketing.com
I am trying to replicate this effect using HTML5, and whilst I can generate the random shards easily enough, the blended overlapping (multiply in Adobe terms) is proving a challenge.
I have a solution which basically creates an array of all the canvas's pixel data before each shard is drawn, then another array with the canvas's pixel data after each shard is drawn. It then compares the two and where it finds a non transparent pixel in the first array whose corresponding pixel in the second array matches the currently selected fill colour, it redraws it with a new colour value determined by a 'multiply' function (topValue * bottomValue / 255).
Generally this works fine and achieves the desired effect, EXCEPT around the edges of the overlapping shards, where a jagged effect is produced.
I believe this has something to do with the browser's anti-aliasing. I have tried replicating the original pixel's alpha channel value for the computed pixel, but that doesn't seem to help.
Javascript:
// Random Shard Generator v2 (HTML5)
var theCanvas;
var ctx;
var maxShards = 6;
var minShards = 3;
var fillArray = new Array(
[180,181,171,255],
[162,202,28,255],
[192,15,44,255],
[222,23,112,255],
[63,185,127,255],
[152,103,158,255],
[251,216,45,255],
[249,147,0,255],
[0,151,204,255]
);
var selectedFill;
window.onload = function() {
theCanvas = document.getElementById('shards');
ctx = theCanvas.getContext('2d');
//ctx.translate(-0.5, -0.5)
var totalShards = getRandom(maxShards, minShards);
for(i=0; i<=totalShards; i++) {
//get snapshot of current canvas
imgData = ctx.getImageData(0,0,theCanvas.width,theCanvas.height);
currentPix = imgData.data
//draw a shard
drawRandomShard();
//get snapshot of new canvas
imgData = ctx.getImageData(0,0,theCanvas.width,theCanvas.height);
pix = imgData.data;
//console.log(selectedFill[0]+','+selectedFill[1]+','+selectedFill[2]);
//alert('break')
//CALCULATE THE MULTIPLIED RGB VALUES FOR OVERLAPPING PIXELS
for (var j = 0, n = currentPix.length; j < n; j += 4) {
if (
//the current pixel is not blank (alpha 0)
(currentPix[j+3]>0)
&& //and the new pixel matches the currently selected fill colour
(pix[j]==selectedFill[0] && pix[j+1]==selectedFill[1] && pix[j+2]==selectedFill[2])
) { //multiply the current pixel by the selected fill colour
//console.log('old: '+currentPix[j]+','+currentPix[j+1]+','+currentPix[j+2]+','+currentPix[j+3]+'\n'+'new: '+pix[j]+','+pix[j+1]+','+pix[j+2]+','+pix[j+3]);
pix[j] = multiply(selectedFill[0], currentPix[j]); // red
pix[j+1] = multiply(selectedFill[1], currentPix[j+1]); // green
pix[j+2] = multiply(selectedFill[2], currentPix[j+2]); // blue
}
}
//update the canvas
ctx.putImageData(imgData, 0, 0);
}
};
function drawRandomShard() {
var maxShardWidth = 200;
var minShardWidth = 30;
var maxShardHeight = 16;
var minShardHeight = 10;
var minIndent = 4;
var maxRight = theCanvas.width-maxShardWidth;
//generate a random start point
var randomLeftAnchor = getRandom(maxRight, 0);
//generate a random right anchor point
var randomRightAnchor = getRandom((randomLeftAnchor+maxShardWidth),(randomLeftAnchor+minShardWidth));
//generate a random number between the min and max limits for the lower point
var randomLowerAnchorX = getRandom((randomRightAnchor - minIndent),(randomLeftAnchor + minIndent));
//generate a random height for the shard
var randomLowerAnchorY = getRandom(maxShardHeight, minShardHeight);
//select a fill colour from an array
var fillSelector = getRandom(fillArray.length-1,0);
//console.log(fillSelector);
selectedFill = fillArray[fillSelector];
drawShard(randomLeftAnchor, randomLowerAnchorX, randomLowerAnchorY, randomRightAnchor, selectedFill);
}
function drawShard(leftAnchor, lowerAnchorX, lowerAnchorY, rightAnchor, selectedFill) {
ctx.beginPath();
ctx.moveTo(leftAnchor,0);
ctx.lineTo(lowerAnchorX,lowerAnchorY);
ctx.lineTo(rightAnchor,0);
ctx.closePath();
fillColour = 'rgb('+selectedFill[0]+','+selectedFill[1]+','+selectedFill[2]+')';
ctx.fillStyle=fillColour;
ctx.fill();
};
function getRandom(high, low) {
return Math.floor(Math.random() * (high-low)+1) + low;
}
function multiply(topValue, bottomValue){
return topValue * bottomValue / 255;
};
Working demo:
http://www.clarendonmarketing.com/html5shards.html
Do you really need multiplication? Why not just use lower opacity blending?
Demo http://jsfiddle.net/wk3eE/
ctx.globalAlpha = 0.6;
for(var i=totalShards;i--;) drawRandomShard();
Edit: If you really need multiplication, then leave it to the professionals, since multiply mode with alpha values is a little tricky:
Demo 2: http://jsfiddle.net/wk3eE/2/
<script type="text/javascript" src="context_blender.js"></script>
<script type="text/javascript">
var ctx = document.querySelector('canvas').getContext('2d');
// Create an off-screen canvas to draw shards to first
var off = ctx.canvas.cloneNode(true).getContext('2d');
var w = ctx.canvas.width, h = ctx.canvas.height;
for(var i=totalShards;i--;){
off.clearRect(0,0,w,h); // clear the offscreen context first
drawRandomShard(off); // modify to draw to the offscreen context
off.blendOnto(ctx,'multiply'); // multiply onto the main context
}
</script>

html5 canvas array of images- draw image to canvas

I'm using the HTML5 canvas and JavaScript to make a basic game, and I have an array of images for the numbers 1-10, and then have another array for the Welsh words for the numbers 1-10.
What I want to do is select a random element from the images array and a random element from the words array and display them both on the canvas. The user will then click on a tick to indicate if the word represents the correct number, or a cross if it doesn't.
The problem is that I'm not sure how to draw an array element to the canvas. I have the following code, which I was going to use just to test that it works, before I think about how to make the elements drawn be chosen at random:
function drawLevelOneElements(){
/*First, clear the canvas */
context.clearRect(0, 0, myGameCanvas.width, myGameCanvas.height);
/*This line clears all of the elements that were previously drawn on the canvas. */
/*Then redraw the game elements */
drawGameElements();
/*Now draw the elements needed for level 1 (08/05/2012) */
/*First, load the images 1-10 into an array */
var imageArray = new Array();
imageArray[0] = "1.png";
imageArray[1] = "2.png";
imageArray[2] = "3.png";
imageArray[3] = "4.png";
imageArray[4] = "5.png";
imageArray[5] = "6.png";
imageArray[6] = "7.png";
imageArray[7] = "8.png";
imageArray[8] = "9.png";
imageArray[9] = "10.png";
/*Then create an array of words for numbers 1-10 */
var wordsArray = new Array();
wordsArray[0] = "Un";
wordsArray[1] = "Dau";
wordsArray[2] = "Tri";
wordsArray[3] = "Pedwar";
wordsArray[4] = "Pump";
wordsArray[5] = "Chwech";
wordsArray[6] = "Saith";
wordsArray[7] = "Wyth";
wordsArray[8] = "Naw";
wordsArray[9] = "Deg";
/*Draw an image and a word to the canvas just to test that they're being drawn */
context.drawImage(imageArray[0], 100, 30);
context.strokeText(wordsArray[3], 500, 60);
}
but for some reason, when I view the page in the browser, in the firebug console, I get the error:
Could not convert JavaScript argument arg 0 [nsIDOMCanvasRenderingContext2D.drawImage]
context.drawImage(imageArray[0], 100, 30);
I'm not sure if this is how I'm meant to access the image in array element 0... could someone please point out what I'm doing wrong?
* EDIT *
I've changed the code below the to arrays to:
var image1 = new Image();
image1.src = imageArray[0];
/*Draw an image and a word to the canvas just to test that they're being drawn */
context.drawImage(image1, 100, 30);
context.strokeText(wordsArray[3], 500, 60);
but for some reason, the only the element from the wordsArray is drawn to the canvas- the image element from imageArray isn't displayed at all.
Any ideas?
You need to create a javascript image with it's src set to your array value
var img = new Image();
img.src = imageArray[0];
ctx.drawImage(img, 100, 30);
Without doing that you're trying to ask the canvas to draw a string of "1.png" for example which is not what you're after here!
This is the code for drawGameElements()
/* This function draws the game elements */
function drawGameElements(){
/* Draw a line for the 'score bar'. */
context.moveTo(0, 25);
context.lineTo(700, 25);
context.stroke();
/* Draw current level/ total levels on the left, and current score on the right. */
context.font = "11pt Calibri"; /* Text font & size */
context.strokeStyle = "black"; /* Font colour */
context.strokeText(currentLevel + "/" + totalLevels, 10, 15);
context.strokeText(currentScore, 650, 15);
}
Literally, all it's doing is drawing a 'score bar' on the canvas, which is just a line across the top, the current level/ total levels, and the user's current score. I don't think this is the issues, as the elements that this function is meant to display are being displayed correctly.
This is an old one but the reason why the image is not showing is probably because you have to call onLoad then set the src like so:
var img = new Image();
img.onLoad = function() {ctx.drawImage(img, 100, 30);};
img.src = imageArray[0];
I solved this using recursive calls on the method img.onload to draw images.
E.g.:
var cx = 10;//x initial position to draw
var cy = 10;//y initial position to draw
var space = 300; //distance between images to draw
var imageArray = new Array();
imageArray[0] = "1.png";
imageArray[1] = "2.png";
imageArray[2] = "3.png";
//etc....
//build a Image Object array
var imgs = new Array();
for(i = 0; i < imageArray.length; i++){
imgs[i] = new Image();
imgs[i].src = imageArray[i];//attention if the images are in a folder
}
var ri = 1;//index of images on the array
imgs[0].onload = function(){
context.drawImage(imgs[0], cx, cy);
cy += imgs[0].height + space;
callDraw(context, imgs, cx, cy, ri, space);
}
The recursive function is defined as following:
function callDraw(context, imgs, cx, cy, ri, space){
if(ri == imgs.length)
return;
context.drawImage(imgs[ri], cx, cy);
cy += imgs[ri].height + space;
ri++;
callDraw(context, imgs, cx, cy, ri, space);
}

Animating sprite frames in canvas

I'm making a small platform game with the canvas element and I'm having trouble working out the best way to use sprites.
Canvas doesn't support animated GIFs, so I can't do that, so instead I made a film strip for my animated sprites to give the illusion that a character is moving. Like this: http://i.imgur.com/wDX5R.png
Here's the relevant code:
function player() {
this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frameCount = 12;
this.runningSprite = new Image();
this.runningSprite.src = "/game/images/runningSprite.png";
this.runningSprite.frameWidth = 34;
this.update = function() {
}
var frameCount = 1;
this.draw = function() {
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
if(frameCount < this.idleSprite.frameCount - 1) { frameCount++; } else { frameCount = 0; }
}
}
var player = new player();
As you can see, I'm defining the idle sprite and also its frame width and frame count. Then in my draw method, I'm using those properties to tell the drawImage method what frame to draw. Everything works fine, but I'm unhappy with the frameCount variable defined before the draw method. It seems... hacky and ugly. Would there be a way that people know of to achieve the same effect without keeping track of the frames outside the draw method? Or even a better alternative to drawing animated sprites to canvas would be good.
Thanks.
You could select the frame depending on some fraction of the current time, e.g.
this.draw = function() {
var fc = this.idleSprite.frameCount;
var currentFrame = 0 | (((new Date()).getTime()) * (fc/1000)) % fc;
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * currentFrame, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
}
That will give you animation with a period of one second (the 1000 is a millisecond value). Depending on your frame rate and animation this might look jerky, but it doesn't rely on persistent counters.

Categories

Resources