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.
Related
Well, I fill ScreenBuffer:ImageData 480x360 and then want to draw it to the canvas 960x720. The task is to decrease the fillrate; the nowadays pixels are very small and we can make them bigger with some quality loss. I look for the operator with 2D-accelaration. But we can't write directly to js.html.Image and ImageData hasn't link to js.html.Image. I found an example for pure JS:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
However, it doesn't want to work in Haxe because there isn't 'zoom' element. And there is some information about restrictions in HTML at copying from one image to another.
Many thanks for answers!
The compiler writes "js.html.Element has no field getContext"
getElementById()'s return type is the generic js.html.Element class. Since in your case, you know you're dealing with a <canvas>, you can safely cast it to the more specific CanvasElement. This then lets you call its getContext() method:
var canvas:CanvasElement = cast js.Browser.document.getElementById('zoom');
var zoomctx = canvas.getContext('2d');
i'm currently developing a js app.
I'm not a js expert so i'm asking for tips.
My question is:
Is it good practice (performance-wise as well) to draw in a canvas with an array of context or it is better to have a single context with all the drawing inside?
Case 1:
for (i = 0; i < 8; i++) {
contextAr[i] = canvasAr.getContext('2d');
contextAr[i].beginPath();
contextAr[i].arc(canvasAr.width / 2, canvasAr.height / 2, ...
}
Case 2:
contextAr.beginPath();
contextAr.arc(...);
contextAr.moveTo(...);
contextAr.LineTo(...);
A simple example could be a circle divided in 4 quarters. Should i treat the draw with a single context or should i treat each pieces separately.
I think i'm not fully grasping the meaning of context though.
Is contextAr[i] = canvasAr.getContext('2d'); a redundant call?
I would not be surprise if contextAr[i] had identical elements.
Thank you.
It's rather pointless obtaining a 2D context several times from the same canvas element as the same context object will be returned.
From the standard (my emphasis) -
Return the same object as was returned the last time the method was
invoked with this same first argument.
So the common and best practice is to obtain a single reference to a context per canvas element and use that successively.
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
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.
I'm using the Canvas object with javascript. Just doing some tests to see how fast I can set pixels in a draw loop.
On mac, it works great in FF, safari, chrome. On windows, I get a flickering effect on FF and chrome. It looks like somehow the canvas implementation on windows is different than on mac for the different browsers? (not sure if that's true).
This is the basic code I'm using to do the drawing (taken from the article below - I've optimized the below to tighten the draw loop, it runs pretty smooth now):
var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvasData.width; x++) {
for (var y = 0; y < canvasData.height; y++) {
// Index of the pixel in the array
var idx = (x + y * canvas.width) * 4;
canvasData.data[idx + 0] = 0;
canvasData.data[idx + 1] = 255;
canvasData.data[idx + 2] = 0;
canvasData.data[idx + 3] = 255;
}
}
ctx.putImageData(canvasData, 0, 0);
again, browers on windows will flicker a bit. It looks like the canvas implementation is trying to clear the canvas to white before the next drawing operation takes place (this does not happen on mac). I'm wondering if there is a setting I can change in the Canvas object to modify that value (double-buffering, clear before draw, etc)?
This is the article I am using as reference:
http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
Thanks
I think it's fairly clear that browsers who implement the Canvas object use DIBS (device independent bitmaps). The fact that you have access to the pixelbuffer without having to lock the handle first is proof of this. And Direct2D has nothing to do with JS in a browser thats for sure. GDI is different since it uses DDBs (device dependent bitmaps, i.e allocated from video memory rather than conventional ram). All of this however has nothing to do with optimal JS rendering speed. I think writing the RGBA values as you do is probably the best way.
The crucial factor in the code above is the call to putImageData(). This is where browsers can differ in their implementation. Are you in fact writing directly to the DIB, and putImageData is simply a wrapper around InvalidateRect? Or are you in fact writing to a duplicate in memory, which in turn is copied into the canvas device context? If you use linux or mac then this is still a valid question. Although device contexts etc. are typically "windows" terms, most OS'es deal with handles or structures in pretty much the same way. But once again, we are at the mercy of the browser vendor.
I think the following can be said:
If you are drawing many pixels in one go, then writing directly to the pixelbuffer as you do is probably the best. It is faster to "bitblt" (copy) the pixelbuffer in one go after X number of operations. The reason for this is that the native graphics functions like FillRect also calls "invalidate rectangle" which tells the system that a portion if the screen needs a re-draw (refresh). So if you call 100 line commands, then 100 update's will be issued - slowing down the process. Unless (and this is the catch) you use the beginPath/EndPath methods as they should be used. Then it's a whole different ballgame.
It's here that the Begin/End path "system" comes into play, and also the Stroke/Outline commands. They allow you to execute X number of drawing operations within a single update. But a lot of people get this wrong and issue a redraw for each call to line/fillrect etc.
Also, have you tried creating an invisible canvas object, drawing to that, and then copying to a visible canvas? This could be faster (proper double-buffering).
The problem is with the way the browsers use the native graphics APIs on the different OSes. And even on the same OS, using different APIs (for example GDI vs. Direct2D in Windows) would also produce different results.