HTML5 canvas break up image - javascript

My overall aim is to make a sliding puzzle piece game. Using the canvas, I have managed to split up the image into multiple pieces. In order to shuffle the pieces, I wrote the co-ordinates of the pieces into an array, shuffled the co-ordinates, and re-drew the image on the canvas. However, the puzzle ends up with some pieces being duplicated! I have no idea why?!
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img=document.getElementById("map");
//img,sx,sy,swidth,sheight,x,y,width,height
function recreate_map(){
ctx.drawImage(img,0,0,133,100,0,0,133,100);
ctx.drawImage(img,133,0,133,100,133,0,133,100);
ctx.drawImage(img,266,0,133,100,266,0,133,100);
ctx.drawImage(img,0,100,133,100,0,100,133,100);
ctx.drawImage(img,133,100,133,100,133,100,133,100);
ctx.drawImage(img,266,100,133,100,266,100,133,100);
ctx.drawImage(img,0,200,133,100,0,200,133,100);
ctx.drawImage(img,133,200,133,100,133,200,133,100);
ctx.drawImage(img,266,200,133,100,266,200,133,100);
}
function shuffle_array(arr){
var i = arr.length;
while(--i>0){
var n = Math.floor(Math.random()*(i));
var temp = arr[n];
arr[n] = arr[i];
arr[i] = temp;
}
return arr;
}
function shuffle_tiles(){
var positions_x = [0,133,266,0,133,266,0,133,266];
var positions_y = [0,0,0,100,100,100,200,200,200];
shuffle_array(positions_x);
shuffle_array(positions_y);
ctx.drawImage(img,0,0,133,100,positions_x[0],positions_y[0],133,100);
ctx.drawImage(img,133,0,133,100,positions_x[1],positions_y[1],133,100);
ctx.drawImage(img,266,0,133,100,positions_x[2],positions_y[2],133,100);
ctx.drawImage(img,0,100,133,100,positions_x[3],positions_y[3],133,100);
ctx.drawImage(img,133,100,133,100,positions_x[4],positions_y[4],133,100);
ctx.drawImage(img,266,100,133,100,positions_x[5],positions_y[5],133,100);
ctx.drawImage(img,0,200,133,100,positions_x[6],positions_y[6],133,100);
ctx.drawImage(img,133,200,133,100,positions_x[7],positions_y[7],133,100);
ctx.drawImage(img,266,200,133,100,positions_x[8],positions_y[8],133,100);
}
If it helps, I'm using JS Bin, on Firefox. Thanks.

