简体   繁体   English

使用canvas,getImageData和Web Worker一次对一个图块进行采样

[英]sampling an image a tile at a time using canvas, getImageData and a Web Worker

I am attempting to build a simple HTML5 canvas based image processor that takes an image and generates a tiled version of it with each tile being the average color of the underlying image area. 我正在尝试构建一个简单的基于HTML5画布的图像处理器,该处理器将获取图像并生成其平铺版本,每个图块是基础图像区域的平均颜色。

This is easy enough to do outside the context of a Web Worker but I'd like to use a worker so as not to block the ui processing thread. 这很容易在Web Worker的上下文之外进行,但是我想使用worker以避免阻塞ui处理线程。 The Uint8ClampedArray form the data takes is giving me a headache with regards to how to process it tile by tile. 数据采用的Uint8ClampedArray形式让我头疼如何如何逐块处理它。

Below is a plunk demonstrating what I've done so far and how it's not working. 下面是一个小小的例子,展示了我到目前为止所做的事情以及它是如何工作的。

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

The relevant code is in worker.js 相关代码在worker.js中

Here it is: 这里是:

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);
};

I'm sure there's a better way of accomplishing what I'm trying to do starting at the labeled for loop. 我敢肯定,从标记的for循环开始,有一种更好的方法可以完成我想做的事情。 So any alternative methods or suggestions would be much appreciated. 因此,任何替代方法或建议将不胜感激。

Update: 更新:
I've taken a different approach to solving this: 我采取了另一种方法来解决这个问题:

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

Close, but no. 关闭,但没有。

I know what the problem is but I have no idea how to go about amending it. 我知道问题出在哪里,但是我不知道如何去解决它。 Again, any help would be much appreciated. 同样,任何帮助将不胜感激。

Approach 1: Manually calculating average per tile 方法1:手动计算每个图块的平均值

Here is one approach you can try: 您可以尝试以下一种方法:

  • There is only need for reading, update can be done later using HW acceleration 只需要阅读,以后可以使用硬件加速来完成更新
  • Use async calls for every row (or tile if the image is very wide) 对每一行使用异步调用(如果图像非常宽,则使用图块)

This gives an accurate result but is slower and depends on CORS restrictions. 这样可以得出准确的结果,但速度较慢,并且取决于CORS限制。

Example

You can see the original image for a blink below. 您可以在下面看到原始图像的闪烁。 This shows the asynchronous approach works as it allows the UI to update while processing the tiles in chunks. 这显示了异步方法的工作原理,因为它允许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"> 

Approach 2: Letting the browser do the job 方法2:让浏览器完成工作

We can also let the browser do the whole job exploiting interpolation and sampling. 我们还可以让浏览器利用内插和采样来完成整个工作。

When the browser scales an image down it will calculate the average for each new pixel. 当浏览器缩小图像时,它将计算每个新像素的平均值。 If we then turn off linear interpolation when we scale up we will get each of those average pixels as square blocks: 如果然后在放大时关闭线性插值,则将得到每个平均像素为正方形块:

  • Scale down image at a ratio producing number of tiles as number of pixels 按比例缩小图像,产生的瓦片数为像素数
  • Turn off image smoothing 关闭图像平滑
  • Scale the small image back up to the desired size 将小图像缩放回所需大小

This will be many times faster than the first approach, and you will be able to use CORS-restricted images. 这将比第一种方法快许多倍,并且您将能够使用受CORS限制的图像。 Just note it may not be as accurate as the first approach, however, it is possible to increase the accuracy by scaling down the image in several step, each half the size. 请注意,它可能不如第一种方法准确,但是,可以通过分几步缩小图像(每幅大小的一半)来提高准确性。

Example

 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"> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM