How to drawImage() and replace old contents with new? - javascript

In JS, I have some performance-critical code that essentially looks like this:
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.drawImage(otherImage);
I profile my code with Chrome and I find that the bottleneck is... clearRect.
Wait, what?
This is really stupid. I shouldn't even need to clearRect! I'm currently touching every pixel of context twice (once for clearRect, once for drawImage), which is a total waste. In theory, I should only need to do it once, to directly copy each pixel from otherImage to context.
How can I say something like "please, screw alpha blending and whatever, just replace the contents of context with whatever is in otherImage!"

You can ensure that no blending is done by calling context.globalCompositeOperation = 'source-over'; (default), which performs only alpha blending or context.globalCompositeOperation = 'copy'; which literally just copies the image on top of the existing content (see the examples here)

Related

Is it bad to reuse the same canvas context multiple times?

I am writing a simulator using the HTML5 canvas. I have made a helper class to contain information about the canvas. It looks like this so far:
class Canvas {
context;
width;
height;
constructor(HTMLCanvas) {
this.context = HTMLCanvas.getContext("2d");
this.width = HTMLCanvas.width;
this.height = HTMLCanvas.height;
}
clear() {
this.context.clearRect(0, 0, this.width, this.height);
}
}
What I am doing with this is every time I draw on the canvas, I reuse that same context. Is this a bad idea? Will it have unforeseen consequences?
It's fine to use the same canvas context for multiple purposes (in fact, preferred than destroying and re-creating the canvas just to clear and redraw it), but this seems somewhat antithetical to a class as shown here.
Classes are supposed to encapsulate data, but the setup here creates a false sense of encapsulation due to the HTMLCanvas parameter. In truth, if you make multiple instances of this class, they all share the same context, leading to a potentially confusing situation for the client of the class when multiple Canvas instances corrupt each other's state.
The benefit of hiding the parameters to the canvas clearRect function seems negligible. Sure, you may add more features to better motivate it, but there's still a potential data sharing problem as well as an unclear benefit/payoff for the whole enterprise even with the sharing fixed.
Here's a demonstration of what I mean:
class Canvas {
constructor(HTMLCanvas) {
this.context = HTMLCanvas.getContext("2d");
this.width = HTMLCanvas.width;
this.height = HTMLCanvas.height;
}
clear() {
this.context.clearRect(0, 0, this.width, this.height);
}
}
const canvas = document.createElement("canvas");
document.body.append(canvas);
const canvas1 = new Canvas(canvas);
const canvas2 = new Canvas(canvas);
canvas1.context.fillRect(0, 0, 30, 30);
canvas2.context.fillRect(30, 30, 30, 30);
It's hard to see the benefit here. As a client of your class, I've allocated 2 whole objects but they basically are views into the same canvas, leading to drawing on one to affect the other canvas. Calling clear would wipe both canvases and there's really no per-instance unique data to speak of. I'd have to explicitly provide two separate canvases to get normal usage and I've already dug myself into a hole of added verbosity and potential confusion before seeing any good come of it.
Another subtle point is that this class seems to conflate canvases and contexts. clear is really a context operation, so the Canvas class here seems to be headed towards a combination interface of a canvas and context rolled into one. Since most canvas apps mostly work with the context and only use canvas for height/width reference, this actually creates a nice separation of concerns that abstracts the DOM/canvas away from entity classes (ball, player, enemy, etc) that are responsible for drawing themselves on an arbitrary context without needing to know about the underlying DOM object the context is from.
Long story short:
Do you even need this? Canvases are already objects with a nice interface that may not need another layer of abstraction. If you want to abstract commonly-used drawing functions, maybe think about the entities in your app (say, a "player" entity) and create per-entity draw functions that use a context as a parameter.
If you do feel your design needs it, consider making the canvas itself a "private" member that isn't injected into the constructor. Instead, perhaps pass in a parent DOM element that the constructor will attach the canvas to.
Writing abstractions on the DOM without a framework like React is tricky!
Simply put: no, that's absolutely fine.

HTML 5 canvas clip very costly

