在画布上应用平滑滤镜

时间:2017-09-29 14:11:52

标签: javascript html5 canvas

我有以下画布有许多小(15x15)块:

enter image description here

我要做的是添加一个过滤器来移除框之间的边界,使图片更加一致,这样矩形就不会太明显而且很光滑。我可以使用什么过滤器或算法来实现这一目标?

请注意,我无法改变我创建画布的方式,我必须绘制一堆矩形才能制作最终形状。如果问题不够明确,请道歉。

3 个答案:

答案 0 :(得分:2)

使用getImageData在任意一个像素周围临时获取数据,并将它们在临时画布上融合在一起。在主画布上以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);
&#13;
&#13;
&#13;

编辑1 - 边缘控制

轻微编辑,删除当我的函数超出现有画布时生成的黑色边框:

&#13;
&#13;
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);
&#13;
&#13;
&#13;

答案 1 :(得分:2)

这样做比起真正的帮助更有趣。但是你可以得到基本的想法,请继续阅读:)

生成位图,获取纯色数组(例如imageData),获取图像宽度并将其乘以4([r,g,b,a]为一个像素)(some docs about manipulation inside canvas

如果你愿意 - 你可以通过做一些“放置数据”循环并将方向乘以15来修改它以获得这种“块状”样式!

然后 - 做你想做的事。我会这样做的目的:

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
}

答案 2 :(得分:1)

有许多模糊方法。不幸的是,大多数都是错误的,导致图像比原始图像更暗。模糊不应该失去光明

我已经从 EmilS.Jørgensen中获取了代码并更正了所使用的数学,以便在图像模糊时图像亮度不受影响。

对于每个通道r,g,b,您可以得到每个邻近像素的平方均值,并使用其中的sqrt来设置新的模糊像素

因此,如果您在0-255范围内有3个像素(仅查看红色通道,r1,r2,r3)以获得平均亮度,则必须将对数通道值转换为线性光子计数。得到均值并转换回来。因此绿色和蓝色meanR = Math.sqrt*((r1 * r1 + r2 * r2 + r3 * r3)/3);相同。不是alpha。

我改变的代码有点像黑客,我会在评论中尝试适合它的重要部分,但不适合

很容易看出差异,所以你甚至不需要并排。

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