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.
Related
I'm working on a project which uses the marching squares algorithm. It outputs many polygons and this causes a performance drop when I draw it via the conventional p5.js way. I know from OpenGL and WebGL that there is VBO/VAO which let you draw much much quicker. Is there similar functionality in p5.js or canvas 2d? Or is there a better solution like direct use of WebGL?
I have a 1000x1000 voxel grid which generates a poly array through the marching squares algorithm.
I draw currently three of these polygon lists per frame.
I use p5.js to draw it every frame. One frame takes more than 500ms to draw.
To reduce it, I think the best solution would be to use some kind of VBO/VAO with p5.js or canvas 2d.
This the code to draw one polygon list with p5.js:
polys.forEach(function(poly : number[][]) {
g.beginShape();
poly.forEach(function(vert : number[]) {
var x = vert[0];
var y = vert[1];
g.vertex(x, y);
});
g.endShape();
});
I need to reduce the drawing time drastically to something like 50ms o less.
I'm still relatively new to working with the canvas tag. What I've done so far is draw an image to the canvas. My goal is to have a fake night/day animation that cycles repeatedly.
I've exhausted quite a few different avenues (SVG, CSS3 filters, etc) and think that canvas pixel manipulation is the best route in my case. I'm trying to:
Loop through all pixels in the image
Select a certain color range
Adjust to new color
Update the canvas
Here's the code I have so far:
function gameLoop(){
requestAnimationFrame(gameLoop);
////////////////////////////////////////////////////////////////
// LOOP PIXEL DATA - PIXEL'S RGBA IS STORED IN SEQUENTIAL ARRAYS
////////////////////////////////////////////////////////////////
for(var i=0; i<data.length; i+=4){
red=data[i+0];
green=data[i+1];
blue=data[i+2];
alpha=data[i+3];
// GET HUE BY CONVERTING TO HSL
var hsl=rgbToHsl(red, green, blue);
var hue=hsl.h*360;
// CHANGE SET COLORRANGE TO NEW COLORSHIFT
if(hue>colorRangeStart && hue<colorRangeEnd){
var newRgb=hslToRgb(hsl.h+colorShift, hsl.s, hsl.l);
data[i+0]=newRgb.r;
data[i+1]=newRgb.g;
data[i+2]=newRgb.b;
data[i+3]=255;
};
};
// UPDATE CANVAS
ctx.putImageData(imgData, 0, 0);
};
The code works and selects a hue ranges and shifts it once, but is incredibly laggy. The canvas dimensions are roughly 500x1024.
My questions:
Is it possible to improve performance?
Is there a better way to perform a defined hue shift animation?
Thanks!
It's hard to do this real-time using high quality HSL conversion. Been there done that, so I came up with a quantized approach which allow you to do this in real-time.
You can find the solution here (GPL3.0 licensed):
https://github.com/epistemex/FastHSL2RGB
Example of usage can be found here (MIT license) incl. demo:
https://github.com/epistemex/HueWheel
Apologies for referencing my own solutions here, but the inner workings (the how to's) is too extensive to present in a simple form here and both of these are free to use for anything..
The key points are in any case:
Quantize the range you want to use (don't use full 360 degrees and not floating points for lightness etc.)
Cache the values in a 3D array (initial setup using web workers or use rough values)
Quantize the input values so they fit in the range of the inner 3D array
Process the bitmap using these values
It is not accurate but good enough for animations (or previews which is what I wrote it for).
There are other techniques such as pre-caching the complete processed bitmap for key positions, then interpolate the colors between those instead. This, of course, requires much more memory but is a fast way.
Hope this helps!
I have a shape (a quarter circle) that I've created using the html canvas function:
moveTo
LineTo
QuadraticCurveTo
How do I go about exploding the shape into particles and then return them to form a circle?
I'm not going to write the code for you because it will take some time, and I'm sure you can find examples on the web, but I'll tell you the theory you need to know in order to make such a thing.
Create an in-memory canvas (using document.createElement('canvas')) that will never be seen on the page. This canvas must be at least as large as your object. I'm going to assume it is exactly as large as your object. We'll call this tempCanvas and it has tempCtx
Draw your object to tempCtx.
There will be some event that you didn't mention exactly but I'm sure you have in mind. Either you press a button or click on the object and it "explodes". For the sake of picking something I'll assume you want it to explode on click.
So to do the explosion:
Draw the object onto your normal context: ctx.drawImage(tempCanvas, x, y) so the user sees something
You're going to want to have an array of pixels for the location of each pixel in tempCanvas. So if tempCanvas is 20x30 you'll want an array of [20][30] to correspond.
You have to keep data for each of those pixels. Specifically, their starting point, which is easy, because pixel [2][4]'s starting point is (2,4)! And also their current location, which is identical to starting point at first but will change on each frame.
When the explosion event occurs keep track of the original mouse x and y position.
At this point for every single pixel you have a vector which means you have a direction. If you clicked in the middle of the object you'll want to save the mouse coordinates of (10,15) (see note 1). So now all of the pixels of the to-be-exploded image have their trajectory. There's a bit of math here that I'm taking for granted, but if you search separate topics either on SO or on the internet you'll find out how to find the slope/etc of these lines and continue them.
For every frame hereafter you must take each pixel [x][y] and use ctx.drawImage(tempCanvas, x, y, 1, 1, newX, newY, 1, 1) where x and y are the same as the pixel's [x][y] and the newX and newY are calculated using the vector and finding what the next point would be along its line.
The result will be each pixel of the image being drawn in a location that is slightly more away from the original click point. If you continue to do this frame after frame it will look as if the object has exploded.
That's the general idea, anyway. Let me know if any of it is unclear.
note 1: Most likely your normal canvas won't be the same size as the to-explode object. Maybe the object is placed at 100,100 so you really clicked on 110, 115 instead of 10,15. I'm omitting that offset just for the sake of simplicity.
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
I want to take an irregularly shaped section from an existing image and render it as a new image in Javascript using HTML5 canvases. So, only the data inside the polygon boundary will be copied. The approach I came up with involved:
Draw the polygon in a new canvas.
Create a mask using clip
Copy the data from the original canvas using getImageData (a rectangle)
Apply the data to the new canvas using putImageData
It didn't work, the entire rectangle (e.g. the stuff from the source outside the boundary) is still appearing. This question explains why:
"The spec says that putImageData will not be affected by clipping regions." Dang!
I also tried drawing the shape, setting context.globalCompositeOperation = "source-in", and then using putImageData. Same result: no mask applied. I suspect for a similar reason.
Any suggestions on how to accomplish this goal? Here's basic code for my work in progress, in case it's not clear what I'm trying to do. (Don't try too hard to debug this, it's cleaned up/extracted from code that uses a lot of functions that aren't here, just trying to show the logic).
// coords is the polygon data for the area I want
context = $('canvas')[0].getContext("2d");
context.save();
context.beginPath();
context.moveTo(coords[0], coords[1]);
for (i = 2; i < coords.length; i += 2) {
context.lineTo(coords[i], coords[i + 1]);
}
//context.closePath();
context.clip();
$img = $('#main_image');
copy_canvas = new_canvas($img); // just creates a new canvas matching dimensions of image
copy_ctx = copy.getContext("2d");
tempImage = new Image();
tempImage.src = $img.attr("src");
copy_ctx.drawImage(tempImage,0,0,tempImage.width,tempImage.height);
// returns array x,y,x,y with t/l and b/r corners for a polygon
corners = get_corners(coords)
var data = copy_ctx.getImageData(corners[0],corners[1],corners[2],corners[3]);
//context.globalCompositeOperation = "source-in";
context.putImageData(data,0,0);
context.restore();
dont use putImageData,
just make an extra in memory canvas with document.createElement to create the mask and apply that with a drawImage() and the globalCompositeOperation function (depending on the order you need to pick the right mode;
I do something similar here the code is here (mind the CasparKleijne.Canvas.GFX.Composite function)