Canvas 进阶(五)实现图片滤镜效果

3,662 阅读4分钟

背景

之前看过一篇写关于图片滤镜的文章,蛮有兴趣,因此作出了这个小 DEMO,可以切换多种图片滤镜并提供图片下载功能。

话不多说,先上 demogithub地址.

实现

  • 使用工具:vue iview canvas
  • 实现功能:图片绘制,滤镜修改,图片下载
  • 关键点:ctx.getImageData() ctx.putImageData() ctx.drawImage() 滤镜逻辑

1. 引入 iviewvue

<link rel="stylesheet" type="text/css" href="https://unpkg.com/view-design/dist/styles/iview.css" />
<script type="text/javascript" src="https://vuejs.org/js/vue.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/view-design/dist/iview.min.js"></script>

2. 设计整体静态页面

首先页面须有两个 canvas 标签,一个绘制原始图片,一个绘制添加滤镜效果的图片。当然还有图片上传下载按钮,以及滤镜选择框,具体如下:

 <div class="text-center">
    <div>
        <i-select
            v-model="pictureMode"
            style="width:200px"
            placeholder="请选择图像模式"
            @on-change="selectMode"
        >
            <i-option v-for="item in selectList" :value="item.value" :key="item.value"
                >{{ item.label }}</i-option
            >
        </i-select>
    </div>
    <div style="margin: 24px 0">
        <i-button icon="ios-cloud-upload-outline" type="primary" @click="$refs.input.click()"
            >上传图片</i-button
        >
        <i-button icon="ios-cloud-download-outline" type="primary" @click="downloadImage"
            >下载图片</i-button
        >
        <input type="file" ref="input" @change="uploadImage" style="display: none;" />
    </div>
    <div>
        <!-- 用户原始图片 -->
        <canvas id="origin" :width="width" :height="height" v-show="image"></canvas>
        <!-- 目标图片 -->
        <canvas id="new" :width="width" :height="height" v-show="image"></canvas>
    </div>
</div>

3. 选择图片并绘制

通过 input 标签获取选择的 file 文件,将其转化为 base64 字符串后赋值给 imagesrc 属性,待图片加载完成后在两个 canvas 中进行绘制,此为原始图片。

methods: {
    // 上传图片
    uploadImage(e) {
        var that = this;
        var file = e.target.files[0];
        if (typeof FileReader === 'undefined') {
            alert('您的浏览器不支持图片上传,请升级您的浏览器');
            return false;
        }
        var image = new Image(); // 创建图片
        image.crossOrigin = 'Anonymous'; // 解决一些跨域问题
        image.onload = function() {
            that.width = image.width; // 设置canvas的宽
            that.height = image.height; // 设置canvas的高
            that.image = image;
            // 等待canvas的宽高属性渲染完毕绘制canvas
            that.$nextTick(() => { 
                that.drawOriginImage(image);
            })
        };
        let reader = new FileReader();
        reader.readAsDataURL(file); // 生成base64
        reader.onload = e => {
            image.src = e.target.result;
        };
    },
    // 画出原始图像
    drawOriginImage(image) {
        var canvasOrigin = document.getElementById('origin');
        var ctxOrigin = canvasOrigin.getContext('2d');
        var canvasNew = document.getElementById('new');
        var ctxNew = canvasNew.getContext('2d');
        ctxOrigin.drawImage(image, 0, 0, image.width, image.height);
        ctxNew.drawImage(image, 0, 0, image.width, image.height);
    },
}

4. 选择滤镜并绘制新图片

canvas 中的 ctx 对象提供了一个方法 getImageData(), 该方法可返回某个区域内每个像素点的数值的组成的数组(例如:ImageData { width: 100, height: 100, data: Uint8ClampedArray[40000] }),data 数组中 4 个元素表示一个像素点的 rgba 值。通过对此数组每四个元素值的修改,然后重新绘制成新的 canvas,即得到我们的目标图片.

