<canvas> diamond/glass-like effect - javascript

I am working on a canvas animation, and one of the images is supposed to be a diamond.
Right now, I got as far as this:
ctx[0].beginPath();
ctx[0].moveTo(0,-80);
ctx[0].lineTo(-60,-130);
ctx[0].lineTo(-36,-160);
ctx[0].lineTo(36,-160);
ctx[0].lineTo(60,-130);
ctx[0].closePath();
ctx[0].fillStyle = "rgba(175,175,255,0.7)";
ctx[0].fill();
which draws a plain, light blue translucid diamond shape.
This is far too simple, but I'm having serious problems with the "color". I'm guessing something glass-like should do the trick, but I haven't found anything useful so far. I can add as many lines as needed, if it helps, but the color is my main problem.
This'll be pre-rendered, so long, complex code is not much of a problem. I'd rather not use images, though.
To sum up: I need a glass-ish effect for canvas. Any ideas?

I think what you are looking for in glass (or, presumably, diamond) is that it is not entirely transparent or flat. Instead, it reflects its surroundings and very slightly distorts its background. You can give the appearance of a reflection by means of a radial gradient. The distortion, however, is trickier. You could move and scale every pixel behind the object, but that would be incredibly difficult to implement, not to mention grindingly slow. Alternatively, you could implement a very fine, rapidly shifting gradient, which would give the appearance of a distortion of the pixels underneath, even though none is actually taking place.
Here is an implementation of a pane of glass with reflection and distortion.
<html>
<canvas id="canvas" style="position:fixed;">
</canvas>
<script type="text/javascript">
document.getElementById("canvas").height=window.innerHeight;
document.getElementById("canvas").width=window.innerWidth;
ctx=document.getElementById("canvas").getContext("2d");
textWidth=ctx.measureText("Hello World! ");
textWidth=Math.ceil(textWidth.width);
ctx.lineWidth=3;
targetWidth=Math.floor(window.innerWidth/textWidth)*textWidth;
for(i=0;i<500;i++)
{
ctx.fillText("Hello World! ",((i*textWidth)%(targetWidth)),(16*Math.floor((i+1)*textWidth/window.innerWidth)+16));
}
var glass = ctx.createRadialGradient(80,110,0,100,140,100);
for(i=0;i<=100;i++)
{
redComponent=Math.round(210-(i%11));
greenComponent=Math.round(245-(i%7));
blueComponent=Math.round(255-(i%5));
opacity=Math.round(((i%3)+1)*Math.sin(i/200*Math.PI)*1000)/3000;
glass.addColorStop(i/100,"rgba("+redComponent+","+greenComponent+","+blueComponent+","+opacity+")");
}
glass.addColorStop(1,"rgba(0,0,0,0)")
ctx.fillStyle=glass;
ctx.beginPath();
ctx.translate(100,0);
ctx.moveTo(100,100);
ctx.lineTo(187,150);
ctx.lineTo(137,237);
ctx.lineTo(50,187);
ctx.lineTo(100,100);
ctx.closePath;
ctx.fill();
ctx.stroke();
</script>
</html>
And the result is:

Related

Javascript & Canvas: Draw and delete lines to create a "breathing" circle

I would like to create an element, that shows a red circle. Once the user clicks on it, she can record her voice. In order to show the LIVE mode, I'd like to make the circle "breath" according to the incoming frequencies.
I'm experimenting with a <canvas> element. That means it creates a circle that gets bigger and smaller, depending on the variable arcrad. However, the lines are being drawn correctly, but they do not disappear afterwards. I tried to apply .clip() but can't get it to work...
if (arcrad <= 10) arcrad = 10;
analyserContext.beginPath();
analyserContext.arc(100,120,arcrad,0,2*Math.PI);
analyserContext.closePath();
analyserContext.lineWidth = 2;
analyserContext.strokeStyle = 'red';
analyserContext.stroke();
Any ideas - or completely different strategies for this use case?
Canvas will overdraw by default. For your animation you’ll need to clean the canvas at the start of each frame. Use something the following at the start of your drawing function:
analyserContext.clearRect(0,0,200,200);
assuming your canvas is 200 pixels wide and high. It’s worth pointing out that sometimes you don’t want to completely clear the animation field every frame. For example, if you were to draw a semi transparent rectangle over the frame at the beginning (instead of clearing it) then you’d end up with a basic ‘bullet time’ style effect.
It's a normal behavior. Once something it's drawn on the canvas, it's there forever. You have to think like if you were painting something: what has been done cannot be undone.
Luckily, you still have solutions:
1) redraw another circle on top of the first one with the background color. It's really not the recommend way, but it still can be useful
2) use clearRect method (see How to clear the canvas for redrawing)
There are numerous ways to clear a canvas pre drawing to create animation:
How to clear the canvas for redrawing
simplest in my mind:
canvas.width=canvas.width;
though can equally use clearRect (which is actually quicker and won't reset the entire canvas if that is an issue regarding transforms etc!) over the region or whole canvas.
Get the likes of:
http://jsfiddle.net/dw17jxee/

Create a bevel effect on non-rectangular shapes in 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>

Why doesn't moveTo(0, 0); lineTo(X, X); draw a 45° line?

I spent a lot of time today trying to do some simple canvas work (which I haven't played with in quite a while), but lines weren't drawing to the right places. It turns out that it was JavaScript and/or Canvas that was screwy, not my math. Given a 2D canvas context, the following:
context.moveTo(0, 0);
context.lineTo(50, 50);
context.stroke();
…draws a line that seems to be at 30° or so, not 45° as expected. Here's a jsFiddle.
The only way that this makes sense is if the canvas/context is using non-square pixels… Is that really the case? Whose bright idea might that be? Is there a way to force the canvas to use square pixels instead to make my math easier?
Sounds like you're resizing the canvas with CSS.
Resizing in CSS actually does result in stretched drawings.
If you need to resize, this will not "stretch the pixels":
var canvas=document.getElementById("myCanvas");
canvas.width=500;
canvas.height=300;
markE has it entirely right. In your example, you've set up a canvas in the HTML which will have default dimensions of 300px by 150px. When you then set the dimensions via CSS, the canvas gets 'stretched' rather than resized. An alternative to markE's solution is to set up the canvas with explicit dimensions:
<canvas id="the-canvas" width="300" height="300"></canvas>

Canvas - Fill a rectangle in all areas that are fully transparent

I'm writing a simple 2D game engine using the HTML5 canvas. I've come to adding a lighting engine. Each light source has a radius value and an intensity value (0-1, eg 1 would be very bright). There's also an ambient light value that is used to light everything else in the world that isn't near a light source (0-1, eg 0.1 would be moonlight). The process of lighting is done on a separate canvas above the main canvas:
For each light source, a radial gradient is drawn at that position with the same radius as the light source. The gradient is given two stops: the center is black with an alpha of 1-intensity of the light and the end/edge is black with alpha of 1-ambient light value. That all works fine.
This is where it goes wrong :/ I need to fill the whole canvas with black with and alpha of 1-ambient light value and at the moment I do this by setting the context.globalCompositeOperation to source-out then fillRecting the whole canvas.
My code for this stuff is:
var amb = 'rgba(0,0,0,' + (1-f.ambientLight) + ')';
for(i in f.entities) {
var e = f.entities[i], p = f.toScreenPoint(e.position.x, e.position.y), radius = e.light.radius;
if(radius > 0) {
var g = cxLighting.createRadialGradient(p.x, p.y, 0, p.x, p.y, radius);
g.addColorStop(0, 'rgba(0,0,0,' + (1-e.light.intensity) + ')');
g.addColorStop(1, amb);
cxLighting.fillStyle = g;
cxLighting.beginPath();
cxLighting.arc(p.x, p.y, radius, 0, Math.PI*2, true);
cxLighting.closePath();
cxLighting.fill();
}
}
//Ambient light
cxLighting.globalCompositeOperation = 'source-out';
cxLighting.fillStyle = amb;
cxLighting.fillRect(0, 0, f.width, f.height);
cxLighting.globalCompositeOperation = 'source-over';
However instead of getting what I wan't out of the engine (left) I get a kind of reversed gradient (right). I think this is because when I draw the rectangle with the source-out composite operation it affects the colours of the gradient itself because they are semi-transparent.
Is there a way to do this differently or better? Using clipping maybe, or drawing the rect over everything first?
Also, I modified the Mozila Dev Centre's example on composting to replicate what I need to do and none of the composite modes seemed to work, check that out if it would help.
Thanks very much, any answer would be great :)
One trivial way would be to use imageData but that would be painfully slow. It's an option, but not a good one for a game engine.
Another way would be to think of the ambient light and the light-source as if they were one path. That would make it very easy to do:
http://jsfiddle.net/HADky/
Or see it with an image behind: http://jsfiddle.net/HADky/10/
The thing you're taking advantage of here is the fact that any intersection of a path on canvas is always only unioned and never compounded. So you're using a single gradient brush to draw the whole thing.
But it gets a bit trickier than that if there's more than one light-source. I'm not too sure how to cover that in an efficient way, especially if you plan for two light-sources to intersect.
What you should probably do instead is devise an alpha channel instead of this overlay thing, but I can't currently think of a good way to get it to work. I'll revisit this if I think of anything else.
EDIT: Hey! So I've done a bit of thinking and came up with a good solution.
What you need to do is draw a sort of alpha channel, where the dark spots mark the places where you want light to be. So if you had three light sources it would look like this:
Then you want to set the fill style to your ambient color and set the globalCompositeOperation to xor and xor the whole thing.
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0,0,500,500);
That will leave you with the "opposite" image except the transparent parts will be correctly ambient:
Here's a working example of the code:
http://jsfiddle.net/a2Age/
Extra optimization: You can actually achieve the effect without using any paths at all, by simply drawing the exact same radial gradients onto rects instead of circular paths:
http://jsfiddle.net/a2Age/2/
Hope that helps!
Just an idea, but since you're getting the opposite effect you're going for from your gradient, have you tried reversing the gradient?

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