Canvas如何实现滤镜效果

2022-07-15 09:27:18 浏览数 (1)

对于管理系统以及类似的应用来说,某个功能的本质可以理解为某一业务点。而对于专业工具以及相关的应用来说,某个功能实际上就是某个技术点。--《功能》

图片滤镜

滤镜,主要是用来实现图像的各种特殊效果。

用过photoshop或者美颜相机,我们都知道滤镜可以帮助我们把图片修缮的更加完美。

那么,作为前端开发人员,如何实现一套滤镜效果呢?一起来了解下吧。

实现滤镜需要了解的内容

我们知道前端通过Canvas可以绘制各种图形,文本,图片,只需要通过fillRect,fillText,drawImage等相关的Api即可绘制对应的内容。

但实际,我们可以通过直接操作ImageData对象来修改像素数据,从而实现各种我们想要的效果。

ImageData 对象

ImageData对象中存储着canvas对象真实的像素数据,它包含以下几个只读属性:

  • width: 图片宽度,单位px
  • height: 图片高度,单位px
  • data: Uint8ClampedArray类型的一维数组,包含着RGBA格式的整型数据,范围在0至255之间(包括255)。

data属性返回一个 Uint8ClampedArray,它可以被使用作为查看初始像素数据。每个像素用4个1bytes值(按照红,绿,蓝和透明值的顺序; 这就是"RGBA"格式) 来代表。每个颜色值部份用0至255来代表。每个部份被分配到一个在数组内连续的索引,左上角像素的红色部份在数组的索引0位置。像素从左到右被处理,然后往下,遍历整个数组。

Uint8ClampedArray 包含高度 × 宽度 × 4 个比特数据,索引值从0到(高度×宽度×4)-1

假如要读取图片中位于第50行,第200列的像素的蓝色部份,则可写以下代码:

代码语言:javascript复制
blueComponent = imageData.data[((50 * (imageData.width * 4))   (200 * 4))   2];

根据行、列读取某像素点的R/G/B/A值的公式:

代码语言:javascript复制
imageData.data[((50 * (imageData.width * 4))   (200 * 4))   0/1/2/3];

也可以使用Uint8ClampedArray.length属性来读取像素数组的大小(以bytes为单位):

代码语言:javascript复制
let numBytes = imageData.data.length;

创建ImageData对象

我们可以使用createImageData()方法创建ImageData对象。

代码语言:javascript复制
 myImageData = ctx.createImageData(width, height);

上面代码创建了一个新的具体特定尺寸的ImageData对象。所有像素被预设为透明黑。

获取场景像素数据

我们可以用getImageData()方法获取画布场景中的像素数据

代码语言:javascript复制
let myImageData = ctx.getImageData(left, top, width, height);

这个方法会返回一个ImageData对象,它代表了画布区域的对象数据,此画布的四个角落分别表示为(left, top), (left width, top), (left, top height), 以及(left width, top height)四个点。这些坐标点被设定为画布坐标空间元素。

Canvas实现拾色器

前端在实现设计稿时,通常会使用拾色器,也叫做吸色工具。基于canvas下面的代码是一种简单的实现。

代码语言:javascript复制
var img = new Image();
img.crossOrigin = 'anonymous';
img.src = './assets/rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
  ctx.drawImage(img, 0, 0);
  img.style.display = 'none';
};
var hoveredColor = document.getElementById('hovered-color');
var selectedColor = document.getElementById('selected-color');
function pick(event, destination) {
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;

    const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;
    destination.style.background = rgba;
    destination.textContent = rgba;
    return rgba;
}
canvas.addEventListener('mousemove', function(event) {
    pick(event, hoveredColor);
});
canvas.addEventListener('click', function(event) {
    pick(event, selectedColor);
});
  1. 在canvas中绘制图片
  2. 移动鼠标时获取鼠标的位置,该位置就是此时鼠标所在的像素点的位置
  3. 点击鼠标时,获取该像素点的颜色信息。

在场景中写入像素数据

我们可以使用putImageData()方法修改像素数据后,对画布场景数据进行重置。

代码语言:javascript复制
ctx.putImageData(myImageData, dx, dy);

图片灰度和反相颜色

下面的例子,我们遍历所有像素以改变他们的数值。然后将被修改的像素数组通过putImageData()放回到画布中去。invert函数仅仅是去减掉颜色的最大色值255.grayscale函数仅仅是用红绿和蓝的平均值。我们也可以用加权平均,例如x = 0.299r 0.587g 0.114b这个公式。

代码语言:javascript复制
var img = new Image();
img.crossOrigin = 'anonymous';
img.src = './assets/rhino.jpg';

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

img.onload = function() {
    ctx.drawImage(img, 0, 0);
};

var original = function() {
    ctx.drawImage(img, 0, 0);
};
// 反相图片
var invert = function() {
    ctx.drawImage(img, 0, 0);
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    for (var i = 0; i < data.length; i  = 4) {
        data[i]     = 255 - data[i];     // red
        data[i   1] = 255 - data[i   1]; // green
        data[i   2] = 255 - data[i   2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
};
// 灰度
var grayscale = function() {
    ctx.drawImage(img, 0, 0);
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    for (var i = 0; i < data.length; i  = 4) {
        var avg = (data[i]   data[i   1]   data[i   2]) / 3;
        data[i]     = avg; // red
        data[i   1] = avg; // green
        data[i   2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
};
// radio变化
const inputs = document.querySelectorAll('[name=color]');
for (const input of inputs) {
    input.addEventListener("change", function(evt) {
        switch (evt.target.value) {
            case "inverted":
                return invert();
            case "grayscale":
                return grayscale();
            default:
                return original();
        }
    });
}

原图

灰度

类似的,我们可以实现各种滤镜效果,比如:浮雕,高斯模糊,镜像,复古...

浮雕

最后

前端实现滤镜的方式其实有两种,一种是canvas,另一种是css。

从canvas来讲,可以通过修改canvas中的图片像素数据来实现各种滤镜效果,但是需要我们自己去修改像素值,自己去查各种算法。

而css相反,我们可以直接使用css的filter来设置各种效果,因为它已经内置了很多滤镜效果。

总结

像素操作需要注意的是,像素点是每四个一组,分别代表:R,G,B,A。

0 人点赞