Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

speed up canvas's getImageData

I need to extract chunks of pixels from the canvas and process them. Currently I am calling getImageData multiple times in nested loops.

_getBinaryStringFromCanvas(canvas) {
  let ctx = canvas.getContext('2d')
  let { xMaxBlock, yMaxBlock, blockSize } = this.metrics;
  let results = '';
  for (let y = 0; y < yMaxBlock; y++) {
    for (let x = 0; x < xMaxBlock; x++) {
      let data = ctx.getImageData(x * blockSize, y * blockSize, blockSize, blockSize);
      let digit = this._somehowProcessTheData(data);
      binaryString += digit;
    }
  }
  return binaryString;
}

This is very slow as xMaxBlock and yMaxBlock can be pretty big. Ideally, I would want to do something like this -

_getChunkFromCache(cache, x, y, width, height){
  // need help implementing this function
}

_getBinaryStringFromCanvas(canvas) {
  let ctx = canvas.getContext('2d')
  let { xMaxBlock, yMaxBlock, blockSize } = this.metrics;
  let results = '';
  let cache = ctx.getImageData(0, 0, xMaxBlock * blockSize, yMaxBlock * blockSize);
  for (let y = 0; y < yMaxBlock; y++) {
    for (let x = 0; x < xMaxBlock; x++) {
      let data = this._getChunkFromCache(cache, x * blockSize, y * blockSize, blockSize, blockSize);
      let digit = this._somehowProcessTheData(data);
      binaryString += digit;
    }
  }
  return binaryString;
}

But I can't seem to comprehend the logic required to address the region specified by x, y, width, height in the flat array returned by getImageData.

Any help implementing _getChunkFromCache is highly appreciated.

like image 442
Sayem Shafayet Avatar asked Oct 21 '17 13:10

Sayem Shafayet


2 Answers

Don`t copy, index the array directly.

Getting a subsection of the original data array will need to be a copy as type arrays (The array in ImageData.data is a typed array Uint8ClampedArray) are only one dimensional and can not index a 2D section of another array. Creating a copy will only add more work and increase memory usage, slowing your app even further.

Your best option is for the somehowProcessTheData function to work directly with the image data and just index the array to the bounds you supply.

function somehowProcessTheData(imageData, x, y, w, h){
    var i,j;
    var result = ""; 
    var r,g,b,a;
    const data = imageData.data;

    for(j = 0; j < h; j++){
        var idx = (x + (y + j) * imageData.width) * 4;  // get left most byte index for row at y + j
        for(i = 0; i < w; i++){
             r = data[idx ++];
             g = data[idx ++];
             b = data[idx ++];
             a = data[idx ++];
             // do the processing
        }
     }
     return result;
 }
         

Or

function somehowProcessTheData(imageData, x, y, w, h){
    var i,j;
    var result = ""; 
    const data = imageData.data;

    for(j = 0; j < h; j++){
        var idx = (x + (y + j) * imageData.width) * 4;  
        for(i = 0; i < w; i++){
             // get the red green blue values
             var blah = data[idx] + data[idx + 1] + data[idx + 2];
             // do the processing
             // ...
             // increment the index to the next pixel.
             idx += 4;

        }
     }
     return result;
 }

Then in the calling loop

 let data = this.processTheData(cache, x * blockSize, y * blockSize, blockSize, blockSize);
  

 
like image 181
Blindman67 Avatar answered Oct 01 '22 02:10

Blindman67


You can cache the whole area you're working on. It's still slow but hopefully you won't need to do it often. Below is an example to get an int for each pixel instead of 4 bytes.

const pixels = new Int32Array(ctx.getImageData(0, 0, w, h).data.buffer)
pixels[x + y * w]
like image 33
Rivenfall Avatar answered Oct 01 '22 02:10

Rivenfall