Implementing Layers in HTML5 Canvas - javascript

I am about to implement Photoshop-like Layers in HTML5 Canvas. Currently I have two ideas. The first and maybe the simpler idea is to have a Canvas element for each layer like:
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
This way when you draw to a layer -- it actually goes to that "layer". Layers with transparent positions can be seen through to below layers (Canvases). Layer stacking is controlled with z-index property.
The second idea is to use a single Canvas element and implement some logic to handle layers like in this case:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
window.addEventListener('load', function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var order = 0;
function drawLayer1() {
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
}
function drawLayer2() {
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);
}
function draw() {
ctx.clearRect(0, 0, 256, 256);
if (order === 0) {
drawLayer1();
drawLayer2();
}
else {
drawLayer2();
drawLayer1();
}
}
setInterval(draw, 250);
setInterval(function() {
order = 1 - order;
}, 200);
}, false);
</script>
</head>
<body>
<canvas id="canvas" width="256px" height="256px"></canvas>
</body>
</html>
In the above code the two layers will change stacking order every 200msec.
So, the question is that which way would be the best way? What are the pros and cons of both approaches?

If you want to use a single canvas element and have multiple layers inside it, you might want to look at my library:
https://github.com/ant512/CanvasLayers
It uses a damaged rect system to reduce the amount of repainting done every time the canvas changes, so not only do you get layers (which can be nested), but you also get optimised redraws.

Using multiple canvases should be faster, because the canvas gets drawn off-screen and then just blitted to the screen by the browser. You put the burden of switching layers on the browser, which just has to move some rectangles of graphics data around.
If you do the layering yourself, you have more control, but the burden is on the JS and the JS engine to do all the work. I would avoid this if I had a choice, but if you're going for layer effects that work on the underlying layers, this might be your only choice.

Setting the container div relative ought to have prevented that layer-overwrite issue. Try setting the position on the "occluded text" - e.g. if it's currently absolue it will obvious go in the same region as the top left of the relative stuff.
And it's probably obvious but, by the order of divs in the html you can eliminate the use of the z axis. If you want your stuff to be generic (and for other developers too), use the z axis but store a baseline to which you add your layer indices (so that baseline can be tweaked when using other code using z-axis in a problematic way).

Using jCanvas,see its layer API: http://projects.calebevans.me/jcanvas/docs/layerAPI/

Related

How efficient is layering multiple canvases?

I've been wondering if layering canvases is actually an efficient means of increasing performance on a game.
E.g, 2 or 3 canvases of the same size on top of each other.
It's difficult to find results of testing from this, but from what I understand about drawing, if the layer on top of the others is changed, the browser would have to repaint the area to pixels anyway.
Furthermore if those canvases all had to move at the same time, you've multiplied the amount of pixels that would need to be calculated.
I was wondering how the browser handles painting canvases beneath the others if an element above changes.
Is it more efficient due to not having to call the canvas methods themselves again? Wouldn't they still need painting?
My experience is anecdotal, so you really need to test your idea to see if its better.
You're probably going to get the best performance by creating offscreen (in memory) canvases and compositing the different layers of your game with those into a single visible canvas where the game is viewed.
I'm not sure if this will be very helpful, but I'll tell you anyway.
Let's say that you've got 2 canvases and you want your game to switch between them under certain condition. This might be possible by using some HTML, CSS and JavaScript.
<canvas id="canvas1" width=500 height=500 style="border: 2px solid #00F"></canvas>
<canvas id="canvas2" width=500 height=500 style="border: 2px solid #F00"></canvas>
<button id="switcher" onclick="switchcanvas=true">Click here to switch the canvas!</button>
<style>
#canvas1 {
left: 0%;
right: 0%;
display: block;
}
#canvas2 {
left: 0%;
right: 0%;
display: none;
}
</style>
<script>
var switchcanvas = false;
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
function update() {
if (switchcanvas) {
if (canvas1.style.display === "block") {
canvas1.style.display = "none";
canvas2.style.display = "block";
switchcanvas = false; //if you set this to true or remove it the canvases will keep switching.
} else {
canvas1.style.display = "block";
canvas2.style.display = "none";
switchcanvas = false;
}
}
}
update();
setInterval(update, 1); //function update will update every ms, you can change that though.
</script>
I tested it and it worked. And yes, it should improve your game's performance since each canvas can have its own context.
I don't know if this is correct, but in my experience if you use two Canvases with the same frame (refresh) rate the performance improvement would be minimal, if there is any at all. You would get an improvement if the refesh rate for Canvas 1 is lower than Canvas 2. A good use case for this would be a grid and a moving object. If the grid doesn't change, you don't have to redraw it. This does not apply for the moving object, because you want to see it moving.