You need to clear the canvas for each redraw or else the previous content will remain.
Try this:
function recreate_map(){
/// call this first
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(img,0,0,133,100,0,0,133,100);
...

Related

JavaScript running pretty slow in specific computer

I writing a code that take a Black & White image and check the pixels in an specific area (with an square shape) and finally retur the sum of how many of them are balck, each pixel of the area is read in a For loop like the next example:
function is_box_black_corner(x,y,width,heigth){
var counter=0;
for (var i=x; i<(x+width); i++){
for (var j=y; j<(y+heigth); j++){
if(my_isblack(i,j)==1){
counter++;
}
}
}
And as you can see inside the for loop a I call a function that verifies if the specific pixel is fairly black:
function my_isblack(x,y){
var p = ctx.getImageData(x, y, 1, 1).data;
if(p[0]<50 && p[1]<50 && p[2]<50){
return 1;
}
else{
return 0;
}
}
As you can imagine, this is a little bit computational expensive. but the problem is that with my computer, suddenly it got much slower than others (even with worst processors). I already check the RAM memory and the processor and none of them were used more than 30%, and the processor before running the code is close to 0%.
And don’t know where else to look. I appreciate some help, also if somebody knows how to do this much faster it will be highly apreciated
I will try wiht one call to getImageData as suggested by #ASDFGerte:
var x=10; var y=10; var width=50; var height=50;
var counter=0;
var image;
var p; //global data
function init(){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(10, 10, 50, 50); //this is just a black square
image = ctx.getImageData(x, y, width, height); //Load the image
p = image.data; //get the data of the image
is_box_black_corner();
};
function is_box_black_corner(){
for (var i=x; i<(x+width); i++){
for (var j=y; j<(y+height); j++){
if(my_isblack(i,j)==1){
counter++;
}
}
}
console.log(counter);
};
function my_isblack(x,y){
if(p[0]<50 && p[1]<50 && p[2]<50){ //check the global data
return 1;
}
else{
return 0;
}
};

Increase performance for 10,000 particles in HTML5 Canvas

I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches.
The first fiddle: http://jsfiddle.net/6eypdhjp/
Uses fillRect with a 4 by 4 white square, providing roughly 60 frames per second # 10,000 snow flakes.
So I wondered if I could improve this and found a bit of information on HTML5Rocks' website regarding canvas performance. One such suggestion was to pre-render the snow flakes to canvases and then draw the canvases using drawImage.
The suggestion is here http://www.html5rocks.com/en/tutorials/canvas/performance/, namely under the title Pre-render to an off-screen canvas. Use Ctrl + f to find that section.
So I tried their suggestion with this fiddle: http://jsfiddle.net/r973sr7c/
How ever, I get about 3 frames per second # 10,000 snow flakes. Which is very odd given jsPerf even shows a performance boost here using the same method http://jsperf.com/render-vs-prerender
The code I used for pre-rendering is here:
//snowflake particles
var mp = 10000; //max particles
var particles = [];
for(var i = 0; i < mp; i++) {
var m_canvas = document.createElement('canvas');
m_canvas.width = 4;
m_canvas.height = 4;
var tmp = m_canvas.getContext("2d");
tmp.fillStyle = "rgba(255,255,255,0.8)";
tmp.fillRect(0,0,4,4);
particles.push({
x : Math.random()*canvas.width, //x-coordinate
y : Math.random()*canvas.height, //y-coordinate
r : Math.random()*4+1, //radius
d : Math.random()*mp, //density
img: m_canvas //tiny canvas
})
}
//Lets draw the flakes
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < particles.length; i++) {
var flake = particles[i];
ctx.drawImage(flake.img, flake.x,flake.y);
}
}
So I wondered why am I getting such horrendous frame rate? And is there any better way to get higher particle counts moving on screen whilst maintaining 60 frames per second?
Best frame rates are achieved by drawing pre-rendered images (or pre-rendered canvases).
You could refactor your code to:
Create about 2-3 offscreen (in-memory) canvases each with 1/3 of your particles drawn on them
Assign each canvas a fallrate and a driftrate.
In each animation frame, draw each offscreen canvas (with an offset according to its own fallrate & driftrate) onto the on-screen canvas.
The result should be about 60 frames-per-second.
This technique trades increased memory usage to achieve maximum frame rates.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var mp=10000;
var particles=[];
var panels=[];
var panelCount=2;
var pp=panelCount-.01;
var maxFallrate=2;
var minOffsetX=-parseInt(cw*.25);
var maxOffsetX=0;
// create all particles
for(var i=0;i<mp;i++){
particles.push({
x: Math.random()*cw*1.5, //x-coordinate
y: Math.random()*ch, //y-coordinate
r: 1, //radius
panel: parseInt(Math.random()*pp) // panel==0 thru panelCount
})
}
// create a canvas for each panel
var drift=.25;
for(var p=0;p<panelCount;p++){
var c=document.createElement('canvas');
c.width=cw*1.5;
c.height=ch*2;
var offX=(drift<0)?minOffsetX:maxOffsetX;
panels.push({
canvas:c,
ctx:c.getContext('2d'),
offsetX:offX,
offsetY:-ch,
fallrate:2+Math.random()*(maxFallrate-1),
driftrate:drift
});
// change to opposite drift direction for next panel
drift=-drift;
}
// pre-render all particles
// on the specified panel canvases
for(var i=0;i<particles.length;i++){
var p=particles[i];
var cctx=panels[p.panel].ctx;
cctx.fillStyle='white';
cctx.fillRect(p.x,p.y,1,1);
}
// duplicate the top half of each canvas
// onto the bottom half of the same canvas
for(var p=0;p<panelCount;p++){
panels[p].ctx.drawImage(panels[p].canvas,0,ch);
}
// begin animating
drawStartTime=performance.now();
requestAnimationFrame(animate);
function draw(time){
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<panels.length;i++){
var panel=panels[i];
ctx.drawImage(panel.canvas,panel.offsetX,panel.offsetY);
}
}
function animate(time){
for(var i=0;i<panels.length;i++){
var p=panels[i];
p.offsetX+=p.driftrate;
if(p.offsetX<minOffsetX || p.offsetX>maxOffsetX){
p.driftrate*=-1;
p.offsetX+=p.driftrate;
}
p.offsetY+=p.fallrate;
if(p.offsetY>=0){p.offsetY=-ch;}
draw(time);
}
requestAnimationFrame(animate);
}
body{ background-color:#6b92b9; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
I don't think you want to create a new canvas element every time. Doing so causes a huge performance drain.
When I moved this code out of the for loop, the performance instantly improved. I think doing so will allow you to optimize your code to achieve the intended behavior:
var m_canvas = document.createElement('canvas');
m_canvas.width = 4;
m_canvas.height = 4;
var tmp = m_canvas.getContext("2d");
tmp.fillStyle = "rgba(255,255,255,0.8)";
tmp.fillRect(0, 0, 4, 4);
Check out this revised JSFiddle.
Hope this helped!
You would pre-render elements you paint many times.
Say for example you have a landscape where you paint bushes (of same form) on various locations during a game scroll. The the use of memory canvas would be ok.
For your code you should try to divide your flakes into for example 10 sizes. Thus create 10 memory canvases. Then paint these into random possitions.
In other words you copy 10 canvases 1.000 times. Not 10.000 canvases 10.000 times.

Pixel manipulation and canvas

Is there a way in javascript to change the alpha channels of each pixel into being fully transparent (a=0) while coding for pixel manipulation (meaning that you can still change the transparency in some of the alpha channels as desired)?
Basically, what I'm doing is: given some data for a specific image, I manipulate the pixel array using an algorithm so that some pixels become fully transparent unless they satisfy some certain condition. In the case of them satisfying the condition I want them to be fully opaque, aka alpha=1. However, because of a complication with the way the algorithm works, I need to have my data "reset"; meaning I want the pixel array to start off as having every alpha = 0. I can provide code if that helps in better understanding the scope of my question.
Thanks so much.
EDIT: I'm looking more for a method/one-line code. Would context.globalAlpha = 0 serve the purposes? Is there any pitfall I should be careful about?
EDIT2: This is my code. Does globalAlpha where I've put it do what I'm expecting it to do? I'm not sure how to use it...
function getBoundary(imagedata){
var imageData = new Array(imagedata.data.length);
imageData = imagedata.data;
var w = imagedata.width;
var h = imagedata.height;
var color1 = [];
var colorRight = [];
var colorDown = [];
context.globalAlpha = 0;
for (var i = 0; i < 4*w*h; i +=4) {
color1 = [imageData[i],imageData[i+1],imageData[i+2]];
colorRight = [imageData[i+4],imageData[i+5],imageData[i+6]];
colorDown = [imageData[4*w+i],imageData[4*w+i+1],imageData[4*w+i+2]];
if(colorRight = [255,255,255]){ //if right is white
if(color1 = [0,0,0]){
imageData[i+3] = 255;
}
else{
if(colorDown = [0,0,0]){
imageData[4*w+i+3] = 255;
}
}
}
else{ //colorRight = black
if(color1 = [0,0,0]){
if(colorDown = [255,255,255]){
imageData[i+3] = 255;
}
}
else if(color1 = [255,255,255]){
imageData[i+7] = 255;
if(colorDown = [0,0,0]){
imageData[4*w+i+3] = 255;
}
else{
}
}
}
}
console.log("done");
imagedata.data = imageData;
return imagedata;
}
You can use getImageData and flip all the alpha elements to zero:
You can create a function that zeros the alpha of all pixels on the canvas like this:
function zeroAllAlpha(){
var imageData=context.getImageData(0,0,canvas.width,canvas.height);
var data=imageData.data;
// set all alpha elements to zero (fully transparent);
for(var i=3;i<data.length;i+=4){
data[i]=0;
}
context.putImageData(imagedata,0,0);
}
And you can call the function with one line like this:
zeroAllAlpha();

Javascript weird random behavior

I'm using JavaScript's Math.random() function to distribute items over buckets.
Afterwards, I display the buckets in a canvas. I would expect the items to be distributed evenly, but (even after multiple retries in multiple browsers), it seems like the distribution is much more fine grained on the left (closer to zero) and becomes more uniform towards the right (closer to 1). See the following image .
Am I doing it wrong, or does JavaScript's random function suck? Below is the code that was used to generate this image:
<html>
<head>
<script>
window.onload = function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var buckets = width;
var total = width*height*0.3;
var bucketList = [];
// initialized each bucket to 0 items
for(var i=0; i<buckets; ++i) {
bucketList[i] = 0;
}
// distribute all items over the buckets
for(var i=0; i<total; ++i) {
++bucketList[Math.floor(Math.random()*buckets)];
}
// draw the buckets
ctx.fillStyle = "rgb(200,0,0)";
for(var i=0; i<buckets; ++i) {
ctx.fillRect (i, height-bucketList[i], i+1, height);
}
}
</script>
</head>
<body>
<canvas id="canvas" width="1000px" height="500px"/>
</body>
</html>
Let me respond with an answer, since my comment on the question has a great chance to be lost among its neighbors.
So, with the fix suggested by #xiaoyi in place, the picture looks quite random to me: http://jsfiddle.net/TvNJw. The plotting routine originally suggested in the question wrongly increases the painted bucket width as i grows, while all buckets should have the width of 1. This can easily be depicted by plotting buckets with different colors.