// 画出目标图像
drawImage() {
    var canvasOrigin = document.getElementById('origin');
    var ctxOrigin = canvasOrigin.getContext('2d');
    var canvasNew = document.getElementById('new');
    var ctxNew = canvasNew.getContext('2d'); 
    var imageData = ctxOrigin.getImageData(0, 0, this.width, this.height);
    var data = imageData.data; // 获取原始图像每一个像素
    this.chooseFilter(data, canvasNew, imageData); // 根据选择的滤镜处理数组
    ctxNew.putImageData(imageData, 0, 0); // 将处理的原图像的数据绘制到新图像的 canvas 中
},

5. 下载图片

通过对新的 canvas 调用 toDataURL() 返回一个包含图片展示的 data URI, 将其赋值的新的图片的 src 属性并触发点击下载事件实现下载图片功能

// 下载图片
downloadImage(image, name) {
    if (!this.image) {
        this.$Modal.error({
            title: '错误',
            content: '请上传图片先啦!!',
        });
        return;
    }
    var image = new Image();
    var canvas = document.getElementById('new');
    image.src = canvas.toDataURL();
    this.downLoad(image, 'suporka-image-filter.jpg');
},
// 下载
downLoad(image, name) {
    const dataURL = image.src;
    const link = document.createElement('a');
    link.download = name;
    link.href = dataURL;
    link.dispatchEvent(new MouseEvent('click'));
},

图像滤镜

this.chooseFilter(data, canvasNew, imageData); 是根据不同滤镜进行图片处理。这里简单介绍几种图像滤镜:

灰度滤镜

将颜色的RGB设置为相同的值即可使得图片为灰色,一般处理方法有:

1、取三种颜色的平均值

2、取三种颜色的最大值(最小值)

3、加权平均值:0.3R + 0.59G + 0.11*B

本文用的是第一种方法

for(var i = 0; i < data.length; i+=4) {
     var grey = (data[i] + data[i+1] + data[i+2]) / 3;
     data[i] = data[i+1] = data[i+2] = grey;
}

黑白滤镜

顾名思义,就是图片的颜色只有黑色和白色,可以计算rgb的平均值arg,arg>=100,r=g=b=255,否则均为0

for(var i = 0; i < data.length; i += 4) {
     var avg = (data[i] + data[i+1] + data[i+2]) / 3;
     data[i] = data[i+1] = data[i+2] = avg >= 100 ? 255 : 0;
}

反向滤镜

取 RGB 三种颜色分别取 255 的差值。

for(var i = 0; i < data.length; i+= 4) {
      data[i] = 255 - data[i];
      data[i + 1] = 255 - data[i + 1];
      data[i + 2] = 255 - data[i + 2];
}

去色滤镜

rgb三种颜色取三种颜色的最值的平均值。

for(var i = 0; i < data.length; i++) {
   var avg = Math.floor((Math.min(data[i], data[i+1], data[i+2]) + Math.max(data[i], data[i+1], data[i+2])) / 2 );
   data[i] = data[i+1] = data[i+2] = avg;
}

单色滤镜

只保留一种颜色,其他颜色设为0

for(var i = 0; i < canvas.height * canvas.width; i++) {
    data[i*4 + 2] = 0;
    data[i*4 + 1] = 0;
}

牛顿说: “我只是站在了巨人的肩膀上”。更多详细的滤镜请移步巨人的肩膀:《图像处理的滤镜算法》( ̄▽ ̄)~*

总结

本案例主要是对 canvasctx.getImageData,ctx.putImageData()'; 及图片数据处理的运用实现我们想要的效果。后续还有 canvas 系列的相关文章,敬请期待!

更多推荐

前端进阶小书(advanced_front_end)

前端每日一题(daily-question)

webpack4 搭建 Vue 应用(createVue)

Canvas 进阶(一)二维码的生成与扫码识别

Canvas 进阶(二)写一个生成带logo的二维码npm插件

Canvas 进阶(三)ts + canvas 重写”辨色“小游戏

Canvas 进阶(四)实现一个“刮刮乐”游戏