简体   繁体   English

在画布上应用平滑滤镜

[英]Apply smoothing filter on a canvas

I have following canvas that has many small (15x15) blocks: 我有以下具有许多小(15x15)块的画布:

在此处输入图片说明

What I'm trying to do is adding a filter to remove the boundaries between boxes and make the picture a bit more consistent so that the rectangles are not too obvious and are smooth. 我想做的是添加一个过滤器以删除框之间的边界,并使图片更加一致,以使矩形不太明显且平滑。 What filter or algorithm can I use to achieve this goal? 我可以使用哪种过滤器或算法来实现此目标?

Please note that I cannot change the way that I'm creating the canvas and I have to draw a bunch of rectangles in order to make the final shape. 请注意,我无法更改创建画布的方式,必须绘制一堆矩形才能制成最终形状。 And apologies if the question is not clear enough. 如果问题不清楚,我们深表歉意。

Use getImageData to get the data immidiately around any one pixel and melt them together on a temporary canvas. 使用getImageData可以立即获取任何一个像素周围的数据,并将它们融合到一个临时画布上。 End by drawImage on the primary canvas. 在主画布上以drawImage结尾。

 var c = document.body.appendChild(document.createElement("canvas")); var size = c.width = c.height = 50; var tileSize = 5; var padding = 0; var ctx = c.getContext("2d"); c.style.width = c.style.height = "400px"; for (var x = 0; x < size; x += tileSize) { for (var y = 0; y < size; y += tileSize) { ctx.fillStyle = (Math.random() > 0.5 ? "#FF0000" : (Math.random() > 0.5 ? "#00FF00" : '#0000FF')); ctx.fillRect(x + padding, y + padding, tileSize - padding, tileSize - padding); } } function blur(canvas) { var ctx = canvas.getContext("2d"); var tempCanvas = canvas.cloneNode(); var tempCTX = tempCanvas.getContext("2d"); var divideBy = 9; for (var x = 0; x < size; x += 1) { for (var y = 0; y < size; y += 1) { var data = ctx.getImageData(x - 1, y - 1, 3, 3).data; var arr = [0, 0, 0, 1]; for (var dataIndex = 0; dataIndex < data.length; dataIndex += 4) { arr[0] += (data[dataIndex] === 0 ? 0 : data[dataIndex] / divideBy); arr[1] += (data[dataIndex + 1] === 0 ? 0 : data[dataIndex + 1] / divideBy); arr[2] += (data[dataIndex + 2] === 0 ? 0 : data[dataIndex + 2] / divideBy); } arr = arr.map(function(a) { return Math.round(a); }); tempCTX.fillStyle = "rgba(" + (arr.join(',')) + ")"; tempCTX.fillRect(x, y, 1, 1); } } ctx.drawImage(tempCanvas, 0, 0); } //TEST var output = document.body.appendChild(document.createElement("p")); var step = 0; function blurStep() { blur(c); step++; output.innerHTML = "Blur step: " + step; if (step < 20) { setTimeout(blurStep, 1000 / 60); } } setTimeout(blurStep, 1000); 

EDIT 1 - Edge control 编辑1-边缘控制

A slight edit that removes the black borders that gets generated when my function goes outside the existing canvas: 进行轻微的编辑,删除当我的函数超出现有画布之外时生成的黑色边框:

 var output = document.body.appendChild(document.createElement("p")); var c = document.body.appendChild(document.createElement("canvas")); var size = c.width = c.height = 50; var tileSize = 5; var padding = 0; var ctx = c.getContext("2d"); c.style.width = c.style.height = "800px"; for (var x = 0; x < size; x += tileSize) { for (var y = 0; y < size; y += tileSize) { ctx.fillStyle = (Math.random() > 0.5 ? "#FF0000" : (Math.random() > 0.5 ? "#00FF00" : '#0000FF')); ctx.fillRect(x + padding, y + padding, tileSize - padding, tileSize - padding); } } function blur(canvas) { var ctx = canvas.getContext("2d"); var tempCanvas = canvas.cloneNode(); var tempCTX = tempCanvas.getContext("2d"); var divideBy = 9; for (var x = 0; x < size; x += 1) { for (var y = 0; y < size; y += 1) { var X = (x >= 1 ? x - 1 : x); var Y = (y >= 1 ? y - 1 : y); var overflowX = size - X < 3; var overflowY = size - Y < 3; var data = ctx.getImageData(X, Y, 3, 3).data; if (overflowX) { var i = 8; do { data[i] = data[i - 4]; data[i + 1] = data[i - 4 + 1]; data[i + 2] = data[i - 4 + 2]; i += 12; } while (i <= 32); } if (overflowY) { var i = 24; do { data[i] = data[i - 12]; i++; } while (i <= 32); } var arr = [0, 0, 0, 1]; for (var dataIndex = 0; dataIndex < data.length; dataIndex += 4) { arr[0] += (data[dataIndex] === 0 ? 0 : data[dataIndex] / divideBy); arr[1] += (data[dataIndex + 1] === 0 ? 0 : data[dataIndex + 1] / divideBy); arr[2] += (data[dataIndex + 2] === 0 ? 0 : data[dataIndex + 2] / divideBy); } arr = arr.map(function(a) { return Math.round(a); }); tempCTX.fillStyle = "rgba(" + (arr.join(',')) + ")"; tempCTX.fillRect(x, y, 1, 1); } } ctx.drawImage(tempCanvas, 0, 0); } //TEST var step = 0; var steps = 20; function blurStep() { blur(c); step++; output.innerHTML = "Blur step: " + step + "/" + steps; if (step < steps) { requestAnimationFrame(blurStep); } } requestAnimationFrame(blurStep); 

Doing it rather for fun than real helping. 这样做是为了娱乐,而不是真正的帮助。 But you can get basic idea, read on :) 但是您可以获得基本的想法,请继续阅读:)