Canvas transform not behaving as expected

Suppose I have a 400x200 canvas.
I want to work in a coordinate system where (0, 0) is in the exact middle of the canvas and positive y means up and positive x means right.
So, I set the transform as follows:
var ctx = document.getElementById("canvas").getContext("2d");
ctx.setTransform(1, 0, 0, -1, 200, 100);
ctx.fillRect(-20, -20, 40, 40);
<canvas id="canvas" style="width: 400px; height: 200px"></canvas>
So when I fill the rectangle as in the snippet above, I would expect to see a square centered in the middle of the canvas element. However, when running the above snippet (in latest Chrome) the square is demonstrably not centered. Why is this? Am I misunderstanding something about the transform matrix? If so, how can I achieve my goal?
The size for the canvas element isn't set properly which means the canvas defaults to 150 pixels in height, which is then stretched out using CSS. This gives the illusion of the object being offset.
To properly set canvas size use its attributes instead of CSS:
<canvas id="canvas" width=400 height=200></canvas>
Also be aware of that the Y-axis is now flipped upside-down so any text and images are drawn upside-down as well. These will need local transformation to be drawn correctly.
The problem seems to be that the dimensions in style are ignored and default to the standard dimensions 300×150. So set them properly with
<canvas id="canvas" width="400" height="200"></canvas>
Independent of that, it can be a good idea to not rely on hard-coded dimensions, especially if you are going to use a local coordinate system anyway.
My snippet modifications:
Use the actual canvas dimensions and scale so that the local coordinates still at least contain the square (-100,100)×(-100,100).
Add a coordinate cross before setting the transformation, that shows that even when the canvas is "wrong", the square is at the coordinate origin.
After the transformation, add a circle at positive y position to show that "up" is really up.
var cnv = document.getElementById("canvas");
var w = cnv.width, h = cnv.height;
var ctx = cnv.getContext("2d");
ctx.beginPath();
ctx.moveTo(0,h/2); ctx.lineTo(w,h/2);
ctx.moveTo(w/2,0); ctx.lineTo(w/2,h);
ctx.closePath(); ctx.stroke();
var scale = Math.min(w,h)/200.0;
ctx.setTransform(scale, 0, 0, -scale, w/2, h/2);
ctx.moveTo(0,50); ctx.arc(0,50,10,0,2*Math.PI); ctx.stroke();
ctx.fillRect(-20, -20, 40, 40);
<canvas id="canvas" width="450" height="250"></canvas>

jQuery resize window background [duplicate]

