使用画布制作图片滤镜

发布于 2023-06-24 14:14:30 字数 6005 浏览 37 评论 0

HTML5 canvas 元素可用于编写图像过滤器。您需要做的是将图像绘制到画布上,回读画布像素,然后对它们运行过滤器。然后,您可以将结果写入新画布(或者,只需重用旧画布即可。

原始测试图像

处理像素

首先,检索图像像素:

Filters = {};
Filters.getPixels = function(img) {
  var c = this.getCanvas(img.width, img.height);
  var ctx = c.getContext('2d');
  ctx.drawImage(img);
  return ctx.getImageData(0,0,c.width,c.height);
};

Filters.getCanvas = function(w,h) {
  var c = document.createElement('canvas');
  c.width = w;
  c.height = h;
  return c;
};

接下来,我们需要一种方法来过滤图像。怎么样 filterImage 获取过滤器和图像并返回过滤像素的方法?

Filters.filterImage = function(filter, image, var_args) {
  var args = [this.getPixels(image)];
  for (var i=2; i<arguments.length; i++) {
    args.push(arguments[i]);
  }
  return filter.apply(null, args);
};

运行简单筛选器

现在我们已经将像素处理管道放在一起,是时候编写一些简单的过滤器了。首先,让我们将图像转换为灰度。

Filters.grayscale = function(pixels, args) {
  var d = pixels.data;
  for (var i=0; i<d.length; i+=4) {
    var r = d[i];
    var g = d[i+1];
    var b = d[i+2];
    // CIE luminance for the RGB
    // The human eye is bad at seeing red and blue, so we de-emphasize them.
    var v = 0.2126*r + 0.7152*g + 0.0722*b;
    d[i] = d[i+1] = d[i+2] = v
  }
  return pixels;
};

可以通过向像素添加固定值来调整亮度:

Filters.brightness = function(pixels, adjustment) {
  var d = pixels.data;
  for (var i=0; i<d.length; i+=4) {
    d[i] += adjustment;
    d[i+1] += adjustment;
    d[i+2] += adjustment;
  }
  return pixels;
};

阈值化图像也非常简单。您只需将像素的灰度值与阈值进行比较,并在此基础上设置颜色:

Filters.threshold = function(pixels, threshold) {
  var d = pixels.data;
  for (var i=0; i<d.length; i+=4) {
    var r = d[i];
    var g = d[i+1];
    var b = d[i+2];
    var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;
    d[i] = d[i+1] = d[i+2] = v
  }
  return pixels;
};

Convolving images

卷积 滤波器是用于图像处理的非常有用的通用滤波器。基本思想是,从源图像中获取像素矩形的加权总和,并将其用作输出值。卷积滤波器可用于模糊、锐化、压花、边缘检测和一大堆其他事情。

Filters.tmpCanvas = document.createElement('canvas');
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');

Filters.createImageData = function(w,h) {
  return this.tmpCtx.createImageData(w,h);
};

Filters.convolute = function(pixels, weights, opaque) {
  var side = Math.round(Math.sqrt(weights.length));
  var halfSide = Math.floor(side/2);
  var src = pixels.data;
  var sw = pixels.width;
  var sh = pixels.height;
  // pad output by the convolution matrix
  var w = sw;
  var h = sh;
  var output = Filters.createImageData(w, h);
  var dst = output.data;
  // go through the destination image pixels
  var alphaFac = opaque ? 1 : 0;
  for (var y=0; y<h; y++) {
    for (var x=0; x<w; x++) {
      var sy = y;
      var sx = x;
      var dstOff = (y*w+x)*4;
      // calculate the weighed sum of the source image pixels that
      // fall under the convolution matrix
      var r=0, g=0, b=0, a=0;
      for (var cy=0; cy<side; cy++) {
        for (var cx=0; cx<side; cx++) {
          var scy = sy + cy - halfSide;
          var scx = sx + cx - halfSide;
          if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
            var srcOff = (scy*sw+scx)*4;
            var wt = weights[cy*side+cx];
            r += src[srcOff] * wt;
            g += src[srcOff+1] * wt;
            b += src[srcOff+2] * wt;
            a += src[srcOff+3] * wt;
          }
        }
      }
      dst[dstOff] = r;
      dst[dstOff+1] = g;
      dst[dstOff+2] = b;
      dst[dstOff+3] = a + alphaFac*(255-a);
    }
  }
  return output;
};

这是一个 3x3 锐化滤镜。了解它如何将权重集中在中心像素上。为了保持图像的亮度,矩阵值的总和应为 1。

Filters.filterImage(Filters.convolute, image,
  [  0, -1,  0,
    -1,  5, -1,
     0, -1,  0 ]
);

这是卷积过滤器的另一个示例,盒子模糊。框模糊输出卷积矩阵内像素值的平均值。做到这一点的方法是创建一个大小为 NxN 的卷积矩阵,其中每个权重为 1 / (NxN)。这样,矩阵中的每个像素对输出图像的贡献量相等,权重的总和为 1。

Filters.filterImage(Filters.convolute, image,
  [ 1/9, 1/9, 1/9,
    1/9, 1/9, 1/9,
    1/9, 1/9, 1/9 ]
);

我们可以通过组合现有过滤器来制作更复杂的图像过滤器。例如,让我们编写一个 索贝尔过滤器 .Sobel 滤波器计算图像的垂直和水平梯度,并组合计算出的图像以查找图像中的边缘。我们在这里实现 Sobel 滤镜的方式是首先对图像进行灰度缩放,然后采用水平和垂直渐变,最后组合渐变图像以构成最终图像。

关于术语,这里的“梯度”是指图像位置处像素值的变化。如果像素具有值为 20 的左邻和值为 50 的右邻,则该像素的水平梯度将为 30。垂直梯度具有相同的思想,但使用上方和下方的邻居。

var grayscale = Filters.filterImage(Filter.grayscale, image);
// Note that ImageData values are clamped between 0 and 255, so we need
// to use a Float32Array for the gradient values because they
// range between -255 and 255.
var vertical = Filters.convoluteFloat32(grayscale,
  [ -1, 0, 1,
    -2, 0, 2,
    -1, 0, 1 ]);
var horizontal = Filters.convoluteFloat32(grayscale,
  [ -1, -2, -1,
     0,  0,  0,
     1,  2,  1 ]);
var final_image = Filters.createImageData(vertical.width, vertical.height);
for (var i=0; i<final_image.data.length; i+=4) {
  // make the vertical gradient red
  var v = Math.abs(vertical.data[i]);
  final_image.data[i] = v;
  // make the horizontal gradient green
  var h = Math.abs(horizontal.data[i]);
  final_image.data[i+1] = h;
  // and mix in some blue for aesthetics
  final_image.data[i+2] = (v+h)/4;
  final_image.data[i+3] = 255; // opaque alpha
}

为了结束我们的卷积之旅,这里有一个小玩具供您玩:一个定制的 3x3 卷积过滤器!

还有一大堆其他很酷的卷积过滤器等着你去发现它们。例如,尝试 实现拉普拉斯滤波器 在上面的卷积玩具中

结论

我希望这篇小文章在介绍使用 HTML canvas 标签在 JavaScript 中编写图像过滤器的基本概念时很有用。我鼓励你去实现更多的图像过滤器,这很有趣!

如果您需要更好的滤镜性能,通常可以移植它们以使用 WebGL 片段着色器进行图像处理。使用着色器,您可以实时运行最简单的滤镜,从而可以使用它们对视频和动画进行后期处理。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

青萝楚歌

暂无简介

0 文章
0 评论
501 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文