Suppose that I have a 900x900 HTML5 Canvas element.
I have a function called computeRow that accepts, as a parameter, the number of a row on the grid and returns an array of 900 numbers. Each number represents a number between 0 and 200. There is an array called colors that contains an array of strings like rgb(0,20,20), for example.
Basically, what I'm saying is that I have a function that tells pixel-by-pixel, what color each pixel in a given row on the canvas is supposed to be. Running this function many times, I can compute a color for every pixel on the canvas.
The process of running computeRow 900 times takes about 0.5 seconds.
However, the drawing of the image takes much longer than that.
What I've done is I've written a function called drawRow that takes an array of 900 numbers as the input and draws them on the canvas. drawRow takes lots longer to run than computeRow! How can I fix this?
drawRow is dead simple. It looks like this:
function drawRow(rowNumber, result /* array */) {
var plot, context, columnNumber, color;
plot = document.getElementById('plot');
context = plot.getContext('2d');
// Iterate over the results for each column in the row, coloring a single pixel on
// the canvas the correct color for each one.
for(columnNumber = 0; columnNumber < width; columnNumber++) {
color = colors[result[columnNumber]];
context.fillStyle = color;
context.fillRect(columnNumber, rowNumber, 1, 1);
}
}
I'm not sure exactly what you are trying to do, so I apologize if I am wrong.
If you are trying to write a color to each pixel on the canvas, this is how you would do it:
var ctx = document.getElementById('plot').getContext('2d');
var imgdata = ctx.getImageData(0,0, 640, 480);
var imgdatalen = imgdata.data.length;
for(var i=0;i<imgdatalen/4;i++){ //iterate over every pixel in the canvas
imgdata.data[4*i] = 255; // RED (0-255)
imgdata.data[4*i+1] = 0; // GREEN (0-255)
imgdata.data[4*i+2] = 0; // BLUE (0-255)
imgdata.data[4*i+3] = 255; // APLHA (0-255)
}
ctx.putImageData(imgdata,0,0);
This is a lot faster than drawing a rectangle for every pixel. The only thing you would need to do is separate you color into rgba() values.
If you read the color values as strings from an array for each pixel it does not really matter what technique you use as the bottleneck would be that part right there.
For each pixel the cost is split on (roughly) these steps:
Look up array (really a node/linked list in JavaScript)
Get string
Pass string to fillStyle
Parse string (internally) into color value
Ready to draw a single pixel
These are very costly operations performance-wise. To get it more efficient you need to convert that color array into something else than an array with strings ahead of the drawing operations.
You can do this several ways:
If the array comes from a server try to format the array as a blob / typed array instead before sending it. This way you can copy the content of the returned array almost as-is to the canvas' pixel buffer.
Use a web workers to parse the array and pass it back as a transferable object which you them copy into the canvas' buffer. This can be copied directly to the canvas - or do it the other way around, transfer the pixel buffer to worker, fill there and return.
Sort the array by color values and update the colors by color groups. This way you can use fillStyle or calculate the color into an Uint32 value which you copy to the canvas using a Uint32 buffer view. This does not work well if the colors are very spread but works ok if the colors represent a small palette.
If you're stuck with the format of the colors then the second option is what I would recommend primarily depending on the size. It makes your code asynchronous so this is an aspect you need to deal with as well (ie. callbacks when operations are done).
You can of course just parse the array on the same thread and find a way to camouflage it a bit for the user in case it creates a noticeable delay (900x900 shouldn't be that big of a deal even for a slower computer).
If you convert the array convert it into unsigned 32 bit values and store the result in a Typed Array. This way you can iterate your canvas pixel buffer using Uint32's instead which is much faster than using byte-per-byte approach.
fillRect is meant to be used for just that - filling an area with a single color, not pixel by pixel. If you do pixel by pixel, it is bound to be slower as you are CPU bound. You can check it by observing the CPU load in these cases. The code will become more performant if
A separate image is created with the required image data filled in. You can use a worker thread to fill this image in the background. An example of using worker threads is available in the blog post at http://gpupowered.org/node/11
Then, blit the image into the 2d context you want using context.drawImage(image, dx, dy).
Related
I have a script which takes a picture and creates black and white R, G and B channels. And I would like to count how many channels have something on them (black = no colour, white = colour data).
It works by going through every single pixel and saving data in 3 different canvas elements. Each one showing different channel.
It works great but when I give a Red and Blue picture only, the green channel (obviously) is completely black.
So my question is, how do I check whether the canvas is completely black? I would like to avoid looping through every pixel in every channel to see if it's all black.
cheers
Ok I've tested a few things and the easiest solution I could come up with was creating a 1x1 offscreen canvas, then drawing either your image or your original canvas you want to check for data on it, scaled down to 1 pixel and check it (I also added basic caching).
It's not a precise check, and the result depends on how the pixel data is distributed in the original image, so as GameAlchemist noted, you may loop through all the pixels anyway if the result of the check is 0 if you want accurate results.
function quickCheck(img){
var i = arguments.callee; //caching the canvas&context in the function
if(!i.canvas){i.canvas = document.createElement("canvas")}
if(!i.ctx) {i.ctx = i.canvas.getContext("2d")}
i.canvas.width=i.canvas.height = 1;
i.ctx.drawImage(img,0,0,1,1);
var result = i.ctx.getImageData(0,0,1,1);
return result.data[0]+result.data[1]+result.data[2]+result.data[3];
}
You must iterate the canvas's pixel data--no magic method to avoid that.
The performance cost of iterating through 2 images is trivial (2 images = your front image and back image).
[ Removed un-necessary part of answer after visiting questioner's site ]
Another quick check would be to calculate the sum of the image data. If the sum is equal to the amount of pixels in the canvas times 255, it is probably all black. There is a small chance that some other combination of pixels could cause the sum to be equal to the same number, but the chance is very low (unless you are looping through all the canvas combinations).
An emulator I am working with internally stores a 1-dimensional framebuffer of RGB values. However, HTML5 canvas uses RGBA values when calling putImageData. In order to display the framebuffer, I currently loop through the RGB array and create a new RGBA array, in a manner similar to this.
This seems suboptimal. There has been much written on performing canvas draws quickly, but I'm still lost on how to improve my application performance. Is there any way to more quickly translate this RGB array to an RGBA array? The alpha channel will always be fully opaque. Also, is there any way to interface with a canvas so that it takes an array of RGB, not RGBA, values?
There's no way to use plain RGB, but the loop in that code could be optimised somewhat by removing repeated calculations, array deferences, etc.
In general you shouldn't use ctx.getImageData to obtain the destination buffer - you don't normally care what values are already there and should use ctx.createImageData instead. If at all possible, re-use the same raw buffer for every frame.
However, since you want to preset the alpha values to 0xff (they default to 0x00) and only need to do so once, it seems to be much most efficient to just fill the canvas and then fetch the raw values with getImageData.
ctx.fillStyle = '#ffffff'; // implicit alpha of 1
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
dest = ctx.getImageData(0, 0).data
and then for each frame for can just leave the alpha byte untouched:
var n = 4 * w * h;
var s = 0, d = 0;
while (d < n) {
dest[d++] = src[s++];
dest[d++] = src[s++];
dest[d++] = src[s++];
d++; // skip the alpha byte
}
You could also experiment with "loop unrolling" (i.e. repeating that four line block multiple times within the while loop) although results will vary across browsers.
Since it's very likely that your total number of pixels will be a multiple of four, just repeat the block another three times and then the while will only be evaluated for every four pixel copies.
Both ctx.createImageData and ctx.getImageData will create a buffer, the later (get) will be slower since it has also to copy the buffer.
This jsperf : http://jsperf.com/drawing-pixels-to-data
confirms that we have a like 33% slowdown on Chrome, and 16 times slower on Firefox (FFF seems to byte-copy when Chrome copy with 32 or 64 bits move).
i'll just recall that you can handle typed array of different types, and even create a view on the buffer (image.data.buffer).
So this may allow you to write the bytes 4 by 4.
var dest = ctx.createImageData(width, height);
var dest32 = new Int32Array(dest.data.buffer);
var i = 0, j=0, last = 3*width*height;
while (i<last) {
dest32[j] = src[i]<<24 + src[i+1] << 16
+ src[i+2] << 8 + 255;
i+=3;
j++;
}
You will see in this jsperf test i made that it is faster to
write using 32 bits integers :
http://jsperf.com/rgb-to-rgba-conversion-with-typed-arrays
notice that there is a big issue in those tests : since this test is
awfull in terms of garbage creation, accuracy is so-so.
Still after many launch, we see that we have around 50%
gain on write 4 vs write 1.
Edit : it might be worth to see if reading the source with a DataView wouldn't speed things up.
but the input array has to be a buffer (or have a buffer property like a Uint8Array).
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/DataView)
do not hesitate to update the fiddle with such a try.
Edit 2 :
I don't understand i re-ran the test and now write 4 is slower : ??? and after, faster again : -------
Anyway you have great interest in keeping the dest32 buffer under your hand and not
create a new one each time anyway, so since this test measure the Int32Array creation, it does not correspond to your use case.
I want to know if I've understood this correctly.
I loop my map and load sprites on the map.
So I decided to store the pixel information in an array so that when I click with my mouse I check if its in a pixel array range and get the id related to it (effectively being pixel accurate for detecting what object was clicked?)
This is my thinking process:
I draw the sprite:
ctx.drawImage(castle[id], abposx, abposy - (imgheight/2));
myImageData[sdata[i][j][1]] =
ctx.getImageData(abposx, abposy, castle[id].width, castle[id].height);
Then some how with left click, check if mouse x and mouse y is inbetween the range of the arrays and return the value of myImageData?
Or have I misunderstood what getImageData is about?
getImageData gives you all of the pixel data for an image. Basically you only need to use getImageData if you are doing any sort of pixel manipulation with the image, like changing its hue/color, or applying a filter, or need specific data, such as the r/g/b, or alpha values. In order to check for pixel perfect collisions you an do something like the following:
var imageData = ctx.getImageData(x, y, 1, 1);
if(imageData.data[3] !== 0){
// you have a collision!
}
imageData.data[0-3] holds an array of data, 0-2 are the color values r/g/b, and 3 is the alpha value. So we assume if the alpha is 0, it must be a transparent portion. Also note, in the example and fiddle I am grabbing the data from the canvas itself, so if there was an image behind it that wasnt transparent it would count as not being transparent. The best way to do it if you have many images that overlap is to keep a copy of the image by itself offscreen somewhere and do a translation of the coordinates to get the position on the image. Heres a good MDN Article explaining getImageData as well.
Live Demo
I'm currently trying to create a page with dynamically generated images, which are not shapes, drawn into a canvas to create an animation.
The first thing I tried was the following:
//create plenty of those:
var imageArray = ctx.createImageData(0,0,16,8);
//fill them with RGBA values...
//then draw them
ctx.putImageData(imageArray,x,y);
The problem is that the images are overlapping and that putImageData simply... puts the data in the context, with no respect to the alpha channel as specified in the w3c:
pixels in the canvas are replaced wholesale, with no composition, alpha blending, no shadows, etc.
So I thought, well how can I use Images and not ImageDatas?
I tried to find a way to actually put the ImageData object back into an image but it appears it can only be put in a canvas context. So, as a last resort, I tried to use the toDataURL() method of a 16x8 canvas(the size of my images) and to stick the result as src of my ~600 images.
The result was beautiful, but was eating up 100% of my CPU...(which it did not with putImageData, ~5% cpu) My guess is that for some unknown reason the image is re-loaded from the image/png data URI each time it is drawn... but that would be plain weird... no? It also seems to take a lot more RAM than my previous technique.
So, as a result, I have no idea how to achieve my goal.
How can I dynamically create alpha-channelled images in javascript and then draw them at an appreciable speed on a canvas?
Is the only real alternative using a Java applet?
Thanks for your time.
Not knowing, what you really want to accomplish:
Did you have a look at the drawImage-method of the rendering-context?
Basically, it does the composition (as specified by the globalCompositeOperation-property) for you -- and it allows you to pass in a canvas element as the source.
So could probably do something along the lines of:
var offScreenContext = document.getCSSCanvasContext( "2d", "synthImage", width, height);
var pixelBuffer = offScreenContext.createImageData( tileWidth, tileHeight );
// do your image synthesis and put the updated buffer back into the context:
offScreenContext.putImageData( pixelBuffer, 0, 0, tileOriginX, tileOriginY, tileWidth, tileHeight );
// assuming 'ctx' is the context of the canvas that actually gets drawn on screen
ctx.drawImage(
offScreenContext.canvas, // => the synthesized image
tileOriginX, tileOriginY, tileWidth, tileHeight, // => frame of offScreenContext that get's drawn
originX, originY, tileWidth, tileHeight // => frame of ctx to draw in
);
Assuming that you have an animation you want to loop over, this has the added benefit of only having to generate the frames once into some kind of sprite-map so that in subsequent iterations you'll only ever need to call ctx.drawImage() -- at the expense of an increased memory footprint of course...
Why don't you use SVG?
If you have to use canvas, maybe you could implement drawing an image on a canvas yourself?
var red = oldred*(1-alpha)+imagered*alpha
...and so on...
getCSSCanvasContext seems to be WebKit only, but you could also create an offscreen canvas like this:
var canvas = document.createElement('canvas')
canvas.setAttribute('width',300);//use whatever you like for width and height
canvas.setAttribute('height',200);
Which you can then draw to and draw onto another canvas with the drawImage method.
I want to check a collision between two Sprites in HTML5 canvas. So for the sake of the discussion, let's assume that both sprites are IMG objects and a collision means that the alpha channel is not 0. Now both of these sprites can have a rotation around the object's center but no other transformation in case this makes this any easier.
Now the obvious solution I came up with would be this:
calculate the transformation matrix for both
figure out a rough estimation of the area where the code should test (like offset of both + calculated extra space for the rotation)
for all the pixels in the intersecting rectangle, transform the coordinate and test the image at the calculated position (rounded to nearest neighbor) for the alpha channel. Then abort on first hit.
The problem I see with that is that a) there are no matrix classes in JavaScript which means I have to do that in JavaScript which could be quite slow, I have to test for collisions every frame which makes this pretty expensive. Furthermore I have to replicate something I already have to do on drawing (or what canvas does for me, setting up the matrices).
I wonder if I'm missing anything here and if there is an easier solution for collision detection.
I'm not a javascript coder but I'd imagine the same optimisation tricks work just as well for Javascript as they do for C++.
Just rotate the corners of the sprite instead of every pixel. Effectively you would be doing something like software texture mapping. You could work out the x,y position of a given pixel using various gradient information. Look up software texture mapping for more info.
If you quadtree decomposed the sprite into "hit" and "non-hit" areas then you could effectively check to see if a given quad tree decomposition is all "non-hit", "all hit" or "possible hit" (ie contains hits and non-hit pixels. The first 2 are trivial to pass through. In the last case you then go down to the next decomposition level and repeat the test. This way you only check the pixels you need too and for large areas of "non-hit" and "hit" you don't have to do such a complex set of checks.
Anyway thats just a couple of thoughts.
I have to replicate something I already have to do on drawing
Well, you could make a new rendering context, plot one rotated white-background mask to it, set the compositing operation to lighter and plot the other rotated mask on top at the given offset.
Now if there's a non-white pixel left, there's a hit. You'd still have to getImageData and sift through the pixels to find that out. You might be able to reduce that workload a bit by scaling the resultant image downwards (relying on anti-aliasing to keep some pixels non-white), but I'm thinking it's probably still going to be quite slow.
I have to test for collisions every frame which makes this pretty expensive.
Yeah, I think realistically you're going to be using precalculated collision tables. If you've got space for it, you could store one hit/no hit bit for every combination of sprite a, sprite b, relative rotation, relative-x-normalised-to-rotation and relative-y-normalised-to-rotation. Depending on how many sprites you have and how many steps of rotation or movement, this could get rather large.
A compromise would be to store the pre-rotated masks of each sprite in a JavaScript array (of Number, giving you 32 bits/pixels of easily &&-able data, or as a character in a Sring, giving you 16 bits) and && each line of intersecting sprite masks together.
Or, give up on pixels and start looking at eg. paths.
Same problem, an alternative solution. First I use getImageData data to find a polygon that surrounds the sprite. Careful here because the implementation works with images with transparent background that have a single solid object. Like a ship. The next step is Ramer Douglas Peucker Algorithm to reduce the number of vertices in the polygon. I finally get a polygon of very few vertices easy and cheap to rotate and check collisions with the other polygons for each sprite.
http://jsfiddle.net/rnrlabs/9dxSg/
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var img = document.getElementById("img");
context.drawImage(img, 0,0);
var dat = context.getImageData(0,0,img.width, img.height);
// see jsfiddle
var startPixel = findStartPixel(dat, 0);
var path = followPath(startPixel, dat, 0);
// 4 is RDP epsilon
map1 = properRDP(path.map, 4, path.startpixel.x, path.startpixel.y);
// draw
context.beginPath();
context.moveTo(path.startpixel.x, path.startpixel.x);
for(var i = 0; i < map.length; i++) {
var p = map[i];
context.lineTo(p.x, p.y);
}
context.strokeStyle = 'red';
context.closePath();
context.stroke();