Почему putImageData так медленно?

Я работаю с относительно большим холстом, на который нарисованы различные (сложные) вещи. Затем я хочу сохранить состояние Canvas, поэтому я могу быстро reset перейти к состоянию, которое оно сейчас находится в более поздней точке. Для этого я использую getImageData и сохраняю данные в переменной. Затем я нарисую еще немного материала на холст и позже reset Canvas, где он был, когда я сохранил его состояние, используя putImageData.

Однако, оказывается, что putImageData очень медленный. Infact, он медленнее, чем просто перерисовывает весь холст с нуля, что включает в себя несколько drawImage, покрывающих большую часть поверхности, и более 40 000 операций lineTo, за которыми следуют штрихи и заливки.

Перерисовывая холст размером примерно 2000 х 5000 пикселей с нуля, занимает ~ 170 мс, используя putImageData, хотя и занимает колоссальные 240 мс. Почему putImageData так медленно по сравнению с перерисованием холста, хотя перерисовывание холста включает в себя заполнение почти всего холста drawImage, а затем снова заполнение примерно 50% холста полигонами с использованием lineTo, штрихов и заполнения. Таким образом, каждый пиксель, по крайней мере, один раз затрагивался при перерисовке.

Поскольку drawImage кажется намного быстрее, чем putImageData (в конце концов, drawImage для перерисовки холста занимает менее 30 мс). Я решил попытаться сохранить состояние canvas, не используя getImageData, но вместо этого используя canvas.toDataURL, а затем создав изображение из URL-адреса данных, который я бы вставлял в drawImage, чтобы нарисовать его на холсте. Оказывается, вся эта процедура выполняется намного быстрее и занимает примерно 35 мс.

Итак, почему putImageData намного медленнее, чем альтернативы (используя getDataURL или просто перерисовку)? Как я мог ускорить процесс? Есть ли и, если, вообще говоря, лучший способ сохранить состояние холста?

(Все номера измеряются с помощью Firebug из Firefox)

Ответ 1

Просто небольшое обновление о том, что лучший способ сделать это. Я на самом деле написал свою диссертацию бакалавра на High Performance ECMAScript и HTML5 Canvas (pdf, немецкий), поэтому я уже собрал некоторые знания по этой теме. Лучшим решением является использование нескольких элементов холста. Рисование из одного холста на другой холст так же быстро, как рисование сурового изображения на холст. Таким образом, "сохранение" состояния холста так же быстро, как и восстановление его позже, при использовании двух элементов холста.

Этот тестовый тест jsPerf показывает очень разные подходы и их преимущества и недостатки.

Просто для полноты, вот как вы действительно должны это делать:

// setup
var buffer = document.createElement('canvas');
buffer.width = canvas.width;
buffer.height = canvas.height;


// save
buffer.getContext('2d').drawImage(canvas, 0, 0);

// restore
canvas.getContext('2d').drawImage(buffer, 0, 0);

Это решение, в зависимости от браузера, до 5000 раз быстрее, чем тот, который получает upvotes.

Ответ 2

В Firefox 3.6.8 мне удалось обойти медленность putImageData, используя вместо этого toDataUrl/drawImage. Для меня это работает достаточно быстро, чтобы я мог вызвать его при обработке события mousemove:

Чтобы сохранить:

savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")

Для восстановления:

ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)

Ответ 3

Во-первых, вы говорите, что вы измеряете Firebug. Я действительно нахожу, что Firebug значительно замедляет выполнение JS, поэтому вы можете не получать хорошие показатели для производительности.

Что касается putImageData, я подозреваю это, потому что функции принимают большой массив JS, содержащий много объектов Number, все из которых должны быть проверены для диапазона (0..255) и скопированы в собственный буфер холста.

Возможно, когда будут доступны типы WebGL ByteArray, это можно сделать быстрее.

Кажется странным, что base64-декодирование и разжатие данных (с URL-адресами PNG) выполняется быстрее, но это вызывает только одну функцию JS с одной строкой JS, поэтому она использует в основном собственный код и типы.