What I'd like to do is draw my graphics on a buffer and then be able to copy it as is to the canvas so I can do animation and avoid flickering. But I couldn't find this option. Anyone know how I can go about this?
A very simple method is to have two canvas-elements at the same screen location and set visibility for the buffer that you need to show. Draw on the hidden and flip when you are done.
Some code:
CSS:
canvas { border: 2px solid #000; position:absolute; top:0;left:0;
visibility: hidden; }
Flipping in JS:
Buffers[1-DrawingBuffer].style.visibility='hidden';
Buffers[DrawingBuffer].style.visibility='visible';
DrawingBuffer=1-DrawingBuffer;
In this code the array 'Buffers[]' holds both canvas-objects. So when you want to start drawing you still need to get the context:
var context = Buffers[DrawingBuffer].getContext('2d');
The following helpful link, in addition to showing examples and advantages of using double buffering, shows several other performance tips for using the html5 canvas element. It includes links to jsPerf tests, which aggregate test results across browsers into a Browserscope database. This ensures that the performance tips are verified.
https://web.dev/canvas-performance/
For your convenience, I have included a minimal example of effective double buffering as described in the article.
// canvas element in DOM
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d');
// buffer canvas
var canvas2 = document.createElement('canvas');
canvas2.width = 150;
canvas2.height = 150;
var context2 = canvas2.getContext('2d');
// create something on the canvas
context2.beginPath();
context2.moveTo(10,10);
context2.lineTo(10,30);
context2.stroke();
//render the buffered canvas onto the original canvas element
context1.drawImage(canvas2, 0, 0);
Browsers I've tested all handle this buffering for you by not repainting the canvas until the code that draws your frame has completed. See also the WHATWG mailing list: http://www.mail-archive.com/whatwg#lists.whatwg.org/msg19969.html
You could always do
var canvas2 = document.createElement("canvas");
and not append it to the DOM at all.
Just saying since you guys seem so obsessed with display:none;
it just seems cleaner to me and mimicks the idea of double buffering way more accurately than just having an awkwardly invisible canvas.
More than two years later:
There is no need for 'manually' implement double buffering. Mr. Geary wrote about this in his book "HTML5 Canvas".
To effectively reduce flicker use requestAnimationFrame()!
For the unbelievers, here's some flickering code. Note that I'm explicitly clearing to erase the previous circle.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw_ball(ball) {
ctx.clearRect(0, 0, 400, 400);
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.arc(ball.x, ball.y, 30, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
var deltat = 1;
var ball = {};
ball.y = 0;
ball.x = 200;
ball.vy = 0;
ball.vx = 10;
ball.ay = 9.8;
ball.ax = 0.1;
function compute_position() {
if (ball.y > 370 && ball.vy > 0) {
ball.vy = -ball.vy * 84 / 86;
}
if (ball.x < 30) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
} else if (ball.x > 370) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
}
ball.ax = ball.ax / 2;
ball.vx = ball.vx * 185 / 186;
ball.y = ball.y + ball.vy * deltat + ball.ay * deltat * deltat / 2
ball.x = ball.x + ball.vx * deltat + ball.ax * deltat * deltat / 2
ball.vy = ball.vy + ball.ay * deltat
ball.vx = ball.vx + ball.ax * deltat
draw_ball(ball);
}
setInterval(compute_position, 40);
<!DOCTYPE html>
<html>
<head><title>Basketball</title></head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
</body></html>
Josh asked (a while back) about how the browser knows "when the drawing process ends" so as to avoid flicker. I would have commented directly to his post but my rep isn't high enough. Also this is just my opinion. I don't have facts to back it up, but I feel fairly confident about it and it may be helpful to others reading this in the future.
I'm guessing the browser doesn't "know" when you're done drawing. But just like most javascript, as long as your code runs without relinquishing control to the browser, the browser is essentially locked up and won't/can't update/respond to its UI. I'm guessing that if you clear the canvas and draw your entire frame without relinquishing control to the browser, it won't actually draw your canvas until you're done.
If you set up a situation where your rendering spans multiple setTimeout/setInterval/requestAnimationFrame calls, where you clear the canvas in one call and draw elements on your canvas in the next several calls, repeating the cycle (for example) every 5 calls, I'd be willing to bet you'd see flicker since the canvas would be updated after each call.
That said, I'm not sure I'd trust that. We're already at the point that javascript is compiled down to native machine code before execution (at least that's what Chrome's V8 engine does from what I understand). I wouldn't be surprised if it wasn't too long before browsers started running their javascript in a separate thread from the UI and synchronizing any access to UI elements allowing the UI to update/respond during javascript execution that wasn't accessing UI. When/if that happens (and I understand there are many hurdles that would have to be overcome, such as event handlers kicking off while you're still running other code), we'll probably see flicker on canvas animation that aren't using some kind of double-buffering.
Personally, I love the idea of two canvas elements positioned over top of each other and alternating which is shown/drawn on each frame. Fairly unintrusive and probably pretty easily added to an existing application with a few lines of code.
There is no flickering in web browsers! They already use dbl buffering for their rendering. Js engine will make all your rendering before showing it. Also, context save and restore only stack transformational matrix data and such, not the canvas content itself.
So, you do not need or want dbl buffering!
Rather than rolling your own, you're probably going to get the best mileage by using an existing library for creating clean and flicker-free JavaScript animation:
Here's a popular one: http://processingjs.org
you need 2 canvas: (notice the css z-index and position:absolute)
<canvas id="layer1" width="760" height="600" style=" position:absolute; top:0;left:0;
visibility: visible; z-index: 0; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<canvas id="layer2" width="760" height="600" style="position:absolute; top:0;left:0;
visibility: visible; z-index: 1; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
you can notice that the first canvas is visible and the second it's hidden the idea it's to draw on the hidden after that we will hide the visible and make the hidden canvas visible. when it's hidden 'clear hidden canvas
<script type="text/javascript">
var buff=new Array(2);
buff[0]=document.getElementById("layer1");
buff[1]=document.getElementById("layer2");
ctx[0]=buff[0].getContext("2d");
ctx[1]=buff[1].getContext("2d");
var current=0;
// draw the canvas (ctx[ current ]);
buff[1- current ].style.visibility='hidden';
buff[ current ].style.visibility='visible';
ctx[1-current].clearRect(0,0,760,600);
current =1-current;
In most situations, you don't need to do this, the browser implements this for you. But not always useful!
You still have to implement this when your drawing is very complicated.
Most of the screen update rate is about 60Hz, it means the screen updates per 16ms. The browser's update rate may near this number. If your shape need 100ms to be completed, you'll see a uncompleted shape. So you can implement double buffering in this situation.
I have made a test: Clear a rect, wait for some time, then fill with some color. If I set the time to 10ms, I won't see flickering. But if I set it to 20ms, the flickering happens.
Opera 9.10 is very slow and shows the drawing process. If you want to see a browser not use double buffering, try Opera 9.10 out.
Some people suggested that browsers are somehow determining when the drawing process ends but can you explain how that can work? I haven't noticed any obvious flicker in Firefox, Chrome, or IE9 even when the drawing is slow so it seems like that is what they are doing but how that is accomplished is a mystery to me. How would the browser ever know that it is refreshing the display just before more drawing instructions are to be executed? Do you think they just time it so if an interval of more than 5ms or so goes by without executing a canvas drawing instruction, it assumes it can safely swap buffers?
This is an example of double buffering with image data.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 Canvas Demo of PixelBuffer</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id="buffer1"></canvas>
<script>
const canvas = document.getElementById('buffer1');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(100, 100);
const pixelBuffer = new Uint32Array(imageData.data.buffer);
for (let i = 0; i < pixelBuffer.length; i++) {
pixelBuffer[i] = 0xFF0000FF;
}
ctx.putImageData(imageData, 0, 0);
</script>
</body>
</html>

how to copy another canvas's data on the canvas with getContext('webgl')?

I have a canvas for showing medical image and I have another canvas for annotating images by draw shape or line.
when I draw a line on canvas#2 I want to copy drawn line on canvas#1 something like this:
var context = canvas1.getContext('2d');
context.drawImage(canvas2,0,0);
but because my canvas#1 has a getContext('webgl') I can't do that.
I mean how to simulate
drawImage() for getcontext('webgl')?
I really appreciate any help or advice.
Regards;
Zohreh
Well, you could just use the toDataURL method of the webgl canvas to convert it into an image. Then draw it on the 2d context.
ctx2D.drawImage(webGLCanvas.toDataURL("image/png"), 0, 0);
In this case I believe you might have to set the preserveDrawingBuffer propertie of the webgl canvas to true when initializing it.
...getContext("webgl", {preserveDrawingBuffer: true});
I had a similar problem with Emscripten, needed to copy WebGL canvas to another empty canvas.
I used this code but got empty screen.
var sourceCanvasWebGL = document.getElementById( "canvas" );
var destinationCanvas = document.getElementById( "canvasCopy" );
var destinationCanvasContext = destinationCanvas.getContext('2d');
destinationCanvasContext.drawImage(sourceCanvasWebGL, 0, 0);
After some googling (Saving canvas to image via canvas.toDataURL results in black rectangle) I figured out that because WebGL is using 2 buffers while drawing I am copying the old buffer with empty content.
Problem was solved by setting preserveDrawingBuffer: true in the code used to make WebGL drawing.
sourceCanvasWebGL.getContext("webgl2", { preserveDrawingBuffer: true })
P.S. As an alternative you could make an image copy right after rendering the canvas. If so you do not need preserveDrawingBuffer.
You could use a 2D canvas as the on-screen canvas, and draw the WebGL canvas to it when updating the WebGL canvas before drawing whatever annotations on.
drawWebGLStuff(webGLCtx);
// Copy image data of WebGL canvas to 2D canvas.
// This should be done right after WebGL rendering,
// unless preserveDrawingBuffer: true is passed during WebGL context creation,
// since WebGL will swap buffers by default, leaving no image in the drawing buffer,
// which is what we want to copy.
ctx2D.drawImage(webGLCanvas, 0, 0);
drawAnnotations(ctx2D);
(The annotations could be rendered each frame from a list of shapes or drawn to another offscreen canvas, which is then drawn to the main (on-screen) canvas similar to the WebGL canvas.)
Or you can simply layer the canvases on the page with absolute positioning and z-index:
<div style="position: relative;">
<canvas id="layer1" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="layer2" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>

Performance Issues KineticJS

I'm quite new to canvas so I need some input.
I wan't to create a library overview with its stocks and so I have to handle about 2k+ rectangles. The problem is, the performance on drag and on scale is not very good, the fps drop below 10. Thats plain ugly, so I would appreciate some input what to do better or in other ways toe improve the performance of my (basic) script.
http://jsfiddle.net/kHGvh/13/embedded/result/
http://jsfiddle.net/kHGvh/13/
I just tried the same thing using Fabric.js, out of curiosity.
I do see noticeably better performance — http://jsfiddle.net/M7n4p/
Note that I'm using experimental "group_rewrite" branch — https://github.com/kangax/fabric.js/branches
FWIW, here's the code used to create it (just to give you comparison with Kinetic.js).
Markup:
<canvas id="c" width="1200" height="600" style="border:1px solid #ccc"></canvas>
JS:
var canvas = new fabric.Canvas('c');
var rects = [ ];
for (var i = 1; i <= 47; i++) {
for (var j = 1; j <= 42; j++) {
var rect = new fabric.Rect({
left: i*28,
top: j*18,
width: 20,
height: 10,
fill: 'green'
});
rects.push(rect);
}
}
canvas.add(new fabric.Group(rects));
I'm having the same issues as well when creating a draggable grid. However, I think there is little one can do about it.
You could think about lessening the number of cells you have. 2k+ rectangles is at least 2k+ objects that track the shape on the canvas. with the drag event happening at least 10 frames per second, you have 20k+ calculations and object accesses per second! The shear number of shapes it causing problems.

Categories