JavaScript iteration

I want to use JavaScript to draw a series of images onto an HTML5 canvas. I have the following while loop, which I had hoped would draw all of the images to the canvas, however, it is currently only drawing the first one:
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();
/*Draw the elements needed for level 1 (26/04/2012) */
var fileName = 1;
var imagePositionX = 20;
var imagePositionY = 30;
while(fileName < 11){
/*Create an array of images here, move to next element of array on each iteration */
var numbers = new Array();
numbers[0] = "1.png"
numbers[1] = "2.png"
numbers[3] = "3.png"
numbers[4] = "4.png"
numbers[5] = "5.png"
image.src = fileName+".png";
image.src = numbers[0];
image.onload = function(){
context.drawImage(image, imagePositionX, imagePositionY, 50, 50);
}
fileName = fileName+1;
imageY = imageY+20;
console.dir(fileName); /* displays in the console- helpful for debugging */
}
To talk through what I had hoped this function would do:
Load each of the images into a different element of the array (so 1.png would be in numbers[0], 2.png in numbers[1], etc. )
It would then take the global variable 'image', and assign its source to the contents of numbers[0]
Then draw that image at the specified position on the canvas.
Then increment the value of the variable fileName by 1, giving it a value of '2'
Next it would increment the value of the Y co-ordinate where it will draw the image on the canvas by 20- moving the position of the image to be drawn down by 20 pixels
After that it would go back to the start of the loop and draw the next image (2.png) on the canvas in a position that is 20 pixels below the position of the first image that was drawn.
It should continue doing this while the value of the variable 'fileName' is less than 11, i.e. it should draw 10 images each new one below the last one that was drawn.
However, for some reason, my function only draws the first image. Could someone point out what I'm doing wrong, and how I could correct this?
Thanks very much.
Edited and commented some points of your code.
The most effective change was at imageY = imageY+20; that was edited to use imagePositionY variable.
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();
/*Draw the elements needed for level 1 (26/04/2012) */
var fileName = 1;
var imagePositionX = 20;
var imagePositionY = 30;
while(fileName < 11){
/*Create an array of images here, move to next element of array on each iteration */
var numbers = new Array();
/* what is not used is not necessary :)
numbers[0] = "1.png"
numbers[1] = "2.png"
numbers[3] = "3.png"
numbers[4] = "4.png"
numbers[5] = "5.png"*/
image.src = fileName+".png";
// image.src = numbers[0];
image.onload = function(){
context.drawImage(image, imagePositionX, imagePositionY, 50, 50);
}
fileName = fileName+1;
imagePositionY = imagePositionY+20; //before: imageY = imageY+20;
console.dir(fileName); /* displays in the console- helpful for debugging */
}
If you take the drawImg stuff and shove it in its own function you can clean this up a bit :) Now we've yanked the async stuff out of the loop, so the image variable doesn't get over-written each time you loop. You're also using a for loop now, which to me is clearer to understand.
function drawLevelOneElements() {
// your stuff
for (var i = 0; i > 5; i++) {
drawImg(ctx, i, x, y);
// update x or y and whatever else
}
}
// put all your image drawing stuff here
function drawImg(ctx, i, x, y) {
var img = new Image();
img.src = i + ".png";
img.onload = function(){
ctx.drawImage(img, x, y, 50, 50);
}
}

Categories