Смешивание ImageData вручную
У меня есть следующая задача, которую я пытаюсь выполнить наиболее эффективным способом: у меня есть различное количество картинок разного размера в виде пиксельных массивов, которые мне нужно добавить к холсту пиксель за пикселем. Значение каждого пикселя должно быть добавлено к ImageData холста, чтобы в результате получилось сочетание двух или более изображений.
Мое текущее решение состоит в том, чтобы извлечь ImageData из места, где изображение должно быть смешано с размером изображения. Затем я добавляю ImageData изображения в извлеченные ImageData и копирую его обратно в то же место.
В некотором смысле это ручная реализация холста globalCompositeOperation "Зажигалка".
"use strict";
let canvas = document.getElementById("canvas");
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let ctx = canvas.getContext("2d");
ctx.fillStyle="black";
ctx.fillRect(0, 0, width, height);
let imageData = ctx.getImageData(0,0,width,height);
let data = imageData.data;
function random(min, max) {
let num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}
function createColorArray(size, color) {
let arrayLength = (size*size)*4;
let array = new Uint8ClampedArray(arrayLength);
for (let i = 0; i < arrayLength; i+=4) {
switch (color) {
case 1:
array[i+0] = 255; // r
array[i+1] = 0; // g
array[i+2] = 0; // b
array[i+3] = 255; // a
break;
case 2:
array[i+0] = 0; // r
array[i+1] = 255; // g
array[i+2] = 0; // b
array[i+3] = 255; // a
break;
case 3:
array[i+0] = 0; // r
array[i+1] = 0; // g
array[i+2] = 255; // b
array[i+3] = 255; // a
}
}
return array;
}
function picture() {
this.size = random(10, 500);
this.x = random(0, width);
this.y = random(0, height);
this.color = random(1,3);
this.colorArray = createColorArray(this.size, this.color);
}
picture.prototype.updatePixels = function() {
let imageData = ctx.getImageData(this.x, this.y, this.size, this.size);
let data = imageData.data;
for (let i = 0; i < data.length; ++i) {
data[i]+=this.colorArray[i];
}
ctx.putImageData(imageData, this.x, this.y);
}
let pictures = [];
let numPictures = 50;
for (let i = 0; i < numPictures; ++i) {
let pic = new picture();
pictures.push(pic);
}
function drawPictures() {
for (let i = 0; i < pictures.length; ++i) {
pictures[i].updatePixels();
}
}
drawPictures();
<!DOCTYPE html>
<html>
<head>
<title>...</title>
<style type="text/css">
body {margin: 0px}
#canvas {position: absolute}
</style>
</head>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
<script type="text/javascript" src="js\script.js"></script>
</body>
</html>
Это решение отлично работает, но очень медленно. Я не знаю, можно ли сделать попиксельное смешивание очень эффективным, но одна из причин низкой производительности может заключаться в том, что мне нужно получать ImageData и вставлять его обратно каждый раз, когда новое изображение смешивается с канвой.
Поэтому основной вопрос заключается в том, как я могу получить весь холст ImageData один раз в начале, а затем смотреть правильные пиксели для обновления на основе местоположения и размера каждого изображения, которое необходимо смешать с холстом, и, наконец, поместить обновленные ImageData обратно на холст? Также приветствуются любые другие идеи о том, как сделать смешивание более эффективным.
1 ответ
Используйте методы массива.
Самый быстрый способ заполнить массив с помощью Array.fill
функция
const colors = new Uint32Array([0xFF0000FF,0xFF00FF00,0xFFFF00]); // red, green, blue
function createColorArray(size, color) {
const array32 = new Uint32Array(size*size);
array32.fill(colors[color]);
return array32;
}
Быстрый зажим с добавлением |
Если вы добавляете 0xFF к любому каналу и 0 к другим, вы можете использовать | и 32-битный массив. Для updatePixels
функция
var imageData = ctx.getImageData(this.x, this.y, this.size, this.size);
var data = new Uint32Array(imageData.data.buffer);
var i = 0;
var pic = this.colorArray; // use a local scope for faster access
do{
data[i] |= pic[i] ; // only works for 0 and FF chanel values
}while(++i < data.length);
ctx.putImageData(imageData, this.x, this.y);
Побитовый или |
аналогично арифметическому сложению и может использоваться для увеличения значений с использованием 32-битных слов. Значения будут зафиксированы как часть побитовой операции.
// dark
var red = 0xFF000088;
var green = 0xFF008800;
var yellow = red | green; // 0xFF008888
Есть много других способов использовать 32-битные операции для увеличения производительности, если вы используете только 1 или 2 оператора. Больше и вам лучше использовать байты.
Вы также можете добавить, если вы знаете, что каждый канал не будет переполнен немного
a = 0xFF101010; // very dark gray
b = 0xFF000080; // dark red
// non overflowing add
c = a + b; // result is 0xFF000090 correct
// as 0x90 + 0x80 will overflow = 0x110 the add will not work
c += b; // result overflows bit to green 0xFF000110 // incorrect
Uint8Array
В Uint8ClampedArray
Uint8Array
немного быстрее чем Uint8ClampedArray
поскольку зажим пропускается для Uint8Array
так что используйте его, если вам не нужно фиксировать результат. Также инт typedArray
Вам не нужно округлять значения при присваивании им.
var data = Uint8Array(1);
data[0] = Math.random() * 255; // will floor for you
var data = Uint8Array(1);
data[0] = 256; // result is 0
data[0] = -1; // result is 255
var data = Uint8ClampedArray(1);
data[0] = 256; // result is 255
data[0] = -1; // result is 0
Вы можете скопировать данные из массива в массив
var imageDataSource = // some other source
var dataS = new Uint32Array(imageData.data.buffer);
var imageData = ctx.getImageData(this.x, this.y, this.size, this.size);
var data = new Uint32Array(imageData.data.buffer);
data.set(dataS); // copies all data
// or to copy a row of pixels
// from coords
var x = 10;
var y = 10;
var width = 20; // number of pixels to copy
// to coords
var xx = 30
var yy = 30
var start = y * this.size + x;
data.set(dataS.subArray(start, start + width), xx + yy * this.size);
Не сбрасывать буферы
Не продолжайте получать данные пикселей, если они не нужны. Если это не меняется между putImageData
а также getImageData
тогда нет необходимости снова получать данные. Лучше сохранить один буфер, чем постоянно создавать новый. Это также снимет нагрузку на память и уменьшит нагрузку на GC.
Вы уверены, что не можете использовать графический процессор
И вы можете выполнять широкий спектр операций с пиксельными данными, используя глобальные составные операции. Сложение, вычитание, умножение, деление, инвертирование. Это намного быстрее, и пока в вашем коде я не вижу причин, по которым вам нужен доступ к данным пикселей.