Create a bevel effect on non-rectangular shapes in Javascript - javascript

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>

Related

Cutting out background from image/canvas using a predefined pattern

I'm not requiring a full answer to this question, just an idea on how to approach it.
Let's say that a user on my site wants to cut out the background from this image:
Normally this would be a job for some magic outline tool, but this site already carries something that would provide a perfect cutout pattern, namely this:
As you can see this car will fit perfectly over the top one.
If the user could somehow fit the bottom picture over the top one and cut out everything outside that, it would be a perfect background removal.
How do I go about building something like this? Or are there already software out that does something similar?
The bottom picture could be anything, for examle a completely black model for easier recognition, but I'd think that it would be smarter if it used the outline of the transparent .png image and cut out everything outside it.
(The picture itself doesn't need to be used either if there is some way to extract the important bits of it needed for the cutout, of course).
Here's how to do your knockout with html5 canvas
If you have an exact image that defines the desired cut and you also know the position where the cut is to be made, then you can use compositing to do you cut-out. With destination-in compositing, new drawings will keep existing pixels only where new & old pixels are non-transparent.
Here's a few notes, example code and a Demo:
Notes:
Your car on your car-only image is not exactly the size of the car on the car+background image -- the car-only is a bit wider. This causes the cut-out to have some extra pixels. But if you had exact sizing the cutout would be perfect.
Your car on your car-only image has semi-transparent shadowing. This causes the cutout to have some extra semi-transparent pixels where the shadow was on the car-only image.
Example & demo using a different exactly sized cutout with no shadow:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var bk=new Image();
bk.onload=start;
bk.src='https://dl.dropboxusercontent.com/u/139992952/multple/model-t-car.png';
var cut=new Image();
cut.crossOrigin='anonymous';
cut.onload=start;
cut.src="https://dl.dropboxusercontent.com/u/139992952/multple/model-t-cutout.png";
var imgcount=2;
function start(){
if(--imgcount>0){return;}
canvas.width=bk.width;
canvas.height=bk.height;
ctx.drawImage(bk,0,0);
ctx.globalCompositeOperation='destination-in';
ctx.drawImage(cut,125,40);
// always clean up -- reset the default compositing mode
ctx.globalCompositeOperation='source-over';
}
body{ background-color: ivory; }
canvas{border:1px solid red; margin:0 auto; }
<h4>Original with background</h4>
<img src='https://dl.dropboxusercontent.com/u/139992952/multple/model-t-car.png'>
<h4>Exactly sized cutout</h4>
<img src='https://dl.dropboxusercontent.com/u/139992952/multple/model-t-cutout.png'>
<br>
<h4>With background removed</h4>
<canvas id="canvas" width=300 height=300></canvas>
There's no easy plug-and-play way. I can think of 2 methods:
(1) SVGs. Plot the different points on the outline of the car yourself (very time-consuming), or import the car into Illustrator (or similar), export it as an SVG, and use the points it calculated for you with clip-path.
(2) PNG (or GIF).
Create a rectangle of solid color in Illustrator/Photoshop.
Paste the car image on top of it as a new layer.
Select the outline of the car.
Delete the selection from the rectangle of color. This will leave a rectangle with a transparent car-shaped hole in it.
Save the rectangle as a PNG or GIF or other format supporting transparent backgrounds.
Use CSS to overlay that PNG on various images of cars.
This is useful if, for instance, you have 5 photos of cars in different colors, all with the same dimensions and taken from the same angle, and want to display the 5 cars with the same background. No need to copy the same background 5 times in Photoshop; just re-use the PNG 5 times in CSS.
Now if you want to change the "background" (which is actually an overlay and not really a background) you need only change it in one place.
Keep in mind: The image you provided is not a perfect outline because it has a shadow.

canvas clearrect, with alpha

So I know that context.clearRect makes pixels transparent, but I'm wondering, is there a function to make pixels translucent?
For example, say I have a canvas with these colors (fourth one in each color is alpha):
#ffff #feef #abff
#5f6f #000f #ffff
Running clearRect would resolve into this (or something, just make them all transparent):
#fff0 #fee0 #abf0
#5f60 #0000 #fff0
I want to remove opacity, but not make it transparent (kind of like globalAlpha for clearRect), so that it can end up like this (lets say I set the globalAlpha equivalent to 0.5):
#fff8 #fee8 #abf8
#5f68 #0008 #fff8
Is this possible? Or would it be simpler just to draw everything on an off-screen canvas, then draw that canvas (with globalAlpha set) on an on-screen one?
Let me know if this isn't clear in any way.
The answer above gets the job done, however getImageData is super slow and if you have a lot of other stuff going on it will slow down your animation immensely. If you create a second off screen canvas element you can set its global alpha to .9 and shuffle them back and forth and get the same effect with much greater speed.
context2.clearRect(0,0,width,height);
context2.globalAlpha = .9;
context2.drawImage(canvas1,0,0);
context1.clearRect(0,0,width,height);
context1.drawImage(canvas2,0,0);
context1.the rest of the drawing that you are doing goes here.
I just tried to figure this out too, and I've decided to count through the pixels, setting the alpha channel of each one manually. This is a bit more work, because I can't just cover the entire canvas with a rect, but it's working so far.
I'm doing this so that I can set a background image for the webpage and put my canvas animation over it, without having to draw the background in the canvas element.
var oldArray = context.getImageData(0,0,canvas.width,canvas.height);
//count through only the alpha pixels
for(var d=3;d<oldArray.data.length;d+=4){
//dim it with some feedback, I'm using .9
oldArray.data[d] = Math.floor(oldArray.data[d]*.9);
}
sw.context.putImageData(oldArray,0,0);

Getting image pixel data from a canvas sprite

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.

How do the createRadialGradient() and addColorStop() methods work?

Can anybody explain me, preferably with illustrative pictures, how do these methods work? I've looked at different examples and tutorials but can't seem to grasp the idea. I understand, that the createRadialGradient() creates two circles, but how do those two circles relate to each other and the addColorStop() method ?
Yes, I know this is necro'd... but it seems a valid question that was never snawered, so I leave this here in case someone else needs it.
================================================================================
Well, a gradient is a smooth shift from one color to another.
In any gradient, you pick a point where the colors begin, a point where the color ends, and the colors you want, and the color smoothly transitions between them.
The color stops are there to determine what colors will be a part of the gradient, and where in the gradient those colors will appear.
In a linear gradient, the color transitions from one color to the next in a straight line so that bands of color form along the line, perpendicular to the axis.
In a radial gradient, the color wraps itself around a central circle (or point, which is simply a very small circle) and transitions from that center to the edge of the gradient.
This means that the color bands that make up the gradient form into larger and larger circles as they transition from center to edge.
HEREis an example of a simple radial gradient transitioning from white in the center to black at the outside edge.
This is the origin of the syntax for createRadialGradient.
This first circle will be where the color begins, we will arbitrarily state that it starts in the center... lets say that is x:100,y:100
The second circle will be where the color ends, since we picked the center to start it, the color finishes at the outside edge of the circle (although these could just as easily be reversed).
For simplicity's sake, the center point (in this case) will remain the same: x:100,y:100
The real difference between these circles will be the radius. Since the center should be small, we will give it a radius of 1, while the larger outside radius of the circle, we will make 100.
This gives us the required parameters:
x = 100;
y = 100;
radiusStart = 1;
radiusEnd = 100;
var grad = ctx.createRadialGradient(x,y,radiusStart,x,y,radiusEnd);
However, if we try to display this code as is, we won't see anything... this is because we need the color stops.
Color stops are declared with two parameters... the position and the color of the stop.
The position is a number between 0 and 1, and represents the percentage of the distance from start to end.
If we want the color to start at white, then we would use:
grad.addColorStop(0,'#FFFFFF');
This means that we the color stop starts at 0% of the way down the line (meaning right where the gradient begins), and gives the color to paint there as white.
Similarly, the second gradient should be black, and should be placed at the very end of the gradient:
grad.addColorStop(1,'#000000');
Notice that these do not reference context directly... we referenced context to create the gradient, but we are adding stops directly to the gradient that we created.
When we are finished creating the gradient, then we can use this gradient as a fillStyle (or even a strokeStyle) for as long as the gradient that we created remains in scope.
Full code:
x = 100;
y = 100;
radiusStart = 1;
radiusEnd = 100;
var grad = ctx.createRadialGradient(x,y,radiusStart,x,y,radiusEnd);
grad.addColorStop(0,'#FFFFFF');
grad.addColorStop(1,'#000000');
ctx.beginPath();
ctx.arc(x,y,radiusEnd,0,Math.PI*2,false);
ctx.fillStyle = grad;
ctx.fill();
While you are playing with this, don't forget to experiment a bit.
Try adding more than two color stops... this means that instead of transitioning from black to white (boring), you can transition from blue to green to yellow to orange to red to purple.
Just remember to set the positions appropriately... if you have 6 colors, for example (as above), and you want them evenly spaced, then you would set the positions at .2 intervals:
grad.addColorStop(0,'#0000FF');
grad.addColorStop(.2,'#00FF00');
grad.addColorStop(.4,'#FFFF00');
grad.addColorStop(.6,'#FF8800');
grad.addColorStop(.8,'#FF0000');
grad.addColorStop(1,'#AA00AA');
Any color stops you try to place in the same position will overwrite one another.
Another cool effect is to set two different centers for the circles when creating the gradient... this lends a different effect to the gradient, and can be a worthy addition to your arsenal.
HERE are two images from the W3C specification (which itself is HERE). Both of these are radial gradient with different center points for the first and second circles.
A better example is HERE, although the code itself is written in svg for html backgrounds, the examples still show some great ways to use radial gradients with differing centers. He covers the theory of radial gradients as well as shows some very nice examples.
Finally, a tip... while it is quite possible to write gradients by hand, its kind of a pain in the butt. It is usually far easier to grab Photoshop, Illustrator, GIMP, or Inkscape, and build the gradient in one of those... then you can adjust the gradient directly until you like it. Then simply copy the color stop information over to your canvas code.
Hope some of that helps someone.

html canvas motion blur with transparent background

I just created a fancy canvas effect using cheap motion blur
ctx.fillStyle = "rgba(255,255,255,0.2)";
ctx.fillRect(0,0,canvas.width,canvas.height);
Now i want to do the same, but with transparent background. Is there any way to do something like that? I'm playing with globalAlpha, but this is probably a wrong way.
PS: Google really don't like me today
Here's a more performance friendly way of doing it, it requires an invisible buffer and a visible canvas.
buffer.save();
buffer.globalCompositeOperation = 'copy';
buffer.globalAlpha = 0.2;
buffer.drawImage(screen.canvas, 0, 0, screen.canvas.width, screen.canvas.height);
buffer.restore();
Basically you draw your objs to the buffer, which being invisible is very fast, then draw it to the screen. Then you replace clearing the buffer with copying the last frame onto the buffer using the global alpha, and globalCompositeOperation 'copy' to make the buffer into a semi-transparent version of the previous frame.
You can create an effect like this by using globalAlpha and two different canvas objects: one for the foreground, and one for the background. For example, with the following canvas elements:
<canvas id="bg" width="256" height="256"></canvas>
<canvas id="fg" width="256" height="256"></canvas>
You could copy draw both a background texture and a motion blurred copied of foreground like so:
bg.globalAlpha = 0.1;
bg.fillStyle = bgPattern;
bg.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
bg.globalAlpha = 0.3;
bg.drawImage(fgCanvas, 0, 0);
Here is a jsFiddle example of this.
OP asked how to do this with an HTML background. Since you can't keep a copy of the background, you have to hold onto copies of previous frames, and draw all of them at various alphas each frame. Nostalgia: the old 3dfx Voodoo 5 video card had a hardware feature called a "t-buffer", which basically let you do this technique with hardware acceleration.
Here is a jsFiddle example of that style. This is nowhere near as performant as the previous method, though.
What you are doing in the example is partially clear the screen with a semi transparent color, but as it is, you will always gonna to "add" to the alpha channel up to 1 (no transparency).
To have this working with transparent canvas (so you can see what lies below) you should subtract the alpha value instead of adding, but I don't know a way to do this with the available tools, except running all the pixels one by one and decrease the alpha value, but this will be really, really slow.
If you are keeping track of the entities on screen you can do this by spawning new entities as the mouse moves and then setting their alpha level in a tween down to zero. Once they reach zero alpha, remove the entity from memory.
This requires multiple drawing and will slow down rendering if you crank it up too much. Obviously the two-canvas approach is the simplest and cheapest from a render performance perspective but it doesn't allow you to control other features like making the "particles" move erratically or apply physics to them!

Categories

Resources