小程序中canvas实现图片滤镜

最近自己在做的一个小程序中用到canvas方面的知识,这篇我会把我遇到的问题,有哪些解决方法记录在这里以便之后复习。有些多,我打算拆开。
本篇先记录下在小程序中与canvas相关的知识,还有我在做的东西,遇到了什么样的问题,学到了哪些知识点。之后会再更新一篇H5 中的canvas,包括canvas的性能优化,双缓存机制(离屏加载)。

目录

微信小程序canvas应该知道的基本操作

canvas绘图

其实没什么说的,微信小程序文档上面都有,我在这里面把常用的和容易忽视的地方说下:

  1. 最基本的canvas创建上下文,其它固定语法记住就行
    wxml:
<canvas canvas-id="canvasId"></canvas>

js:

const ctx = wx.createCanvasContext("canvasId");//创建上下文
//一些基本的掌握语法
//scaleX水平缩放,scaleY垂直缩放,skewX水平倾斜,skewY垂直倾斜,translateX水平移动,translateY垂直移动,这个可以做缩放和移动
ctx.transform(scaleX, skewX, skewY, scaleY, translateX, translateY);
ctx.setFillStyle("red");//设置填充颜色为red
ctx.setStrokeStyle("red");//设置线条颜色
ctx.setLineWidth(10);//设置线条为10像素
ctx.fillRect(0,0,100,100);//在(0,0)位置画一个100*100像素的填充矩形
ctx.strokeRect(100,100,100,100);//在(100,100)位置画一个100*100像素的不填充矩形
ctx.moveTo(0, 100);//画线,起点
ctx.lineTo(100, 100);//画线,到哪
ctx.scale(2,2);//放大两倍,控制缩放
ctx.setFontSize(10);//设置字体大小
//在(100,100)的位置写"hello world",最后一个100是文本最大宽度,可以省略
ctx.fillText("hello world",100,100,100);
ctx.stroke();//对当前路径进行描边,说白了就是画线

以上这些都是基本,用到套语法就行,不是全部,有需要其他的看文档就行,当设置完之后就需要画到画布上draw()
这里draw()有个容易忽视的语法:他有两个参数,都是非必填
一个是reserve, boolean类型,默认是false,意思就是每次画图的时候先清空画布,什么时候用true?做画板的时候或者涂鸦的时候,这里一定要写上true,不清空画布接着画
一个是callback,function函数,回调函数,这个可以应用在比如把当前画布指定区域的内容导出生成指定大小的图片,不然的话很可能在还没绘图完成时候导出生成一张空的图片
这里写图片描述

ctx.draw();//可以理解为画,当把所有的东西定义完要用draw()来画在画布上,当画布上没图案的时候,记得看看是不是忘写draw()了

例:用canvas实现图片滤镜

图片滤镜用到的新的语法,有三个:

  • ctx.drawImage() //绘制图像到画布
    dx,dy —开始横纵坐标
    dWidth,dHeight —要绘制的图片宽高
    sx,sy —源图像开始横纵坐标
    sWidth,sHeight —源图像选择的图片宽高
//有三个版本的写法:
drawImage(dx, dy)
drawImage(dx, dy, dWidth, dHeight)
drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) //从 1.9.0 起支持

怎么理解呢?举个例子,到编译器上试一试就明白,这个可以做放大镜效果

//从(0,0)点坐标截取图片150*150,将截取下来的150*150的图放大到300*300画在画布上
wx.chooseImage({  //加载本地图片
  success: function (res) {  //res.tempFilePaths[0] 图片地址
    ctx.drawImage(res.tempFilePaths[0],0,0,150,150,0,0,300,300);  //绘制图像到画布
    ctx.draw();
  }
})

举个例子,接着上一个

wx.chooseImage({  //加载本地图片
  success: function (res) {  //res.tempFilePaths[0] 图片地址
    ctx.drawImage(res.tempFilePaths[0],0,0,150,150,0,0,300,300)  //绘制图像到画布
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(res) {
          console.log(res.width)//图像宽
          console.log(res.height)//图像高
          console.log(res.data)//*重点,图像每像素的颜色RGBA,返回的是个一维数组
        }
      })
    })
  }
})

举个例子,接着上一个

//wxml
<canvas canvas-id="canvas"></canvas>
<canvas canvas-id='canvasNew'></canvas>