Generate bitmap, get pure color array (for example imageData), get width of image and multiply it with 4 ([r, g, b, a] is one pixel) ( some docs about manipulation inside canvas ) 生成位图,获取纯色数组(例如imageData),获取图像的宽度并将其乘以4([r,g,b,a]是一个像素)( 有关在画布内部进行操作的一些文档

If you will - you can modify it by doing some "put data" loops and multipying directions by 15 to get this "blocky" style! 如果可以的话,您可以通过执行一些“放置数据”循环并将其乘以15的方向进行修改来获得这种“块状”样式!

Then - do what you want. 然后-做你想做的。 I will do for example purpose: 我将举例说明此目的:

const channelResolution = 4; // if you don't have somehow alpha channel - make it 3.
const lineDistance = imageWidth * channelResolution;
const imageData = [ /* your ImageData from canvas */];

const [r, g, b, a] = [0, 1, 2, 3]; // directions inside channels
const [left, right, up, down] = [
    -channelResolution,
    channelResolution,
    -lineDistance,
    lineDistance]; // directions inside pixels

// return array of channel values in given directions
const scan = (data, scanChannel, scanPixel, scanDirections) => {
    const scanResult = [];

    scanResult.push(data[scanPixel + scanChannel]);
    return scanResult.concat(scanDirections.map(direction => {
        return data[scanPixel + scanChannel + direction];
    }));
};

// mixer filter
const mixChannel = (array) => {
    let sum = 0;
    array.map(channel => sum+=channel);
    return sum / (array.length + 1);
};

// blur edge filter/shader
const blurEdges = () => {
  const resultData = clone(imageData); // (you can do json transformation)
  for(let pointer = 0; pointer < imageData * channelResolution - channelResolution; pointer += channelResolution) {
    const [red, green, blue, alpha] = [
        mixChannel(scan(imageData, r, pointer, [up, left, down, right])),
        mixChannel(scan(imageData, g, pointer, [up, left, down, right])),
        mixChannel(scan(imageData, b, pointer, [up, left, down, right])),
        mixChannel(scan(imageData, a, pointer, [up, left, down, right])),
    ];

    resultData[pointer + r] = red;
    resultData[pointer + g] = green;
    resultData[pointer + b] = blue;
    resultData[pointer + a] = alpha;
  }
  return resultData; // or putImageData
}

There are many many ways to blur. 有很多模糊的方法。 Unfortunately most of them are wrong and result in images that are darker then the original. 不幸的是,其中大多数错误,导致图像比原始图像更暗。 A blur should not lose brightnest 模糊不应失去最亮的效果

I have taken the code from Emil S. Jørgensen and corrected the maths used so that the image brightness is not affected as the image is blurred. 我已经从Emil S.Jørgensen那里获取了代码,并更正了所用的数学运算 ,以使图像模糊不影响图像亮度。

For each channel r,g,b you get the mean of the square of each nearby pixel and use the sqrt of that to set the new blurred pixel 对于每个通道r,g,b,您可以获得每个附近像素的平方的均值,并使用其平方根设置新的模糊像素

So if you had 3 pixels (looking only at the red channel, r1,r2,r3) each in the range 0-255 to get the mean brightness you have to convert the logarithmic channel value to a linear photon count. 因此,如果您有3个像素(仅查看红色通道r1,r2,r3),其范围在0-255之间,以获得平均亮度,则必须将对数通道值转换为线性光子计数。 Get the mean and convert back. 得到平均值并转换回来。 Thus meanR = Math.sqrt*((r1 * r1 + r2 * r2 + r3 * r3)/3); 因此meanR = Math.sqrt*((r1 * r1 + r2 * r2 + r3 * r3)/3); same for green and blue. 绿色和蓝色相同。 Not alpha. 不是Alpha。

The code I changed is a bit of a hack i was going to try fit the important part it in a comment but would not fit 我更改的代码有点hack,我打算尝试将其重要部分放入注释中,但不适合

It is easy to see the difference so you don't even need a side by side. 很容易看到差异,因此您甚至不需要并排。

 var c = document.body.appendChild(document.createElement("canvas")); var size = c.width = c.height = 50; var tileSize = 5; var padding = 0; var ctx = c.getContext("2d"); c.style.width = c.style.height = "400px"; for (var x = 0; x < size; x += tileSize) { for (var y = 0; y < size; y += tileSize) { ctx.fillStyle = (Math.random() > 0.5 ? "#FF0000" : (Math.random() > 0.5 ? "#00FF00" : '#0000FF')); ctx.fillRect(x + padding, y + padding, tileSize - padding, tileSize - padding); } } function blur(canvas) { var r,g,b,i,c; var s = (v)=>Math.sqrt(v); // sqrt of a value var a = () => d[i] * d[i++]; // sqrt pixel channel and step to next var ctx = canvas.getContext("2d"); var tempCanvas = canvas.cloneNode(); var tempCTX = tempCanvas.getContext("2d"); var divideBy = 9; for (var x = 0; x < size; x += 1) { for (var y = 0; y < size; y += 1) { var d = ctx.getImageData(x - 1, y - 1, 3, 3).data; //============================ // Change start by BM67 //============================ c = i = r = g = b = 0; while(i < 36){ if(d[i+3] !== 0){ r += a(); g += a(); b += a(); i ++; c++; } else { i+= 4 } } tempCTX.fillStyle = `rgb(${s(r/c)|0},${s(g/c)|0},${s(b/c)|0})`; //============================ // End change by BM67 //============================ tempCTX.fillRect(x, y, 1, 1); } } ctx.drawImage(tempCanvas, 0, 0); } //TEST var output = document.body.appendChild(document.createElement("p")); var step = 0; function blurStep() { blur(c); step++; output.innerHTML = "Blur step: " + step; if (step < 20) { setTimeout(blurStep, 1000 / 60); } } setTimeout(blurStep, 1000); 

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

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