Нарисовать 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, но есть два главных недостатка.
-
Вы можете заметить, что изображения полностью не уважают прозрачность, его можно исправить.. но это выходит за рамки этого ответа.
-
Вам нужно будет использовать математику для каких-либо преобразований, потому что стандартная матрица преобразования холста не может быть применена к ImageDatap >
Объяснение кода и методов
Итак, чтобы получить максимально возможную производительность с помощью холста и большого количества объектов, вам нужно использовать 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;
}
}