使用canvas,getImageData和Web Worker一次为图像采样一个图块

时间:2015-04-11 20:48:22

标签: javascript html5 image canvas web-worker

我正在尝试构建一个简单的基于HTML5画布的图像处理器,它采用图像并生成图像的平铺版本,每个图块是底层图像区域的平均颜色。

这很容易在Web Worker的上下文之外进行,但是我想使用worker来阻止ui处理线程。 Uint8ClampedArray形式的数据采取让我头疼如何处理瓦片。

下面是一个插图,展示了我到目前为止所做的以及它是如何工作的。

http://plnkr.co/edit/AiHmLM1lyJGztk8GHrso?p=preview

相关代码位于worker.js

这是:

onmessage = function (e) {
    var i,
        j = 0,
        k = 0,
        data = e.data,
        imageData = data.imageData,
        tileWidth = Math.floor(data.tileWidth),
        tileHeight = Math.floor(data.tileHeight),
        width = imageData.width,
        height = imageData.height,
        tile = [],
        len = imageData.data.length,
        offset,
        processedData = [],
        tempData = [],
        timesLooped = 0,
        tileIncremented = 1;

    function sampleTileData(tileData) {
        var blockSize = 20, // only visit every x pixels
            rgb = {r:0,g:0,b:0},
            i = -4,
            count = 0,
            length = tileData.length;

            while ((i += blockSize * 4) < length) {
                if (tileData[i].r !== 0 && tileData[i].g !== 0 && tileData[i].b !== 0) {
                    ++count;
                    rgb.r += tileData[i].r;
                    rgb.g += tileData[i].g;
                    rgb.b += tileData[i].b;
                }
            }

            // ~~ used to floor values
            rgb.r = ~~(rgb.r/count);
            rgb.g = ~~(rgb.g/count);
            rgb.b = ~~(rgb.b/count);

            processedData.push(rgb);
    }

    top:
    for (; j <= len; j += (width * 4) - (tileWidth * 4), timesLooped++) {

        if (k === (tileWidth * 4) * tileHeight) {
            k = 0;
            offset = timesLooped - 1 < tileHeight ? 4 : 0;
            j = ((tileWidth * 4) * tileIncremented) - offset;
            timesLooped = 0;
            tileIncremented++;
            sampleTileData(tempData);
            tempData = [];
            //console.log('continue "top" loop for new tile');
            continue top;
        }

        for (i = 0; i < tileWidth * 4; i++) {
            k++;
            tempData.push({r: imageData.data[j+i], g: imageData.data[j+i+1], b: imageData.data[j+i+2], a: imageData.data[j+i+3]});
        }

        //console.log('continue "top" loop for new row per tile');

    }

    postMessage(processedData);
};

我确信从标记的for循环开始,我有更好的方法来完成我正在尝试做的事情。因此,非常感谢任何替代方法或建议。

更新
我采取了不同的方法来解决这个问题:

http://jsfiddle.net/TunMn/425/

关闭,但没有。

我知道问题是什么,但我不知道如何去修改它。再次,任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:1)

方法1:手动计算每个平铺的平均值

您可以尝试以下方法:

  • 只需要阅读,以后可以使用硬件加速进行更新
  • 对每一行使用异步调用(如果图像非常宽,则使用平铺)

这给出了准确的结果,但速度较慢,取决于CORS限制。

实施例

您可以在下面看到原始图像闪烁。这表明异步方法有效,因为它允许UI在处理块中的块时进行更新。

window.onload = function() {
  var img = document.querySelector("img"),
      canvas = document.querySelector("canvas"),
      ctx = canvas.getContext("2d"),
      w = img.naturalWidth, h = img.naturalHeight,
      
      // store average tile colors here:
      tileColors = [];
  
  // draw in image
  canvas.width = w; canvas.height = h;
  ctx.drawImage(img, 0, 0);
  
  // MAIN CALL: calculate, when done the callback function will be invoked
  avgTiles(function() {console.log("done!")});
  
  // The tiling function
  function avgTiles(callback) {
  
    var cols = 8,          // number of tiles (make sure it produce integer value
        rows = 8,          //  for tw/th below:)
        tw = (w / cols)|0, // pixel width/height of each tile
        th = (h / rows)|0,
        x = 0, y = 0;
    
    (function process() {  // for async processing
    
      var data, len, count, r, g, b, i;
      
      while(x < cols) {    // get next tile on x axis
        r = g = b = i = 0;
        data = ctx.getImageData(x * tw, y * th, tw, th).data;  // single tile
        len = data.length;
        count = len / 4;
        while(i < len) {   // calc this tile's color average
          r += data[i++];  // add values for each component
          g += data[i++];
          b += data[i++];
          i++
        }
        
        // store average color to array, no need to write back at this point
        tileColors.push({
          r: (r / count)|0,
          g: (g / count)|0, 
          b: (b / count)|0
        });

        x++;               // next tile
      }
      y++;                 // next row, but do an async break below:
      
      if (y < rows) {
        x = 0;
        setTimeout(process, 9);  // call it async to allow browser UI to update
      }
      else {
        
        // draw tiles with average colors, fillRect is faster than setting each pixel:
        for(y = 0; y < rows; y++) {
          for(x = 0; x < cols; x++) {
            var col = tileColors[y * cols + x];   // get stored color
            ctx.fillStyle = "rgb(" + col.r + "," + col.g + "," + col.b + ")";
            ctx.fillRect(x * tw, y * th, tw, th);
          }
        }
        
        // we're done, invoke callback
        callback()
      }
    })();   // to self-invoke process()
  }
};
<canvas></canvas>
<img src="http://i.imgur.com/X7ZrRkn.png" crossOrigin="anonymous">

方法2:让浏览器完成工作

我们也可以让浏览器利用插值和采样来完成整个工作。

当浏览器缩小图像时,它将计算每个新像素的平均值。如果我们在向上扩展时关闭线性插值,我们将把每个平均像素作为方块:

  • 以产生多个图块的比率缩小图像作为像素数
  • 关闭图像平滑
  • 将小图像缩小至所需尺寸

这比第一种方法快很多倍,您将能够使用受CORS限制的图像。请注意,它可能不如第一种方法准确,但是,可以通过在几个步骤中缩小图像来提高精度,每个步长只有一半。

实施例

window.onload = function() {
  var img = document.querySelector("img"),
      canvas = document.querySelector("canvas"),
      ctx = canvas.getContext("2d"),
      w = img.naturalWidth, h = img.naturalHeight;
  
  // draw in image
  canvas.width = w; canvas.height = h;
  
  // scale down image so number of pixels represent number of tiles,
  // here use two steps so we get a more accurate result:
  ctx.drawImage(img, 0, 0, w, h, 0, 0, w*0.5, h*0.5);    // 50%
  ctx.drawImage(canvas, 0, 0, w*0.5, h*0.5, 0, 0, 8, 8); // 8 tiles
  
  // turn off image-smoothing
  ctx.imageSmoothingEnabled = 
  ctx.msImageSmoothingEnabled = 
  ctx.mozImageSmoothingEnabled = 
  ctx.webkitImageSmoothingEnabled = false;
  
  // scale image back up
  ctx.drawImage(canvas, 0, 0, 8, 8, 0, 0, w, h);
};
<canvas></canvas>
<img src="http://i.imgur.com/X7ZrRkn.png" crossOrigin="anonymous">