//js
wx.chooseImage({  //加载本地图片
  success: function (res) {  //res.tempFilePaths[0] 图片地址
    ctx.drawImage(res.tempFilePaths[0],0,0,150,150,0,0,300,300)  //绘制图像到画布
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(res) {
		  //就是将canvas中加载图片复制到canvasNew中
          wx.canvasPutImageData({
            canvasId: 'canvasNew',//canvas的标识符,也就是上面的cnavas-id
            x: 0,
            y: 0,
            width: 300,
            data: res.data,
            success(res) { 
              console.log(res)
            }
          })
        }
      })
    })
  }
})

有了这三个方法,我们可以利用修改图片每个像素的颜色RGBA来实现滤镜
这里写图片描述
这里我打印了一张图片的每个像素颜色RGBA一维数组,四个组成1像素

//第 i 个像素:
let R = res.data[4*i+0];
let G = res.data[4*i+1];
let B = res.data[4*i+2];
let A = res.data[4*i+3];
//第 x 行第 y列的像素:
let i=x*width+y;
let R = res.data[4*i+0];
let G = res.data[4*i+1];
let B = res.data[4*i+2];
let A = res.data[4*i+3];

妥了,知道这个就简单了,给几个简单例子

//公共的
//wxml:
<canvas canvas-id="canvas" class="canvasStyle"></canvas>
<canvas canvas-id='canvasNew' class="canvasStyle"></canvas>
//wxss:
.canvasStyle{width:300px;height:300px;border:1px solid #e4e4e4;margin:auto;}

###1. 灰度滤镜###
效果图:
这里写图片描述

const ctx = wx.createCanvasContext('canvas');
wx.chooseImage({
  success: function (res) {
    ctx.drawImage(res.tempFilePaths[0],0,0,300,300)
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(result) {
          let data = result.data;
          for (let i = 0; i < result.width * result.height;i++){
          //********************只有这里有区别****************************
            let R = data[i * 4 + 0];
            let G = data[i * 4 + 1];
            let B = data[i * 4 + 2];
            let grey = R * 0.3 + G * 0.59 + B * 0.11;
            data[i * 4 + 0] = grey;
            data[i * 4 + 1] = grey;
            data[i * 4 + 2] = grey;
          //********************只有这里有区别****************************
          }
          wx.canvasPutImageData({
            canvasId: 'canvasNew',
            x: 0,
            y: 0,
            width: 300,
            data: data,
            success(res) { 
              console.log(res)
            }
          })
        }
      })
    })
  }
})

###2. 黑白滤镜###

效果图:
这里写图片描述

const ctx = wx.createCanvasContext('canvas');
wx.chooseImage({
  success: function (res) {
    ctx.drawImage(res.tempFilePaths[0],0,0,300,300)
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(result) {
          let data = result.data;
          for (let i = 0; i < result.width * result.height;i++){
          //********************只有这里有区别****************************
            let R = data[i * 4 + 0];
            let G = data[i * 4 + 1];
            let B = data[i * 4 + 2];
            let grey = R * 0.3 + G * 0.59 + B * 0.11;
            if (grey > 125){
              grey=255;
            } else { 
              grey = 0;
            } 
            data[i * 4 + 0] = grey;
            data[i * 4 + 1] = grey;
            data[i * 4 + 2] = grey;
          //********************只有这里有区别****************************
          }
          wx.canvasPutImageData({
            canvasId: 'canvasNew',
            x: 0,
            y: 0,
            width: 300,
            data: data,
            success(res) { 
              console.log(res)
            }
          })
        }
      })
    })
  }
})

###3.反相滤镜(底片效果)###

效果图:
这里写图片描述

const ctx = wx.createCanvasContext('canvas');
wx.chooseImage({
  success: function (res) {
    ctx.drawImage(res.tempFilePaths[0],0,0,300,300)
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(result) {
          let data = result.data;
          for (let i = 0; i < result.width * result.height;i++){
          //********************只有这里有区别****************************
            let R = data[i * 4 + 0];
            let G = data[i * 4 + 1];
            let B = data[i * 4 + 2];
            data[i * 4 + 0] = 255-R;
            data[i * 4 + 1] = 255-G;
            data[i * 4 + 2] = 255-B;
          //********************只有这里有区别****************************
          }
          wx.canvasPutImageData({
            canvasId: 'canvasNew',
            x: 0,
            y: 0,
            width: 300,
            data: data,
            success(res) { 
              console.log(res)
            }
          })
        }
      })
    })
  }
})

###4.马赛克滤镜(复杂的一个)###

效果图:
这里写图片描述

这个我要说下思路:
1.设置个size=10,马赛克模糊大小,越大越模糊
2.比如说图片是300300像素,原本是由11像素构成,现在我们定义size=10,那现在一份像素点就是1010,将这份中R、G、B分别累加取平均值,得到一个颜色再赋值到1010中

