I'm trying to implement pixel perfect collision detection in my canvas game, however I can't seem to get the pixel information from my sprites.
I need the x and y values for each pixel of the sprite, and from what I've read I use the getImageData() method to do that.
However, this doesn't work:
this.sprite = new Image();
this.sprite.src = 'img/player.png';
console.log(this.sprite.getImageData());
Am I maybe using the wrong type of sprite? Because I get this error in the console:
Uncaught TypeError: Object # has no method
'getImageData'
Here’s how to use a sprite’s pixel data to do pixel-perfect hit-testing
First, draw your sprite normally on your visible canvas.
Create a red-masked copy of the sprite on a hidden canvas. This copy is the exact size as the sprite but contains only transparent or red pixels.
Track the visible sprite’s bounding box. When the bounding box is clicked calculate the X/Y of the mouseclick in relation to the sprite’s bounding box (not in relation to the canvas).
Then, refer to the red-masked sprite and see if the corresponding pixel at that X/Y is red or transparent. If the pixel is red, you have a pixel-perfect hit. If the pixel is transparent, no hit.
In this illustration, assume the blue dots are the X/Y click position. Since the corresponding X/Y pixel in the red-masked canvas is “red”, this is a HIT.
Here is code to create a red-masked sprite. I don’t show the code for hit-testing here, but if you try and can’t code the hit-test, I’ll take the time to code up the hit-testing too.
Important note: to get this code to run, you must avoid the cross-domain security restriction. Be sure your image source is on your local domain, otherwise you will run into a cross domain security violation and the masking image will not be drawn…So you can't do this for your sprite source: http://otherDomain.com/picture.jpg!
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid blue;}
</style>
<script>
$(function(){
var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
var img=new Image();
img.onload=function(){
ctx.drawImage(this,100,25);
// make a red-masked copy of just the sprite
// on a separate canvas
var canvasCopy=document.getElementById("canvasCopy");
var ctxCopy=canvasCopy.getContext("2d");
canvasCopy.width=this.width;
canvasCopy.height=this.height;
ctxCopy.drawImage(img,0,0);
// make a red-masked copy of the sprite on a separate canvas
var imgData=ctxCopy.getImageData(0,0,c.width,c.height);
for (var i=0;i<imgData.data.length;i+=4)
{
if(imgData.data[i+3]>0){
imgData.data[i]=255;
imgData.data[i+1]=0;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
}
}
ctxCopy.putImageData(imgData,0,0);
}
img.src = "houseIcon.png";
}); // end $(function(){});
</script>
</head>
<body>
<p>Original sprite drawn on canvas at XY:100/25</p>
<canvas id="canvas" width="400" height="300"></canvas>
<p>Red-masked on canvas used for hit-testing</p>
<canvas id="canvasCopy" width="300" height="300"></canvas>
</body>
</html>
To do pixel-perfect collision testing between 2 sprites, you would:
Create a red-masked canvas for both sprite#1 and sprite#2.
First test if the bounding boxes of the 2 sprites are colliding. If the bounding boxes are not colliding, the 2 sprites are not colliding, so you can stop the hit-test here.
If the 2 sprites are possibly colliding using the bounding boxes test, create a third canvas for the collision test.
You’re going to use canvas’s compositing method to test for a collision between sprite#1 and sprite#2 by drawing both sprite#1 and sprite#2 onto the third canvas. Using compositing, only the COLLIDING pixels of the 2 sprites will be drawn on the third canvas.
Here’s how Compositing with “destination-in” works: The existing canvas content is kept where both the new shape (sprite#2) and existing shape (sprite#1) content overlap. Everything else is made transparent.
So…
Draw sprite#1 in its transformed state into the third canvas (transforms could be move, rotate, scale, skew--anything!).
Set the globalCompositeOperation to destination-in.
context.globalCompositeOperation = 'destination-over';
Draw sprite#2 in its transformed state into the third canvas.
After this draw, the third canvas contains only the colliding portions of sprite#1 and sprite#2
Test each pixel in the third canvas for non-transparent pixels. If you find any non-transparent pixels, the 2 sprites ARE COLLIDING.
Depending on what action you want to take upon collision, you might just bail out when you find the first colliding pixel.
Related
The following creates an unexpected clip path. Is it because of the
movement from the end of one arc to the start of the next? The circles turned out to be ellipses, which was also unexpected.
var canvas3=document.getElementById("drawing3");
var ct=canvas3.getContext("2d");
ct.beginPath();
ct.arc(50,80,30,0,Math.PI*2,false);
ct.arc(120,40,60,0,Math.PI*2,true);
ct.arc(180,40,30,0,Math.PI*2,true);
ct.arc(240,40,30,0,Math.PI*2,true);
ct.clip();
ct.fillStyle="yellow";
ct.fillRect(10,10,400,200);
ct.fillStyle="magenta";
ct.font = "40px sans-serif";
ct.fillText("what's this?",30,70);
By the way, does a clip area always have to start with beginPath()?
Why ellipses instead of arcs
Although you don't include your CSS, canvas drawings become deformed when the canvas element is resized with CSS. The default size of html5 canvas is 300x150 so adjusting its size with CSS will stretch or squish subsequent drawings unless the CSS resizes the canvas exactly in proportion to its original 2:1 proporitions.
So, if you want to resize the canvas do the resizing by changing the element's size directly:
var canvas3=document.getElementById("drawing3");
canvas3.width=400;
canvas3.height=300;
Why beginPath is useful (essential!)
A context.clip always acts upon the last set of path commands and a set of path commands should be started with context.beginPath. So yes, a clip should always start with context.beginPath.
Why is beginPath useful (always use it!): If you issue multiple sets of path commands without starting each set with beginPath, then each new set will also execute all previous sets. Your path will just become one long drawing rather than having distinct drawings.
Fixing the inter-connected circles
context.arc is a canvas path drawing command.
Path commands will always connect (with a line) all drawings that occur between context.beginPath and context.stroke or context.fill.
But you can command the path to "pick up the pencil and move it to the next circle center" using context.moveTo. This will prevent your circles from becoming inter-connected.
// pick up the pencil and move it to the next arc's centerpoint
ct.moveTo(x,y);
// and draw the arc around the centerpoint
ct.arc(x,y,r,0,Math.PI*2,false);
Here's your code refactored to "pick up the pencil" between arcs:
var canvas3=document.getElementById("drawing3");
var ct=canvas3.getContext("2d");
ct.beginPath();
arcAtXY(50,80,30);
arcAtXY(120,40,60);
arcAtXY(180,40,30);
arcAtXY(240,40,30);
ct.clip();
ct.fillStyle="yellow";
ct.fillRect(10,10,400,200);
ct.fillStyle="magenta";
ct.font = "40px sans-serif";
ct.fillText("what's this?",30,70);
function arcAtXY(x,y,r){
ct.moveTo(x,y);
ct.arc(x,y,r,0,Math.PI*2,false);
}
canvas{border:1px solid red;}
<canvas id="drawing3" width=300 height=300></canvas>
An additional thought not directly related to your design
If you want to stroke the circles instead of filling them, you would move to the next circle's perimeter rather than its centerpoint. Moving to the centerpoint would cause an automatic line to draw from the centerpoint to the perimeter.
function arcAtXY(x,y,r){
ct.moveTo(x+r,y);
ct.arc(x,y,r,0,Math.PI*2,false);
}
I have a jpeg image that has been decoded, and I am writing it to canvas with putImageData. Is it possible to then move this image around? I cannot find any documentation on it.
The idea is that I will crop a certain part of the decoded image with dirtyX and dirtyY, and now I would like to crop another part of the image. I'm using the decoded image like a spritesheet.
Use the clipping version of drawImage to draw your individual sprites to the canvas
(Don't use getImageData & putImageData for clipping since drawImage is easier and faster.)
If you want to use your decoded image as a spritesheet then you can use the canvas's clipping version of context.drawImage to clip a particular sub-section of your spritesheet and draw it to the canvas.
Here's a previous SO post about the clipping version of drawImage.
HTML / Java Script Canvas - how to draw an image between source and destination points?
Here's example code and a Demo:
Note that the way to "move" sprites on the canvas is to clear the canvas and redraw the desired sprite in it's new position. (You can't "move" an existing drawing on the canvas).
$(window).load(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// animation related variables
var lastFlap,lastMove;
// define a bird object
// x,y are the position of the bird on the canvas
// spriteX,spriteY is the position of the first desired
// sprite image on the spritesheet
// width,height is the size of 1 sprite image
// currentFrame is the index of which of the sprite images to display
// currentDirection. The sprite plays forward and then backward to
// accomplish 1 flap. This determines if the next frame index will
// be increased (play forward) or decreased (play backward)
var bird={
x:30,
y:30,
spriteX:0,
spriteY:52,
width:51,
height:51,
frames:4,
currentFrame:0,
currentDirection:1
}
// load the spritesheet and start the animation
var spritesheet=new Image();
spritesheet.onload=start;
spritesheet.src="https://dl.dropboxusercontent.com/u/139992952/multple/birdSpritesheet.png";
function start(){
requestAnimationFrame(animate);
}
function animate(time){
// request another animation frame
if(bird.x<canvas.width){
requestAnimationFrame(animate);
}
// if the lastFlap or lastMove times don't aren't set, then set them
if(!lastFlap){lastFlap=time;}
if(!lastMove){lastMove=time;}
// calculate the elapsed times since the last flap and the last move
var elapsedFlap=time-lastFlap;
var elapsedMove=time-lastMove;
// if 50ms have elapsed, advance to the next image in this sprite
if(elapsedFlap>50){
// advance to next sprite on the spritesheet (flap)
bird.currentFrame+=bird.currentDirection;
// clamp bird.currentFrame between 0-3 (0,1,2,3)
// (because there are 4 images that make up the whole bird sprite)
if(bird.currentFrame<0 || bird.currentFrame>bird.frames-1){
bird.currentDirection*=-1;
bird.currentFrame+=bird.currentDirection;
}
// reset the flap timer
lastFlap=time;
}
// locate the current sprite from the spritesheet
var sx=bird.spriteX+bird.currentFrame*bird.width;
var sy=bird.spriteY;
// if 100ms have elapsed, move the bird across the canvas
if(elapsedMove>100){
bird.x+=3;
lastMove=time;
}
// clear the whole canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw the current part of the bird sprite at the current bird.x
ctx.drawImage(spritesheet,
sx,sy,bird.width,bird.height,
bird.x,bird.y,bird.width,bird.height
);
}
}); // end $(function(){});
body{ background-color: white; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>The canvas animating a clipped sprite</h4>
<canvas id="canvas" width=300 height=100></canvas>
<br>
<h4>The spritesheet</h4>
<img id=spritesheet src='https://dl.dropboxusercontent.com/u/139992952/multple/birdSpritesheet.png'>
Yes and No :-)
Since canvas draws in immediate-mode it doesn't know that you have drawn an image on the canvas. So, you cannot draw it on the canvas one single time and move the painted image around. You will have to manually redraw the complete image in each animationframe at a new postion.
You can do that using the requestAnimationFrame() as Jonas Grumann explains.
I'm looking to create a bevel effect on a (non-rectangular) shape (in a canvas element). Searched almost the entire internets: no luck so far. Looking for suggestions. I do not want to implement a bevel effect myself until I exhaust all existing possibilities. Target browser is Chrome.
Here is an image without a bevel applied, and an image with the effect I am looking for. This was done in Photoshop:
Edit:
I wrangled together something with markE's suggestion.
The two problems with the shadow offset approach are:
1) There is a border that must be drawn, which I did not want.
2) The border's thickness determines the strength of the shadow.
I did not want the border to be visible so I needed a way to have as small a border as possible, and then to cut out the border. As of now that involves a number of different steps:
1) First, I create a shape with a transparent fill color, a border, and a slight shadow (with (0,0) offsets) and a blur of 1. I then draw the shape onto itself to increase the opacity of the shadow.
2) Then, I create a shape with a transparent fill color, a border, and shadows as described by markE. I set the lineWidth to a very small number -- e.g. .5. I apply the shape in (1) onto this shape (via globalCompositeOperation = 'destination-out'), then draw the shape over itself 3 times in order to increase the opacity of the shadow.
3) Then I draw the normal shape, without a border. I apply (2) onto the normal shape and again cut what would have been the border with the shape from (1) using globalCompositeOperation = 'destination-out'.
Here is the result:
SO the question is first - how does the bevel effect work?
You might say that bevel effect is also 3d-ing your flat 2d image, and it really does (well, in the way the object's been lit).
Basically, shading calculations are done like they were done on some actual 3d object, but since you have only 2d image, you would also need something known as - normal map.
Normal map is 2d image that, instead of colors in the image, has encoded surface normal information in the RGB channels. So every pixel in the image has RGB component and R channel is for surface normal in the x-direction, G is for normal in y-direction and B is for z-axis component of the normal.
Read more about normal mapping here.
Another option is having the bump map. That's an image that instead of color of the pixels or information about normals, holds the information about "height" of the pixels.
Read more about bump mapping here.
Whether you have bump map or normal map, results are quite similar. Bump maps have only one channel in the image (greyscale images), so they are easier to make, and tend to be more "realistic looking", but it depends what effect are you after.
Here's an example of the bump map for the bevel effect on the square image.
The problem is that images in general have non-uniform shapes (like your example), so how to create bump maps for those?
The solution is to create something known as "Eucledian Distance Matrix".
EDM's are widely used in Photoshop and they hold the information how much certain pixel is away from your image (nearest colored pixel on your layer). EDMs are used for stroking objects and for - bevel effect. (There are plenty of resources for how to generate EDM)
So example EDM for 4x4 picture that has only pixel colored in 2nd row and 2nd column would look like this.
1 1 1 2
1 0 1 2
1 1 1 2
2 2 2 3
Using EDM, you can get information how much certain pixel is "inside" your image and then generate the bump map based on that information.
if (isInside(x,y))
if ( dist = innerDistanceAt(x,y) < bevelWidth )
bumpMap[x][y] = dist/bevelWidth;
else
bumpMap[x][y] = 1.0;
This code is just an example, pseudo-code, but you should get the idea.
So we have now made out bump map, and it's time to calculate the lighting (do shading).
There are plenty of shading models that can be used - list. And here's more visual difference between them - link.
I would start with Lambert model. There are plenty of resource that you can find about it.
Generally, you need surface normal (or bump map from which you can then calculate the surface normal (use differentials)) and light vector. From that information and diffuse color (well, in this case, pixel color in the image) you can calculate how lit that pixel is and then output shaded pixel.
I haven't posted a lot of working code, since stroke effect and bevel effects are quite complex, but you should get the idea. This is how they 'really' work in photo editing software, and as you can see, it's not that easy.
If you have any question feel free to ask.
Random link about bevel effect - here.
You can create a bezel effect with an inset-shadow.
The process is straightforward:
Define a non-rectangular path,
Create a clipping path from the non-rectangular path,
Apply a shadow-stroke (which is the bevel effect);
Here's example code and a Demo: http://jsfiddle.net/m1erickson/4kvLn/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: white; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var points=[];
points.push({x:130,y:130});
points.push({x:200,y:130});
points.push({x:250,y:165});
points.push({x:200,y:200});
points.push({x:130,y:200});
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/landscape2.jpg";
function start(){
definePath();
ctx.save();
ctx.strokeStyle="#000";
ctx.clip();
ctx.drawImage(img,10,0);
ctx.shadowColor = '#000';
for(var i=0;i<3;i++){
for(var j=0;j<3;j++){
ctx.shadowBlur=4+i;
ctx.lineWidth=0.50;
ctx.stroke();
}
}
ctx.restore();
}
function definePath(){
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
var pt=points[i];
ctx.lineTo(pt.x,pt.y);
}
ctx.quadraticCurveTo(80,165,130,130);
ctx.closePath();
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Let say I got the sprite to the left and I want to apply a red filter over the image before drawing it in a canvas so it looks like the sprite to the right. http://puu.sh/6dQ0E.png
Is there a way to do that?
It could be done in two steps, but I don't know if it's possible to draw the geometric figure that has the exact same shape than the image. (Ex: Drawing a rectangle over it won't match.)
Note: I want the filter to only be applied to this image, not the whole canvas.
You can combine 2 filtering operations to draw the red filter only on top of your existing sprite.
globalCompositeOperation="source-atop" will cause new drawings to only draw on top of existing non-transparent pixels.
globalAlpha will make your new drawings semi-transparent.
Taken together, you can draw a semi-transparent filter that only fills where your non-transparent ghost exsits.
Then just draw a red rectangle over your existing sprite (the rectangle will only be visible inside the existing ghost).
ctx.drawImage(ghostSprites,0,0);
ctx.globalAlpha=0.62;
ctx.globalCompositeOperation="source-atop";
ctx.fillStyle="red";
ctx.fillRect(spriteX,spriteY,spriteWidth,spriteHeight);
Demo: http://jsfiddle.net/m1erickson/W4XrG/
From here...
Notice the black outlines of your sprite become washed from the red filter.
You could also use context.getImageData to grab only the black pixels of your ghost. Then redraw those black pixels over your red-filtered ghost so the black outlines are not washed. If you feel ambitious, you could give that a try!
Good luck with your project!
Here’s code
<style>
body{ background-color: ivory; padding:20px;}
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=start;
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/temp0a.png";
function start(){}
var spriteX=0;
var spriteY=0;
var spriteWidth=133;
var spriteHeight=161
$("#recolor").click(function(){
ctx.drawImage(img,0,0);
ctx.globalAlpha=0.62;
ctx.globalCompositeOperation="source-atop";
ctx.fillStyle="red";
ctx.fillRect(spriteX,spriteY,spriteWidth,spriteHeight);
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Before</p>
<img src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/temp0a.png">
<p>After</p>
<button id="recolor">Click to recolor the green sprite</button><br>
<canvas id="canvas" width=300 height=161></canvas>
</body>
</html>
I am building up to a rotating hypercube on an HTML5 Canvas, but before that goal I am reaching a basic difficulty with the Canvas. I have a white/uncolored canvas, and I am trying to draw lines after setting fillStyle and strokeStyle to '#000000', and I have not yet succeeded at getting any pixel on the canvas to appear other than white.
The canvas is at http://blajeny.com/tesseract.html , and the JavaScript which is part math and part old-fashioned JavaScript, is at http://blajeny.com/js/tesseract.js . The log says that it is drawing lines on the canvas, some of which should intersect the 500x500 canvas and some of which should lie completely inside the canvas, but all I can see is pure white.
The math side of it needs work in terms of projection from a higher- to a lower-dimensional surface. However the difficulty I am trying to address now is a basic HTML5 canvas issue in that I am setting a color, moving to and drawing a line to coordinates some of which overlap and some of which are within the 500x500 canvas, and not seeing anything turn black. (The JavaScript console logs the lines I am trying to draw.)
How can I get the lines I am trying to draw to show up?
You need to let canvas know when you start and stop drawing using context.beginPath() and context.stroke()/context.fill(). Here's code and a Fiddle: http://jsfiddle.net/m1erickson/Jw8XU/
<!DOCTYPE HTML>
<html>
<head>
<style>
canvas{border:1px solid red;}
</style>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(50, 75);
ctx.lineTo(150, 150);
ctx.stroke();
</script>
</body>
</html>