So the issue that I'm having is that in developing an HTML5 canvas app I need to use a lot of transformations (i.e. translate, rotate, scale) and therefore a lot of calls being made to context.save() and context.restore(). The performance drops very quickly even with drawing very little (because the save() and restore() are being called as many times as possible in the loop). Is there an alternative to using these methods but still be able to use the transformations? Thank you!
Animation and Game performance tips.
Avoid save restore
Use setTransform as that will negate the need for save and restore.
There are many reasons that save an restore will slow things down and these are dependent on the current GPU && 2D context state. If you have the current fill and/or stroke styles set to a large pattern, or you have a complex font / gradient, or you are using filters (if available) then the save and restore process can take longer than rendering the image.
When writing for animations and games performance is everything, for me it is about sprite counts. The more sprites I can draw per frame (60th second) the more FX I can add, the more detailed the environment, and the better the game.
I leave the state open ended, that is I do not keep a detailed track of the current 2D context state. This way I never have to use save and restore.
ctx.setTransform rather than ctx.transform
Because the transforms functions transform, rotate, scale, translate multiply the current transform, they are seldom used, as i do not know what the transform state is.
To deal with the unknown I use setTransform that completely replaces the current transformation matrix. This also allows me to set the scale and translation in one call without needing to know what the current state is.
ctx.setTransform(scaleX,0,0,scaleY,posX,posY); // scale and translate in one call
I could also add the rotation but the javascript code to find the x,y axis vectors (the first 4 numbers in setTransform) is slower than rotate.
Sprites and rendering them
Below is an expanded sprite function. It draws a sprite from a sprite sheet, the sprite has x & y scale, position, and center, and as I always use alpha so set alpha as well
// image is the image. Must have an array of sprites
// image.sprites = [{x:0,y:0,w:10,h:10},{x:20,y:0,w:30,h:40},....]
// where the position and size of each sprite is kept
// spriteInd is the index of the sprite
// x,y position on sprite center
// cx,cy location of sprite center (I also have that in the sprite list for some situations)
// sx,sy x and y scales
// r rotation in radians
// a alpha value
function drawSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
On just an average machine you can render 1000 +sprites at full frame rate with that function. On Firefox (at time of writing) I am getting 2000+ for that function (sprites are randomly selected sprites from a 1024 by 2048 sprite sheet) max sprite size 256 * 256
But I have well over 15 such functions, each with the minimum functionality to do what I want. If it is never rotated, or scaled (ie for UI) then
function drawSprite(image, spriteInd, x, y, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(1,0,0,1,x,y); // set scale and position
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,0,0,w,h); // render the subimage
}
Or the simplest play sprite, particle, bullets, etc
function drawSprite(image, spriteInd, x, y,s,r,a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(s,0,0,s,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-w/2,-h/2,w,h); // render the subimage
}
if it is a background image
function drawSprite(image){
var s = Math.max(image.width / canvasWidth, image.height / canvasHeight); // canvasWidth and height are globals
ctx.setTransform(s,0,0,s,0,0); // set scale and position
ctx.globalAlpha = 1;
ctx.drawImage(image,0,0); // render the subimage
}
It is common that the playfield can be zoomed, panned, and rotated. For this I maintain a closure transform state (all globals above are closed over variables and part of the render object)
// all coords are relative to the global transfrom
function drawGlobalSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
// m1 to m6 are the global transform
ctx.setTransform(m1,m2,m3,m4,m5,m6); // set playfield
ctx.transform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a * globalAlpha; (a real global alpha)
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
All the above are about as fast as you can get for practical game sprite rendering.
General tips
Never use any of the vector type rendering methods (unless you have the spare frame time) like, fill, stroke, filltext, arc, rect, moveTo, lineTo as they are an instant slowdown. If you need to render text create a offscreen canvas, render once to that, and display as a sprite or image.
Image sizes and GPU RAM
When creating content, always use the power rule for image sizes. GPU handle images in sizes that are powers of 2. (2,4,8,16,32,64,128....) so the width and height have to be a power of two. ie 1024 by 512, or 2048 by 128 are good sizes.
When you do not use these sizes the 2D context does not care, what it does is expand the image to fit the closest power. So if I have an image that is 300 by 300 to fit that on the GPU the image has to be expanded to the closest power, which is 512 by 512. So the actual memory footprint is over 2.5 times greater than the pixels you are able to display. When the GPU runs out of local memory it will start switching memory from mainboard RAM, when this happens your frame rate drops to unusable.
Ensuring that you size images so that you do not waste RAM will mean you can pack a lot more into you game before you hit the RAM wall (which for smaller devices is not much at all).
GC is a major frame theef
One last optimisation is to make sure that the GC (garbage collector) has little to nothing to do. With in the main loop, avoid using new (reuse and object rather than dereference it and create another), avoid pushing and popping from arrays (keep their lengths from falling) keep a separate count of active items. Create a custom iterator and push functions that are item context aware (know if an array item is active or not). When you push you don't push a new item unless there are no inactive items, when an item becomes inactive, leave it in the array and use it later if one is needed.
There is a simple strategy that I call a fast stack that is beyond the scope of this answer but can handle 1000s of transient (short lived) gameobjects with ZERO GC load. Some of the better game engines use a similar approch (pool arrays that provide a pool of inactive items).
GC should be less than 5% of your game activity, if not you need to find where you are needlessly creating and dereferencing.
I have some ActionScript3 code I'm using to create liquid-like "droplets", and when they're first generated they look like a curved square (that's as close as I can get them to being a circle). I've tried and failed a lot here but my goal is to make these droplets look more organic and free-form, as if you were looking closely at rain drops on your windshield before they start dripping.
Here's what I have:
var size:int = (100 - asset.width) / 4,
droplet:Shape = new Shape();
droplet.graphics.beginFill(0xCC0000);
droplet.graphics.moveTo(size / 2, 0);
droplet.graphics.curveTo(size, 0, size, size / 2);
droplet.graphics.curveTo(size, size, size / 2, size);
droplet.graphics.curveTo(0, size, 0, size / 2);
droplet.graphics.curveTo(0, 0, size / 2, 0);
// Apply some bevel filters and such...
Which yields a droplet shaped like this:
When I try adding some randomness to the size or the integers or add more curves in the code above, I end up getting jagged points and some line overlap/inversion.
I'm really hoping someone who is good at math or bezier logic can see something obvious that I need to do to make my consistently rounded-corner square achieve shape randomness similar to this:
First off, you can get actual circle-looking cirles using beziers by using 0.55228 * size rather than half-size (in relation to bezier curves, this is sometimes called kappa). It only applies if you're using four segments, and that's where the other hint comes in: the more points you have, the more you can make your shape "creep", so you might actually want more segments, in which case it becomes easier to simply generate a number of points on a circle (fairly straight forward using good old sine and cosine functions and a regularly spaced angle), and then come up with the multi-segment Catmul-Rom curve through those points instead. Catmul-Rom curves and Bezier curves are actually different representations of the same curvatures, so you can pretty much trivially convert from one to the other, explained over at http://pomax.github.io/bezierinfo/#catmullconv (last item in the section gives the translation if you don't care about the maths). You can then introduce as much random travel as you want (make the upper points a little stickier and "jerk" them down when they get too far from the bottom points to get that sticky rain look)
I'm working on a simple image crop where the user draws a line with the mouse around an area that they want to keep. When they confirm, the rest of the image will be cropped out. Here's how I'm currently handling said cropping:
var data = c.getImageData(0,0,canvas.width,canvas.height);
for (var x = 0; x < data.width; x++) {
for (var y = 0; y < data.height; y++) {
if (!c.isPointInPath(x,y)) {
var n = x + (data.width * y);
var index = n*4;
data.data[index+3] = 0;
}
}
}
However, this can bog down really quickly. The less of the image you try to retain, the faster it goes, but even saving 30% of the image (canvas is 800x800) causes it to hang for several seconds. Is there a faster way to go about this?
I don't really understand why you are diving into pixel details to manipulate your cropping image functionality. It's understandable as bigger the image is get as more time is needed for cropping out the rest of the image, because practically with iterating over a two dimensional array of pixels the processing time needed for the operation is exponentially increasing with the increasing in size of the pixels map.
So my suggestion would be to try to remake the function without to even touch the getImageData and putImageData function. It's useless. I would make in the following way:
Obtain the pixel coordinates at the mouse down.
Create an event listener for the mouse move.
Create a semi-transparent image over the original image and use the fillRect function to draw into the created canvas.
Create an event listener for mouse up.
Obtain the pixel coordinates at the mouse up.
Calculate the coordinates of the resulting square.
Draw the resulting image into the canvas using as parameters the square coordinates.
As a final step draw the content of the canvas to an image.
This way you will save a lot of overhead on image cropping processing.
Here is a script for your reference: https://github.com/codepo8/canvascropper/blob/master/canvascrop.js
There is no real way to speed it up when you have to use a user defined shape, but the bogging down can be handled with a worker.
Some ideas:
Restrict getImageData to the bounding box of the polygon the user draws.
Put data.height, data.width etc. used inside the loop in a variable.
Maybe you can split up inside/outside tests and setting the imagedata alpha value.
Maybe even draw the polygon to a black and white imagedata object, then get the difference between the two?
Can you share the isPointInPath(x,y) function?
I'm trying to position an image on top of another image based upon the make-up of the smaller image. The smaller image is a cut-out of a larger image and I need it to be positioned exactly on the larger image to make it look like a single image, but allow for separate filters and alphas to be applied. As the images are not simple rectangles or circles, but complex satellite images, I cannot simply redraw them in code. I have quite a few images and therefore do not feel like manually finding the position of each image every and hard setting them manually in actionscript. Is there any way for me to sample a small 5-10 sq. pixel area against the larger image and set the x and y values of the smaller image if a perfect match is found? All the images are in an array and iterating through them has already been set, I just need a way to sample and match pixels. My first guess was to loop the images pixel by pixel right and down, covering the whole bitmap and moving to the next child in the array once a match was found, leaving the matched child where it was when the perfect match was found.
I hope I understood your question correctly.
There may be an option that uses copypixels to achieve what you want. You can use the bitmapdata.rect value to determine the size of the sample you want, and loop through the bigger bitmap using thet rectangle and a moving point. Let's see if I can code this out...
function findBitmapInBitmap(tinyimg:BitmapData, largeimg:BitmapData):Point {
var rect:Rectangle = tinyimg.rect;
var xbound:uint = largeimg.rect.width;
var ybound:uint = largeimg.rect.height;
var imgtest:BitmapData = new BitmapData(tinyimg.rect.width, tinyimg.rect.height);
for (var ypos:uint = 0, y <= ybound, y++) {
for (var xpos:uint = 0, x <= xbound, x++) {
imgtest.copyPixels(largeimg, rect, new Point(xpos, ypos);
if (imgtest.compare(tinyimg) == 0) return new Point(xpos, ypos);
}
}
return new Point(-1,-1); // Dummy value, indicating no match.
}
Something along those lines should work - I'm sure there's room for code elegance and possible optimization. However, it seems like something like this method would be very slow, since you'd have to check each pixel for a match.
There is a better way. Split your big image into layers, and use the blitting technique to composite them at runtime. In your case, you could create a ground texture without satellites, and then create the satellites separately, and use the copyPixels method to place them whereever you want. Google "blitting in as3" to find some good tutorials. I'm currently working on a game project that uses this technique and it's a very good method.
Good luck!
Edit: Forgot to code in a default return statement. Using this method, you'd have to return an invalid point (like (-1,-1)) and check for it outside the function. Alternatively, you could just copy your small bitmap to the big one within the function, which would be much more logical, but I don't know your requirements.
You need to find pixel sequence in the big image. BitmapData.getPixel gives you pixel value. So get first pixel from small image, find it in big image, then continue comparing until you find full match. If you have trouble to code that, feel free to ask.
For the actual comparison, there's BitmapData.compare which returns the number 0 if the BitmapData objects are equivalent.
I have a rectangle, and a circle inside that rectangle (that sits around the center of the rectangle). I want to generate a random 2-component vector that falls inside the rectangle, but not the circle. How can I do it?
Edit: I'd prefer a method that i can use to generate a vector that meets these constraints without brute-forcing it.
Vector = Rectangle.RandomVector();
while (Circle.Contains(Vector)) {
Vector = Rectangle.RandomVector();
}
Aka, just brute force it. It has a 21.5% chance of being outside the circle every time :)
Generate random numbers for the x and y component of the vector such that x < rectangle.width and y < rectangle.heigth. Then check whether x^2 + y^2 < circle.radius^2 and throw the vector away if so.
Update: Another way which generates a non uniform distribution but doesn't throw away any vector is as follows: Randomly choose an angle from the interval 0 to 2*pi. Now determine the length of the vector by randomly choosing a value from the interval determined by the intersections of the line with the former angle starting at the center with the circle and the rectangle.
The resulting distribution will be uniform when protected onto the circle. It will also be uniform for each angle. It wont be uniform in the plane however.