Нарисовать 10 000 объектов на холсте javascript

Мне нужно нарисовать более 10000 изображений (32x32 px) на холсте, но более 2000 рисунков очень плохие.

это небольшой пример:

структура объекта {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++;
}

с помощью этого кода я трассирую изображения вокруг координат.

Что вы рекомендуете для повышения производительности?

обновление:

Я пытаюсь расслоить, но характеристики ухудшаются

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

Ответы

Ответ 1

Я могу получить вам 10 000, но есть два главных недостатка.

  • Вы можете заметить, что изображения полностью не уважают прозрачность, его можно исправить.. но это выходит за рамки этого ответа.

  • Вам нужно будет использовать математику для каких-либо преобразований, потому что стандартная матрица преобразования холста не может быть применена к ImageData​​p >

Live Demo

Объяснение кода и методов

Итак, чтобы получить максимально возможную производительность с помощью холста и большого количества объектов, вам нужно использовать ImageData. Это доступ к элементу холста на уровне пикселя в основном и позволяет делать всевозможные классные вещи. Я использовал два основных метода.

Также здесь есть хороший учебник, который немного помогает ему лучше понять.

Так что я сделал, сначала создал временный холст для изображения

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();
};

Далее Является основной частью, которая находится в функции рендеринга

Я просто размещаю соответствующую часть.

// 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);

Главное Отвлечься от этого, если вам нужно нарисовать смешное количество объектов на холсте, вы не можете использовать drawImage, манипуляция с пикселями - ваш друг.

Ответ 2

Я думаю, это, что вам нужно.

Эрик Роуэлл (создатель KineticJS) провел здесь несколько стресс-тестов.

И он говорит это:

"Создайте 10 слоев, каждая из которых содержит 1000 фигур, чтобы создать 10 000 фигур. Это значительно улучшает производительность, потому что нужно будет нарисовать только 1000 фигур в то время, когда круг удаляется из слоя, а не все 10000 фигур."

"Имейте в виду, что слишком много слоев может замедлить производительность. Я обнаружил, что использование 10 слоев, каждая из которых состоит из 1000 фигур, выполняет более 20 слоев с 500 формами или 5 слоями с 2000 формами".

Обновление: Вам нужно будет запустить тестовые примеры, в которых наиболее оптимизированная процедура будет для вас. Пример: 10000 форм могут быть достигнуты либо:

10000 форм * 1 слой

5000 форм * 2 слоя

2500 фигур * 4 слоя

Какое бы ни было для вас, выберите это! Это зависит от вашего кода.

Ответ 3

Если изображения не перекрываются, то получившееся изображение имеет размер 3200x3200 пикселей, что больше, чем отображает большинство дисплеев. Таким образом, вы можете попытаться получить ограничительную рамку преобразованного изображения и пропустить те, которые находятся за пределами видимой области (даже если холст уже должен сделать это для вас).

Другая идея - объединить маленькие изображения в более крупные и преобразовать их вместе как группу.

Если вы хотите упорядочить изображения в кольце, вы можете нарисовать их один раз в качестве кольца, сохранить это как изображение и затем повернуть "изображение кольца" вместо каждого отдельного изображения.

Наконец, посмотрите на WebGL, который может быть более эффективным, чем 2D canvas API.

Ответ 4

Вот несколько шагов, которые вы можете сделать для повышения производительности:

  • Сначала избавьтесь от save/restore - они очень дорогие звонки и могут быть заменены на setTransform
  • Отвяжите цикл, чтобы сделать больше внутри каждой итерации
  • Кэш всех свойств

FIDDLE

Пример с разверткой цикла для 4 итераций:

for(var nObject = 0,
        len = objects.length,    // cache these
        x = coords.x,
        y = coords.y; nObject < len; nObject++){

    ctx.setTransform(1,0,0,1, x, y);   // sets absolute transformation
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;
}
ctx.setTransform(1,0,0,1,0,0);  // reset transform for rAF loop

(не ожидайте производительности в режиме реального времени, хотя).

Хотя, возможно, это немного бессмысленный рисунок объектов 2000 в такой небольшой области. Если вы после эффекта, я бы предложил этот подход:

  • Создать экранный холст
  • Создайте 5-8 кадров с помощью метода выше и сохраните их как изображения
  • Воспроизведение этих 5-8 изображений как есть вместо выполнения всех вычислений

Если вам нужно больше жидкостного вида, просто создайте больше кадров. Вы можете хранить каждый кадр в одном холсте на основе ячеек, которые вы позже используете в качестве спрайта. При рисовании вы должны, конечно, следить за тем, чтобы текущие позиции были статичными или движущимися при фактическом анимировании. Вращение и результирующее положение являются еще одним фактором.

Ответ 5

После различных тестов я пришел к следующим выводам:

  • canvas не может использовать эту задачу.
  • Многослойное полотно помогает только производительности, когда статические элементы не нужно постоянно перерисовывать.
  • Добавление предела печати в координатах помогает в рендеринге.
  • альтернативы медленным функциям
  • Не печатать элементы в конечном счете будут скрыты другим элементом с более высоким z-индексом (работающим над ним).

конечный результат - небольшая комбинация всех вкладов. но нуждается в улучшении.

Протестировано 30 000 объектов, а производительность поддерживается на уровне 60/fps.

http://jsfiddle.net/NGn29/1/

        var banPrint = true;
        for(nOverlap = nObject; nOverlap < objects.length; nOverlap++){
            if(
                objects[nOverlap].position == objects[nObject].position
                && nOverlap != nObject
            ){
                banPrint = false;
                break;
            }
        }