Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

draw 10,000 objects on canvas javascript

I need draw over 10,000 images (32x32 px) on canvas but over 2000 draws the performances is very bad.

this is a little example:

object structure {position:0}

for(var nObject = 0; nObject < objects.length; nObject++){
    ctx.save();
    ctx.translate(coords.x,coords.y);
    ctx.rotate(objects[nObject].position/100);
    ctx.translate(radio,0);
    ctx.drawImage(img,0,0);
    ctx.restore();
    objects[nObject].position++;
}

with this code I traslate the images around of a coordinates.

What do you recommend to improve performance?

update:

i try layering but the performances worsens

http://jsfiddle.net/72nCX/3/

like image 578
J261 Avatar asked May 05 '14 08:05

J261


People also ask

Does JavaScript work in canvas?

<canvas> is an HTML element which can be used to draw graphics via scripting (usually JavaScript). This can, for instance, be used to draw graphs, combine photos, or create simple animations.

Can we draw anything other than a rectangle in canvas?

Unlike SVG, <canvas> only supports two primitive shapes: rectangles and paths (lists of points connected by lines). All other shapes must be created by combining one or more paths. Luckily, we have an assortment of path drawing functions which make it possible to compose very complex shapes.

Which API is used in JavaScript for drawing?

The Canvas API provides a means for drawing graphics via JavaScript and the HTML <canvas> element. Among other things, it can be used for animation, game graphics, data visualization, photo manipulation, and real-time video processing. The Canvas API largely focuses on 2D graphics.


2 Answers

I can get you 10,000 but there are two main drawbacks.

  1. You may notice the images don't respect transparency entirely, its possible to fix.. but that's beyond the scope of this answer.

  2. You will have to use math to do any sort of transformations because the standard canvas transformation matrix can not be applied to ImageData

Live Demo

Explanation of the code and methods

So to get the fastest performance possible with canvas and a large number of objects you need to use ImageData. This is accessing the canvas element on a per pixel level basically, and allows you to do all sorts of cool stuff. I used two primary methods.

  • putImageData
  • createImageData

Also here is a nice tutorial that goes into it a bit to help get a better understanding.

So what I did is first I created a temporary canvas for the image

imgToDraw.onload = function () {
    // In memory canvas
    imageCanvas = document.createElement("canvas"),
    iCtx = imageCanvas.getContext("2d");

    // set the canvas to the size of the image
    imageCanvas.width = this.width;
    imageCanvas.height = this.height;

    // draw the image onto the canvas
    iCtx.drawImage(this, 0, 0);

    // get the ImageData for the image.
    imageData = iCtx.getImageData(0, 0, this.width, this.height);
    // get the pixel component data from the image Data.
    imagePixData = imageData.data;

    // store our width and height so we can reference it faster.
    imgWidth = this.width;
    imgHeight = this.height;

    draw();
};

Next Is the main piece which is in the rendering function

I'm just posting the relevant portion.

// create new Image data. Doing this everytime gets rid of our 
// need to manually clear the canvas since the data is fresh each time
var canvasData = ctx.createImageData(canvas.width, canvas.height),
    // get the pixel data
    cData = canvasData.data;

// Iterate over the image we stored 
for (var w = 0; w < imgWidth; w++) {
    for (var h = 0; h < imgHeight; h++) {
        // make sure the edges of the image are still inside the canvas
        // This also is VERY important for perf reasons
        // you never want to draw outside of the canvas bounds with this method
        if (entity.x + w < width && entity.x + w > 0 &&
            entity.y + h > 0 && entity.y + h < height) {

            // get the position pixel from the image canvas
            var iData = (h * imgWidth + w) * 4;

            // get the position of the data we will write to on our main canvas
            // the values must be whole numbers ~~ is just Math.floor basically
            var pData = (~~ (entity.x + w) + ~~ (entity.y + h) * width) * 4;

            // copy the r/g/b/ and alpha values to our main canvas from 
            // our image canvas data.

            cData[pData] = imagePixData[iData];
            cData[pData + 1] = imagePixData[iData + 1];
            cData[pData + 2] = imagePixData[iData + 2];
            // this is where alpha blending could be applied
            if(cData[pData + 3] < 100){
                cData[pData + 3] = imagePixData[iData + 3];
            }
        }
    }
}

// now put all of that image data we just wrote onto the actual canvas.
ctx.putImageData(canvasData, 0, 0);

The main Take away from this is, if you need to draw a ridiculous number of objects on the canvas you can't use drawImage, pixel manipulation is your friend.

like image 60
Loktar Avatar answered Oct 07 '22 19:10

Loktar


I think this is what you need.

Eric Rowell (creator of KineticJS) has done some stress tests here.

And he says this:

"Create 10 layers each containing 1000 shapes to create 10,000 shapes. This greatly improves performance because only 1,000 shapes will have to be drawn at a time when a circle is removed from a layer rather than all 10,000 shapes."

"Keep in mind that having too many layers can also slow down performance. I found that using 10 layers each made up of 1,000 shapes performs better than 20 layers with 500 shapes or 5 layers with 2,000 shapes."

Update: You would need to run test cases in which the most optimized procedure would be for you. Example: 10000 shapes can be achieved by either:

10000 shapes * 1 layer

5000 shapes * 2 layer

2500 shapes * 4 layer

Whichever works for you,choose that! It depends upon your code.

like image 29
Nevin Madhukar K Avatar answered Oct 07 '22 18:10

Nevin Madhukar K