手动混合ImageData

时间:2017-10-16 11:58:15

标签: javascript arrays canvas getimagedata putimagedata

我有一个以下任务,我试图以最有效的方式实现:我有不同数量的不同大小的图片作为像素阵列,我需要逐像素地添加到画布。每个像素的值必须添加到画布的ImageData中,以便结果是两个或多个图像的混合。

我目前的解决方案是从图片需要与图片大小混合的位置检索ImageData。然后我将图片的ImageData添加到检索到的ImageData并将其复制回相同的位置。

从某种意义上说,这是canvas globalCompositeOperation" light"的手动实现。



"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();
&#13;
<!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>
&#13;
&#13;
&#13;

此解决方案工作正常,但速度很慢。我不知道逐像素混合是否可以非常高效,但性能降低的一个原因可能是我需要获取ImageData并在每次将新图像混合到画布时将其放回。

因此,主要的问题是如何在开始时获得整个画布ImageData,然后根据需要混合到画布中的每张图片的位置和大小来查看正确的像素更新,最后将更新的ImageData放回画布?此外,非常感谢任何其他关于如何提高混合效率的想法。

1 个答案:

答案 0 :(得分: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

只要您只使用1或2个运算符,还有许多其他方法可以使用32位运算来提高性能。更多,你最好使用字节。

如果您知道每个频道都不会溢出

,您也可以添加
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 V Uint8ClampedArray

Uint8Array略快于Uint8ClampedArray,因为Uint8Array会跳过钳位,因此如果您不需要钳制结果,请使用它。此外,int 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);

不要转储缓冲区

如果不需要,请不要继续提取像素数据。如果它在putImageDatagetImageData之间没有变化,则无需再次获取数据。保留一个缓冲区比连续创建一个缓冲区更好。这也将减轻内存压力并减少GC上的工作量。

您确定无法使用GPU

您可以使用全局复合操作对像素数据执行各种操作。加法,减法,乘法,除法,反转这些速度要快得多,而且到目前为止我的代码中没有理由说你需要访问像素数据。