const ctx = wx.createCanvasContext('canvas');
wx.chooseImage({
  success: function (res) {
    ctx.drawImage(res.tempFilePaths[0],0,0,300,300)
    ctx.draw(false,function(){
      wx.canvasGetImageData({
        canvasId: 'canvas',
        x: 0,
        y: 0,
        width: 300,
        height: 300,
        success(result) {
          let data = result.data;
          const size = 10;
          const totalnum = size*size;
          for(let i=0;i<result.height;i+=size){
            for(let j=0;j<result.width;j+=size){
              var totalR=0,totalG=0,totalB=0;
              for(let dx=0;dx<size;dx++){
                for(let dy=0;dy<size;dy++){
                  var x = i+dx;
                  var y = j+dy;
                  
                  var p = x * result.width + y;
                  totalR += data[p * 4 + 0];
                  totalG += data[p * 4 + 1];
                  totalB += data[p * 4 + 2];
                }
              }

              var p = i * result.width + j;
              var resR = totalR / totalnum;
              var resG = totalG / totalnum;
              var resB = totalB / totalnum;
              for (let dx = 0; dx < size; dx++){
                for (let dy = 0; dy < size; dy++) {
                  var x = i + dx;
                  var y = j + dy;

                  var p = x * result.width + y;
                  data[p * 4 + 0] = resR;
                  data[p * 4 + 1] = resG;
                  data[p * 4 + 2] = resB;
                }
              }
            }
          }
          wx.canvasPutImageData({
            canvasId: 'canvasNew',
            x: 0,
            y: 0,
            width: 300,
            data: data,
            success(res) { 
              console.log(res)
            }
          })
        }
      })
    })
  }
})

现在还没有解决的问题?

###问题1:###
问题:将图片转成像素图,转换以后,我遍历整张图上的颜色,不重复的颜色居然有上百个,而我想实现的图其实只有20个颜色如图2。所以想问的是这个颜色是怎么弄的?
我试过取范围内出现次数最多的颜色进行赋值,还是有很多种颜色。举例子就是10*10像素为一份,一共100块,我取这100块中颜色出现最多的那个来赋值。

我实现的,图1:
这里写图片描述
想要达到的效果,图2:
这里写图片描述

var colorArr = [];
Page({
  data: {
  },
  onLoad: function (options) {
    const ctx = wx.createCanvasContext('canvas');
    wx.chooseImage({
      success: function (res) {
        ctx.drawImage(res.tempFilePaths[0],0,0,320,320)
        ctx.draw(false,function(){
          wx.canvasGetImageData({
            canvasId: 'canvas',
            x: 0,
            y: 0,
            width: 320,
            height: 320,
            success(result) {
              let data = result.data;
              const size = 8;
              const totalnum = size*size;
              for(let i=0;i<result.height;i+=size){
                for(let j=0;j<result.width;j+=size){
                  var totalR=0,totalG=0,totalB=0;
                  for(let dx=0;dx<size;dx++){
                    for(let dy=0;dy<size;dy++){
                      var x = i+dx;
                      var y = j+dy;
                      var p = x * result.width + y;
                      totalR += data[p * 4 + 0];
                      totalG += data[p * 4 + 1];
                      totalB += data[p * 4 + 2];
                    }
                  }
                  var p = i * result.width + j;
                  var resR = totalR / totalnum;
                  var resG = totalG / totalnum;
                  var resB = totalB / totalnum;
                  for (let dx = 0; dx < size; dx++){
                    for (let dy = 0; dy < size; dy++) {
                      var x = i + dx;
                      var y = j + dy;
                      var p = x * result.width + y;
                      data[p * 4 + 0] = resR;
                      data[p * 4 + 1] = resG;
                      data[p * 4 + 2] = resB;
                    }
                  }
                  var color = resR + "," + resG + "," + resB;
                  if (colorArr.length==0){
                    colorArr.push(color);
                  }else{
                    for (let m = 0; m < colorArr.length;m++){
                      if (colorArr[m] == color){
                        break;
                      }else{
                        if (m == colorArr.length-1){
                          colorArr.push(color);
                        }
                      }
                    }
                  }
                }
              }
              console.log(colorArr)
              wx.canvasPutImageData({
                canvasId: 'canvasNew',
                x: 0,
                y: 0,
                width: 320,
                data: data,
                success(res) { 
                  console.log(res)
                }
              })
            }
          })
        })
      }
    })
  }
})
  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个......

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值