首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >手动混合ImageData

手动混合ImageData
EN

Stack Overflow用户
提问于 2017-10-16 19:58:15
回答 1查看 408关注 0票数 0

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

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

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

代码语言:javascript
复制
"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();
代码语言:javascript
复制
<!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放回画布中?此外,任何其他关于如何使混合更有效的想法都是非常感谢的。

EN

回答 1

Stack Overflow用户

发布于 2017-10-16 21:28:13

使用数组方法。

填充数组的最快方法是使用Array.fill函数

代码语言:javascript
复制
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函数

代码语言:javascript
复制
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位字递增数值。这些值将作为按位运算的一部分被钳制。

代码语言:javascript
复制
// dark
var red = 0xFF000088;
var green = 0xFF008800;
var yellow = red | green; // 0xFF008888

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

如果您知道每个通道不会有一点溢出,也可以添加

代码语言:javascript
复制
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

Uint8ArrayUint8ClampedArray略快,因为跳过了对Uint8Array的钳制,所以如果不需要钳制结果,请使用它。此外,对整型typedArray赋值时,不需要对其进行四舍五入。

代码语言:javascript
复制
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

您可以将数据从一个阵列复制到另一个阵列

代码语言:javascript
复制
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吗

您还可以使用全局复合操作对像素数据执行各种操作。相加、相减、相乘、相除、反转要快得多,到目前为止,在你的代码中,我看不出你为什么需要访问像素数据。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46769789

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档