Каков наилучший способ установить один пиксель на холсте HTML5? - PullRequest
168 голосов
/ 04 февраля 2011

В HTML5 Canvas нет метода явной установки одного пикселя.

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

Другим способом может быть создание небольшого ImageData объекта и использование:

context.putImageData(data, x, y)

чтобы поставить его на место.

Кто-нибудь может описать эффективный и надежный способ сделать это?

Ответы [ 14 ]

268 голосов
/ 04 февраля 2011

Есть два лучших претендента:

  1. Создайте данные изображения 1 × 1, установите цвет и putImageData в месте:

    var id = myContext.createImageData(1,1); // only do this once per page
    var d  = id.data;                        // only do this once per page
    d[0]   = r;
    d[1]   = g;
    d[2]   = b;
    d[3]   = a;
    myContext.putImageData( id, x, y );     
    
  2. Используйте fillRect() для рисования пикселя (проблем с наложением не должно быть):

    ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
    ctx.fillRect( x, y, 1, 1 );
    

Вы можете проверить их скорость здесь: http://jsperf.com/setting-canvas-pixel/9 или здесь https://www.measurethat.net/Benchmarks/Show/1664/1

Я рекомендую тестировать браузеры, которые вас интересуют, на максимальной скорости. По состоянию на июль 2017 года fillRect() на 5-6 раз быстрееFirefox v54 и Chrome v59 (Win7x64).

Другие, более глупые альтернативы:

  • с использованием getImageData()/putImageData() на всем холсте;это примерно в 100 раз медленнее, чем другие параметры.

  • создание собственного изображения с использованием URL-адреса данных и использование drawImage() для его отображения:

    var img = new Image;
    img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a);
    // Writing the PNGEncoder is left as an exercise for the reader
    
  • создаем еще один img или холст, заполненный всеми пикселями, которые вы хотите, и используйте drawImage(), чтобы перетаскивать только тот пиксель, который вы хотите.Вероятно, это будет очень быстро, но есть ограничение, которое необходимо для предварительного вычисления необходимых пикселей.

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

15 голосов
/ 04 февраля 2011

Я не учел fillRect(), но ответы подтолкнули меня к сравнению с putImage().

Помещение 100 000 случайно окрашенных пикселей в случайные места с Chrome 9.0.597.84 на (старый) MacBook Pro занимает менее 100 мс при putImage(), но почти 900 мс при использовании fillRect(). (Контрольный код на http://pastebin.com/4ijVKJcC).

Если вместо этого я выберу один цвет вне петель и просто нанесу этот цвет в случайных местах, putImage() потребует 59 мс против 102 мс для fillRect().

Похоже, что большая часть различий связана с генерацией и анализом спецификации цвета CSS в синтаксисе rgb(...).

С другой стороны, размещение необработанных значений RGB прямо в блок ImageData не требует обработки или разбора строк.

11 голосов
/ 29 февраля 2016
function setPixel(imageData, x, y, r, g, b, a) {
    index = (x + y * imageData.width) * 4;
    imageData.data[index+0] = r;
    imageData.data[index+1] = g;
    imageData.data[index+2] = b;
    imageData.data[index+3] = a;
}
7 голосов
/ 17 июня 2013

Поскольку разные браузеры предпочитают разные методы, возможно, было бы целесообразно провести меньший тест со всеми тремя методами в рамках процесса загрузки, чтобы выяснить, какой из них лучше всего использовать, а затем использовать его во всем приложении?1001 *

4 голосов
/ 08 июля 2018

Один метод, который не был упомянут, - это использование getImageData, а затем putImageData.
Этот метод хорош, если вы хотите быстро нарисовать много за один раз.
http://next.plnkr.co/edit/mfNyalsAR2MWkccr

  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');
  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);
  var id = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
  var pixels = id.data;

    var x = Math.floor(Math.random() * canvasWidth);
    var y = Math.floor(Math.random() * canvasHeight);
    var r = Math.floor(Math.random() * 256);
    var g = Math.floor(Math.random() * 256);
    var b = Math.floor(Math.random() * 256);
    var off = (y * id.width + x) * 4;
    pixels[off] = r;
    pixels[off + 1] = g;
    pixels[off + 2] = b;
    pixels[off + 3] = 255;

  ctx.putImageData(id, 0, 0);
4 голосов
/ 08 октября 2015

Это кажется странным, но, тем не менее, HTML5 поддерживает рисование линий, кругов, прямоугольников и многих других базовых фигур, в нем нет ничего подходящего для рисования базовой точки. Единственный способ сделать это - смоделировать точку тем, что у вас есть.

Таким образом, в основном есть 3 возможных решения:

  • нарисовать точку в виде линии
  • точка рисования в виде многоугольника
  • нарисовать точку в виде круга

У каждого из них есть свои недостатки


Line

function point(x, y, canvas){
  canvas.beginPath();
  canvas.moveTo(x, y);
  canvas.lineTo(x+1, y+1);
  canvas.stroke();
}

Имейте в виду, что мы движемся в юго-восточном направлении, и если это край, может возникнуть проблема. Но вы также можете рисовать в любом другом направлении.


Прямоугольник

function point(x, y, canvas){
  canvas.strokeRect(x,y,1,1);
}

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

function point(x, y, canvas){
  canvas.fillRect(x,y,1,1);
}

круг


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

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.stroke();
}

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

function point(x, y, canvas){
  canvas.beginPath();
  canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
  canvas.fill();
}

Проблемы со всеми этими решениями:

  • Трудно отслеживать все точки, которые вы собираетесь нарисовать.
  • при увеличении выглядит ужасно.

Если вам интересно: «Какой лучший способ нарисовать точку? », я бы пошел с заполненным прямоугольником. Вы можете увидеть мой jsperf здесь со сравнительными тестами .

4 голосов
/ 04 февраля 2011

А как насчет прямоугольника? Это должно быть более эффективным, чем создание объекта ImageData.

2 голосов
/ 04 февраля 2011

Нарисуйте прямоугольник, как сказал sdleihssirhc!

ctx.fillRect (10, 10, 1, 1);

^ - следует нарисовать прямоугольник 1x1 в x: 10, y: 10

1 голос
/ 14 марта 2013

Хм, вы также можете просто сделать линию шириной 1 пиксель длиной 1 пиксель и заставить ее направление двигаться вдоль одной оси.

            ctx.beginPath();
            ctx.lineWidth = 1; // one pixel wide
            ctx.strokeStyle = rgba(...);
            ctx.moveTo(50,25); // positioned at 50,25
            ctx.lineTo(51,25); // one pixel long
            ctx.stroke();
1 голос
/ 12 марта 2013

Чтобы завершить Phrogz очень подробный ответ, существует критическая разница между fillRect() и putImageData().
Первый использует контекст для рисования над с помощью , добавляя прямоугольник (НЕ пиксель), используя значение fillStyle альфа И контекст globalAlpha и матрица преобразования , строчные буквы и т. Д.
Вторая заменяет весь набор пикселей (возможно, один, но почему?)
Результат отличается, как вы можете видеть на jsperf .


Никто не хочет устанавливать один пиксель за раз (то есть рисовать его на экране).Вот почему нет конкретного API для этого (и это правильно).
С точки зрения производительности, если целью является создание изображения (например, программного обеспечения для трассировки лучей), вы всегда хотите использовать массив, полученный с помощью getImageData(), который является оптимизированным Uint8Array.Затем вы звоните putImageData() ОДИН РАЗ или несколько раз в секунду, используя setTimeout/seTInterval.

...