簡體   English   中英

JavaScript逐像素畫布操作

[英]JavaScript pixel by pixel canvas manipulation

我正在使用一個簡單的Web應用程序,該應用程序將上傳的圖像的顏色簡化為用戶選擇的調色板。 該腳本可以工作,但是要花很長時間才能遍歷整個圖像(對於大圖像來說,這需要幾分鍾的時間),因此需要更改像素。

最初,我是在寫畫布本身,但是我更改了代碼,以便對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);

在整個過程中,網站被完全凍結,因此我什至無法顯示進度欄。 有什么方法可以對此進行優化,從而減少時間嗎?

改進的一個問題或選擇顯然是您的slice函數,該函數將在每次調用它時創建一個新數組,而您不需要這樣做。 我會像這樣更改for循環:

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

無需在每次迭代時對數組進行切片。 (如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/

發現顏色差異

我添加一個答案,因為您使用了非常差的色彩匹配算法。

如果您將每種可能的顏色想象為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 )。 這意味着紅色= 255的強度是紅色= 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));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM