Related
function getRandomInt(min,max) { // random number from (inclusive,exclusive)
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max-min)) + min;
}
var boardLayout = [ // index of images for the canvas
'redtile.png','bluetile.png','greentile.png','shoptile.png','healthtile.png','trainingtile.png'
];
imageObj.onload = function(x,y) { // onload function outside the loop
context.drawImage(imageObj,x,y,x+100,y+100); // makes each image non-overlapping and 100x100
};
for (var x = 0; 900 > x; x += 100) {
for (var y = 0; 700 > y; y += 100) { // every 100x100 square should have another image on it
var integer = getRandomInt(0,6);
var tile = boardLayout[integer]; // gets a random image from boardLayout
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
var imageObj = new Image();
image.onload(x,y); // calling my function
imageObj.src = tile;
}
}
<!doctype html>
<html>
<head>
<title>Fiona</title>
</head>
<body>
<canvas id='game' width="900px" height="700px" style="border:2px solid #000000;">
</canvas>
<script type='text/javascript' src='try.js'></script>
</body>
</html>
I'm pretty new to this, so I was hesitant to post on here, but after a few hours I gave in. The goal is to create a canvas that will have a random assortment of the "boardLayout" images on it. Each image will be 100x100 and the canvas is just big enough for my for-loops, so it should cover the entire canvas.
I have tried countless different ways of doing this over the last few hours and have inserted what I think is the closest I got to doing it. My problem is that I can't put a function (imageObj.onload) into a for-loop and I can't get it to work with the for-loop separate from or even inside the function.
the only thing that pulls up on my html document is the border. I have gotten a picture into the canvas, just never more than one at a time. Any help is appreciated!
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.
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);
...
First time poster here but definitely not a first time reader.
My question is aimed directly at this portion of code I have. I am currently learning how HTML 5 canvases work and am designing my own RPG style game for a University project. After looking around I found some good tutorials on this guys blog, I have followed his code and triple checked it but images are now showing up.
I tried putting an alert() before and after when the image is called to the canvas under drawMap(). It works before the image is drawn but not after, leading me to believe it is something to do with my image rendering. Can someone double check my code and see what is going on? It's driving me insane!
<canvas id="game-viewport" width="760" height="440"></canvas>
<script>
window.onload = init;
var map = Array([0,0],[0,0],[0,0],[0,0]);
var tileSize = 40;
tileTypes = Array("grass.png");
tileImage = new Array();
var loaded = 0;
var loadTimer;
function loadImage(){
for(i = 0; i < tileTypes.length; i++){
tileImage[i] = new Image();
tileImage[i].src = "./game/lib/icons/own_icons/" + tileTypes[i];
tileImage[i].onload = function(){
loaded++;
}
}
}
function loadAll(){
if(loaded == tileTypes.length){
clearInterval(loadTimer);
drawMap();
}
}
function drawMap(){
var mapX = 80;
var mapY = 10;
for(i = 0; i < map.length; i++){
for(j = 0; j < map[i].length; j++){
var drawTile = map[i][j];
var xPos = (i - j) * tileSize;
var yPos = (i + j) * tileSize;
ctx.drawImage(tileImage[drawTile], xPos, yPos);
}
}
}
function init(){
var canvas = document.getElementById('game-viewport')
var ctx = canvas.getContext('2d');
loadImage();
loadTimer = setInterval(loadAll, 100);
}
</script>
The only problem is that ctx is not defined in your drawMap function.
Either pass ctx in to the function as an argument or make it a global variable.
I was lazy and did the second, but you should really do the first. Working code:
http://jsfiddle.net/YUddC/
You really should have the Chrome debugger (or whatever browser you use) on 100% of the time you're developing.. If you did, you'd see an error saying that ctx is not defined in drawMap. If you're using Chrome and press F12 to open developer tools and go to the scripts tab, you'd see this:
Which makes the problem pretty clear!
Would like to test if 2 images pixel match and/or whether a smaller sized image pixel matches some same sized section of a larger image.
The first part, test if two images match perfectly, is pretty straight forward.
//assuming data1,data2 are canvas data of images of the same size
function isMatch(data1,data2){
for(var i = 0; i<data1.length; i++){
if(data1[i] != data2[i]) return false;
}
return true;
}
As long as you aren't using extremely hi-res images I wouldn't worry about speed, one iteration trough canvas data like this is typically very fast, ~10ms per Mega Pixel on a decent computer with a modern browser. Remember width and height of the images must be the same. For most practical purposes this yes or no match is not very useful, encoding errors lead to imperceptible changes in images, it is best to use a metric to measure how far apart the two images are. A naive but fairly effective metric is the RMS of the two images:
//assuming data1,data2 are canvas data of images of the same size
function rmsDiff(data1,data2){
var squares = 0;
for(var i = 0; i<data1.length; i++){
squares += (data1[i]-data2[i])*(data1[i]-data2[i]);
}
var rms = Math.sqrt(squares/data1.length);
return rms;
}
Here the rms will range from [0,255], 0 if the images match exactly, 255 if one image is all rgba(0,0,0,0) and the other is all rgba(255,255,255,255). How to threshold what is an acceptable rms difference is up to you to tune (around 1 to 2 perhaps).
For the second part of your question, whether a smaller sized image pixel matches some same sized section of a larger image, I'm assuming that they are not translated and that we know the scale difference between the two, otherwise this would open it up into a very complicated and interesting problem see Pattern Recognition. Then with this egregious assumption I would use the canvas to scale the large image down to the exact same size as the smaller one and get the rms between them. Don't test for an exact match as scaling will always create slight errors and never be an exact match.
I don't know if there is anything like that out there, and I'm not sure the web-browser is the place to do it in... however I have been playing around with the canvas element a little bit and wanted to share what I come up with. Maybe you find something useful in it.
The code below is tested in Chrome 20 only.
You must have the images and the script loaded from the same domain, or it will not work (I didn't find a way to upload images on jsfiddle so I didn't put this there)
To test if two images are identical I load each image and draw it on a canvas element, and compare the width, height and pixeldata.
index.html
<!doctype html>
<html>
<head>
<title></title>
<script type="application/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="application/javascript" src="compare.js"></script>
</head>
<body>
</body>
</html>
Identical pixels
compare.js
$(function(){
function compare(url_a, url_b, callback) {
var canvas = $("<canvas>").appendTo(document.body)[0];
var ctx = canvas.getContext('2d');
var
image_a = new Image(),
image_b = new Image();
function getData(image) {
//Set canvas to the same size as the image
canvas.width = image.width;
canvas.height = image.height;
//Draw the image on the canvas
ctx.drawImage(image, 0, 0);
//Get the image data from the canvas.
return ctx.getImageData(0,0,canvas.width,canvas.height);
}
function compareIdentical(A,B) {
// Check sizes
if (
A.width != B.width ||
A.height != B.height ||
A.data.length != B.data.length
) {
return callback(false,'Not same size');
}
var a=A.data; b=B.data;
//Check image data
for (var idx=0, len=A.data.length; idx < len; ++idx) {
if (a[idx] !== b[idx]) {
return callback(false,'Not same');
}
}
return callback(true,'Identical');
}
$(image_a).load(function(){
console.log('Image A loaded');
image_a = getData(image_a);
//Load image b
image_b.src = url_b;
});
$(image_b).load(function(){
console.log('Image B loaded');
image_b = getData(image_b);
canvas.parentNode.removeChild(canvas);
compareIndentical(image_a, image_b);
//comparePartOf(image_a, image_b);
});
//Load image a
image_a.src = url_a;
}
//IMPORTANT: Images must be loaded from the same domain as the
//script, or getImageData will not give any data.
compare(
'IMG_2195.JPG',
'IMG_2195.JPG',
function(result,message){
console.log(result,message);
}
);
});
To test if an image is part of another image, is slow.
Compare sizes: if SMALL > BIG then it can't be part of BIG.
For every x and y of BIG, compare if SMALL is a fit.
(add this to the code above, and change the call after image B is loaded)
function comparePartOf(bigimg, smallimg) {
if (
bigimg.width < smallimg.width ||
bigimg.height < smallimg.height ||
bigimg.data.length < smallimg.data.length
) {
return callback(false,'bigimg smaller than smallimg');
}
var
big=bigimg.data,
small=smallimg.data,
idx,
len=small.length/4,
big_x,
big_y,
big_width = bigimg.width,
big_height = bigimg.height,
small_x,
small_y,
small_width = smallimg.width,
small_height = smallimg.height,
lenw = big_width - small_width,
lenh = big_height - small_height,
big_offset,
small_offset,
result=false;
for(big_x=0; big_x < lenw; ++big_x) {
for(big_y=0; big_y < lenh; ++big_y) {
result = true;
for (idx=0; idx < len; ++idx) {
small_x = idx % small_width;
small_y = Math.floor(idx / small_width);
big_offset = (
(big_x + small_x) + ((big_y + small_y) * big_width)
)<<2;
small_offset = idx << 2;
if (
big[big_offset++] != small[small_offset++] ||
big[big_offset++] != small[small_offset++] ||
big[big_offset++] != small[small_offset++] ||
big[big_offset] != small[small_offset]
) {
result = false;
break;
}
}
if (result) return callback(true,'Found at '+big_x+' '+big_y);
}
}
return callback(false,'not found');
}
you could try paper.js
it allows you to use your image as a raster
http://paperjs.org/tutorials/images/using-pixel-colors/
http://paperjs.org/tutorials/images/working-with-rasters/
the paper.js lib can definitely do more than comparing images..
here's a simple script that works..
// rasterize both images
var im_a = new Raster('image_a');
var im_b = new Raster('image_b');
var ratio = Math.round(im_a.width / 100);
// downsize the images so we don't have to loop through all the pixels.
im_a.size = new Size(im_a.width/ratio, im_a.height/ratio);
im_b.size = new Size(im_b.width/ratio, im_b.height/ratio);
//hide the images, so they don't display on the canvas
im_a.visible = false;
im_b.visible = false;
var different = false;
// check the image dimensions
if((im_a.width == im_b.width) && (im_a.height == im_b.height)){
// loop through the pixels
for(x = 0 ; x < im_a.width ; x++){
for(y = 0; y < im_a.height; y++){
if(im_a.getPixel(x,y) != im_b.getPixel(x,y) ){
different = true;
break;
}
}
}
}else{
alert('not the same size');
}
Well, i don't know if there's something out there for that so, i'll give it a shot:
first, you will need a function to compare two arrays, you will find one anywhere on the web; like:
function arrayCompare(){
if (x === y)
return true;
if (x.length != y.length)
return false;
for (key in x) {
if (x[key] !== y[key]) {
return false;
}
}
return true;
}
then you create a function that will return the imageData, of the image:
function getImgData(imgUrl){
var img = new Image(); // Create new img element
img.src = '../img/logo.png'; // params are urls for the images
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img1,0,0);
var imageData = ctx.getImageData(0,0,canvas.width, canvas.height);
//returns an array, see documentation for * info
return imageData.data;
}
then you can do something like:
var imgData1 = getImgData('../img/image1');
var imgData2 = getImgData('../img/image2');
if(arrayCompare(imgData1, imgData2)){
//images are the same;
} else {
// images are different
}
Now, this covers the first part of the problem. I'm sure that with a little bit of effort, you can find a function on the web to compare 2 arrays finding out if one is contained by the other.
As a final note, i am aware that these arrays are very very large and that this implemention may not be cost effective. I know that there are probably better solutions for this problem, but this was what i could think of. I'm sure you can find more effective array comparers around and, maybe my approach is completely useless near others that i don't know.
Resemble.js is a JavaScript library for image comparison. From GitHub description:
Resemble.js can be used for any image analysis and comparison
requirement you might have in the browser. However, it has been
designed and built for use by the PhantomJS powered visual regression
library PhantomCSS. PhantomCSS needs to be able to ignore antialiasing
as this would cause differences between screenshots derived from
different machines.
Resemble.js uses the HTML5 File API to parse image data, and canvas
for rendering image diffs.
assuming data1 & data2 are arrays. you can use canvasPixelArray(s)
var same = [];
same = data1 && data2;
if (same.length < data1.length) {
//your code here
}