I have serious performance problems using canvas clip() with chrome.
I have made a test case to illustrate.
Even in a simple case like this the red rectangle blinks as if it takes too much time to redraw, and a CPU profiling shows the clip() method takes about 10% of the CPU.
In my real program, it get to 16% and keep increasing each frame until the canvas almost freezes the browser..
Is there something wrong in my use of clip ?
Thank you for any suggestions,
Regards.
Cause
Insert a beginPath() as rect() adds to the path unlike fillRect()/strokeRect(). What happens here is that the rectangles are accumulating eventually slowing the clipping down over time.
This in combination with using setInterval, which is not able to synchronize with monitor/screen refreshes, worsens the problem.
To elaborate on the latter:
Using setInterval()/setTimeout() can cause tearing which happens when the draw is in the "middle" of its operation not fully completed and a screen refresh occur.
setInterval can only take integer values and you would need for 16.67 ms to synchronize frame-wise (#60Hz). Even if setInterval could take floats it would not synchronize the timing with the monitor timing as the timer mechanism isn't bound to monitor at all.
To solve this always use requestAnimationFrame to synchronize drawings with screen updates. This is directly linked to monitor refreshes and is a more low-level and efficient implementation than the other, and is made for this purpose, hence the name.
Solution embedding both fixes above
See modified bin here.
The code for future visitors:
function draw() {
context.fillStyle = '#000';
context.fillRect(0, 0, width, height);
context.save();
context.beginPath(); /// add a beginPath here
context.rect(0, 0, 100, 100);
context.clip();
context.fillStyle = '#ff0000';
context.fillRect(0, 0, 200, 200);
context.restore();
requestAnimationFrame(draw); /// use rAF here
}
canvas.width = width;
canvas.height = height;
canvas.style.width = width+'px';
canvas.style.height = height+'px';
requestAnimationFrame(draw); /// start loop
PS: If you need to stop the loop inject a condition to run rAF all inside the loop, ie:
if (isPlaying) requestAnimationFrame(draw);
There is BTW no need for closePath() as this will be done implicit for you as by the specs. You can add it if you want, but calling clip() (or fill() etc.) will do this for you (specs is addressing browser implementers):
Open subpaths must be implicitly closed when computing the clipping region

Canvas draw calls are rendering out of sequence

I have the following code for writing draw calls to a "back buffer" canvas, then placing those in a main canvas using drawImage. This is for optimization purposes and to ensure all images get placed in sequence.
Before placing the buffer canvas on top of the main one, I'm using fillRect to create a dark-blue background on the main canvas.
However, the blue background is rendering after the sprites. This is unexpected, as I am making its fillRect call first.
Here is my code:
render: function() {
this.buffer.clearRect(0,0,this.w,this.h);
this.context.fillStyle = "#000044";
this.context.fillRect(0,0,this.w,this.h);
for (var i in this.renderQueue) {
for (var ii = 0; ii < this.renderQueue[i].length; ii++) {
sprite = this.renderQueue[i][ii];
// Draw it!
this.buffer.fillStyle = "green";
this.buffer.fillRect(sprite.x, sprite.y, sprite.w, sprite.h);
}
}
this.context.drawImage(this.bufferCanvas,0,0);
}
This also happens when I use fillRect on the buffer canvas, instead of the main one.
Changing the globalCompositeOperation between 'source-over' and 'destination-over' (for both contexts) does nothing to change this.
Paradoxically, if I instead place the blue fillRect inside the nested for loops with the other draw calls, it works as expected...
Thanks in advance!
Addenum: Changing the composite operation does behave as expected, but not for remedying this specific issue. Sorry for the ambiguity.
There's a lot that's suspect here.
First off double buffering a canvas does nothing but hurt performance by adding complication, all browsers do double buffering automatically, so if that's your goal here you shouldn't be drawing to a buffer.
Here's an example of why you don't need double buffering: http://jsfiddle.net/simonsarris/XzAjv/
So getting to the meat of the matter, lines of javascript inside a discrete function don't simply run out of order. Something else is wrong here.
Setting a breakpoint on the drawImage would solve this pretty much instantly, so if you aren't familiar with firebug or chrome developer tools I'd highly recommend giving them a look.
I'm guessing that the "blue" you're seeing is actually the only thing drawn to your "buffer" canvas and perhaps this.buffer is not actually the buffer context.
Another possibility is that this.w and this.h are accidentally very small, so that your initial clearRect and fillRect at the start of the method are doing nothing.
In any case speculation is nowhere near as good as opening up developer tools and actually looking at what's happening.
Generally speaking if you need things to be in order use an array not an object. Iterating over an object is not guaranteed to be in any particular order.
Use an array and for (var i=0; i

Html5 Canvas method isPointInPath determines only the last object

An example here.
var context=document.getElementById("canvas").getContext("2d");
//Red Box
context.beginPath();
context.fillStyle="Red";
context.rect(10,10,50,50);
context.fill();
//Pink circle
context.beginPath();
context.lineWidth="3";
context.fillStyle="Pink";
context.arc(250,250,50,0,Math.PI*2,false);
context.fill();
context.stroke();
context.font="1.2em Verdana";
context.fillStyle="Black";
context.fillText(context.isPointInPath(35,35),35,35);
context.fillText(context.isPointInPath(250,250),250,250);
If you write without beginPath all objects detected.
How to identify objects on the canvas or to omit beginPath?
If you want to use that function you need to rebuild the path every time you want to do the test (just don't call fill or stroke).
What I do normally instead is using my own point-in-polygon test function or my own spatial data structure if there are a lot of objects and speed is important.
Note that a canvas is just a bitmap and it doesn't store the commands you use to draw on it. That is why it cannot do the check after drawing a shape and you can test only the current path.
Once you call beginPath the previous path geometry is discarded and what you have are only the affected pixels if you called fill or stroke.
May be for your case it may make sense to check the color of the canvas pixel...
I've just read that a new addition to the canvas specification is Path() objects. Presumably these could be stored and subsequently tested and/or replayed. Could be useful. If I've understood the spec correctly.
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#path-objects

html5 canvas - Saving paths or clip areas to reuse

I'm currently implementing a 2d deformable terrain effect in a game I'm working on and its going alright but I can envision it becoming a performance hog very fast as I start to add more layers to the effect.
Now what I'm looking for is a way to possibly save a path, or clipping mask or similar instead of having to store each point of the path in the terrain that i need to draw through each frame. And as I add more layers I will have to iterate over the path more and more which could contain thousands of points.
Some very simple code to demonstrate what I'm currently doing
for (var i = 0; i < aMousePoints.length; i++)
{
cRenderContext.save();
cRenderContext.beginPath();
var cMousePoint = aMousePoints[i];
cRenderContext.arc(cMousePoint.x, cMousePoint.y, 30, 0, 2 * Math.PI, false);
cRenderContext.clip();
cRenderContext.drawImage(cImg, 0, 0);
cRenderContext.closePath();
cRenderContext.restore();
}
Basically I'm after an effecient way to draw my clipping mask for my image over and over each frame
Notice how your clipping region stays exactly the same except for its x/y location. This is a big plus.
The clipping region is one of the things that is saved and restored with context.save() and context.restore() so it is possible to save it that way (in other words defining it only once). When you want to place it, you will use ctx.translate() instead of arc's x,y.
But it is probably more efficient to do it a second way:
Have an in-memory canvas (never added to the DOM or shown on the page) that is solely for containing the clipping region and is the size of the clipping region
Apply the clipping region to this in-memory canvas, and then draw the image onto this canvas.
Then use drawImage with the in-memory canvas onto your game context. In other words: cRenderContext.drawImage(in-memory-canvas, x, y); where x and y are the appropriate location.
So this way the clipping region always stays in the same place and is only ever drawn once. The image is moved on the clipping-canvas and then drawn to look correct, and then the in-memory canvas is drawn to your main canvas. It should be much faster that way, as calls to drawImage are far faster than creating and drawing paths.
As a separate performance consideration, don't call save and restore unless you have to. They do take time and they are unnecessary in your loop above.
If your code is open-source, let me know and I'll take a look at it performance-wise in general if you want.
Why not have one canvas for the foreground and one canvas for the background? Like the following demo
Foreground/Background Demo (I may of went a little overboard making the demo :? I love messing with JS/canvas.
But basically the foreground canvas is transparent besides the content, so it acts like a mask over the background canvas.
It looks like it is now possible with a new path2D object.
The new Path2D API (available from Firefox 31+) lets you store paths, which simplifies your canvas drawing code and makes it run faster. The constructor provides three ways to create a Path2D object:
new Path2D(); // empty path object
new Path2D(path); // copy from another path
new Path2D(d); // path from from SVG path data
The third version, which takes SVG path data to construct, is especially handy. You can now re-use your SVG paths to draw the same shapes directly on a canvas as well:
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
Information is taken from Mozilla official site.

Categories

Resources