【问题标题】:JavaScript pixel by pixel canvas manipulationJavaScript 逐像素画布操作
【发布时间】:2019-12-12 05:37:18
【问题描述】:

我正在开发一个简单的网络应用程序,它将上传图像的颜色简化为用户选择的调色板。该脚本有效,但循环整个图像需要很长时间(对于大图像,它需要几分钟),改变像素。

最初,我正在写入画布本身,但我更改了代码,以便对ImageData 对象进行更改,并且画布仅在脚本末尾更新。但是,这并没有太大的区别。

// User selects colours:
colours = [[255,45,0], [37,36,32], [110,110,105], [18,96,4]];

function colourDiff(colour1, colour2) {
    difference = 0
    difference += Math.abs(colour1[0] - colour2[0]);
    difference += Math.abs(colour1[1] - colour2[1]);
    difference += Math.abs(colour1[2] - colour2[2]);
    return(difference);
}

function getPixel(imgData, index) {
    return(imgData.data.slice(index*4, index*4+4));
}

function setPixel(imgData, index, pixelData) {
    imgData.data.set(pixelData, index*4);
}

data = ctx.getImageData(0,0,canvas.width,canvas.height);
for(i=0; i<(canvas.width*canvas.height); i++) {
    pixel = getPixel(data, i);
    lowestDiff = 1024;
    lowestColour = [0,0,0];
    for(colour in colours) {
        colour = colours[colour];
        difference = colourDiff(colour, pixel);
        if(lowestDiff < difference) {
            continue;
        }
        lowestDiff = difference;
        lowestColour = colour;
    }
    console.log(i);
    setPixel(data, i, lowestColour);
}
ctx.putImageData(data, 0, 0);

在整个过程中,网站完全冻结,所以我什至无法显示进度条。有什么办法可以优化它以减少时间吗?

【问题讨论】:

    标签: javascript jquery canvas html5-canvas


    【解决方案1】:

    无需每次迭代都对数组进行切片。 (正如 niklas 已经说过的那样)。

    我会遍历数据数组,而不是遍历画布维度并直接编辑数组。

    for(let i = 0; i < data.length; i+=4) { // i+=4 to step over each r,g,b,a pixel
      let pixel = getPixel(data, i);
      ...
      setPixel(data, i, lowestColour);
    }
    
    function setPixel(data, i, colour) {
        data[i] = colour[0];
        data[i+1] = colour[1];
        data[i+2] = colour[2];
    }
    
    function getPixel(data, i) {
        return [data[i], data[i+1], data[i+2]];
    }
    

    此外,如果您打开了控制台,console.log 可以让浏览器崩溃。如果您的图像是 1920 x 1080,那么您将登录控制台 2,073,600 次。

    您还可以将所有处理工作交给 Web Worker 以获得最终的线程性能。例如。 https://jsfiddle.net/pnmz75xa/

    【讨论】:

      【解决方案2】:

      一个问题或改进选项显然是您的slice 函数,每次调用它都会创建一个新数组,您不需要这个。我会像这样更改 for 循环:

      for y in canvas.height {
        for x in canvas.width {
          //directly alter the canvas' pixels
        }
      }
      

      【讨论】:

        【解决方案3】:

        发现颜色差异

        我正在添加一个答案,因为您使用了非常糟糕的颜色匹配算法。

        如果您将每种可能的唯一颜色想象为 3D 空间中的一个点,则最好确定一种颜色与另一种颜色的匹配程度。红色、绿色和蓝色值代表 x,y,z 坐标。

        然后您可以使用一些基本几何图形来确定从一种颜色到另一种颜色的距离。

        // the two colours as bytes 0-255
        const colorDist = (r1, g1, b1, r2, g2, b2) => Math.hypot(r1 - r2, g1 - g2, b1 - b2);
        

        还需要注意的是,通道值 0-255 是一个压缩值,实际强度接近该值的平方 (channelValue ** 2.2)。这意味着 red = 255 的强度是 red = 1 的 65025 倍

        以下函数是两种颜色之间色差的近似值。避免使用 Math.hypot 函数,因为它非常慢。

         const pallet = [[1,2,3],[2,10,30]]; // Array of arrays representing rgb byte 
                                             // of the colors you are matching
         function findClosest(r,g,b) {  
             var closest;
             var dist = Infinity;
             r *= r;
             g *= g;
             b *= b;
             for (const col of pallet) {
                const d = ((r - col[0] * col[0]) + (g - col[1] * col[1]) + (b - col[2] * col[2])) ** 0.5;
                if (d < dist) {
                    if (d === 0) { // if same then return result
                        return col;
                    }
                    closest = col;
                    dist = d;
                }
            }
            return closest;
          }
        

        至于性能,您最好的选择是通过网络工作者,或者使用 webGL 进行实时转换。

        如果您想保持简单,以防止代码阻塞页面,请使用计时器将作业切割成更小的片段,以留出页面喘息的空间。

        该示例使用setTimeoutperformance.now() 进行10ms 的切片,让其他页面事件和渲染来做这些事情。它返回一个承诺,当所有像素都被处理时解决

          function convertBitmap(canvas, maxTime) { // maxTime in ms (1/1000 second)
             return new Promise(allDone => {
                 const ctx = canvas.getContext("2d");
                 const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
                 const data = pixels.data;
                 var idx = data.length / 4;
                 processPixels(); // start processing
                 function processPixels() {
                     const time = performance.now();
                     while (idx-- > 0) {
                         if (idx % 1024) { // check time every 1024 pixels
                              if (performance.now() - time > maxTime) {
                                  setTimeout(processPixels, 0);
                                  idx++;
                                  return;
                              }
                         }
                         let i = idx * 4;
                         const col = findClosest(data[i], data[i + 1], data[i + 2]);
                         data[i++] = col[0];
                         data[i++] = col[1];
                         data[i] = col[2];
                     }
                     ctx.putImageData(pixels, 0, 0);
                     allDone("Pixels processed");
                 }
              });
          }
        
          // process pixels in 10ms slices. 
          convertBitmap(myCanvas, 10).then(mess => console.log(mess));
              
        
              
        
          
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-06-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-29
          • 1970-01-01
          • 2013-09-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多