博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
玩转你的图片,各种图片效果的Canvas实现
阅读量:5937 次
发布时间:2019-06-19

本文共 5362 字,大约阅读时间需要 17 分钟。

前阵子因业务需求,需要对图片进行一些特殊处理,例如反相,高亮,黑白等,都是使用Canvas来实现

ImageData

要实现上述所说的各种效果,最核心的事情便是对图片的对象进行改动。

ImageData对象是一个用来描述图片属性的一种数据对象,它有三个属性,分别是datawidthheight。后两个代表的是图片的宽高,不用多说。最重要的就是data属性,它是一个(8位无符号整形固定数组)类型化数组。按照从上到下,从左到右的顺序,它里面储存了一张图片的所有像素的rgba信息。

例如,一张图片有4个像素,那data里面就有16个值,data[0]~data[3]的值就是第一个像素中的r、g、b、a值(不了解rgba的看)。

如何获得一张图片的ImageData对象?通过canvas的getImageData便可以很简单地获得:

const canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')ctx.drawImage(img, 0, 0, canvas.width, canvas.height)const oriPeixel = ctx.getImageData(0, 0, canvas.width, canvas.height)

值得注意的是,ImageData里面的属性都是只读的,不能直接更改和赋值。

例如我们把上面的oriPeixel的属性赋值,就会报以下的错:

oriPeixel.data = []> Uncaught TypeError: Cannot assign to read only property 'data' of object '#
'

了解了ImageData后,我们来看看效果demo

Demo 1:图片反相渐变

先看demo:

1、像素处理

可以见到,图片先是渐变成反相的样子,再渐变为下一张图片,是不是很酷炫。要现实这个,主要是用到getImageDataputImageData这两个API

刚才我们说过,图片的ImageData对象储存着该图片的每个像素的信息,想要得到图片的反相效果,要作如下处理:

threshold (ctx, idx) {  let pixels = ctx.getImageData(0, this.height * idx, this.width, this.height)  let d = pixels.data  for (let i = 0; i < d.length; i += 4) {    let r = d[i]    let g = d[i + 1]    let b = d[i + 2]    // 根据rgb求灰度值公式0.2126 * r + 0.7152 * g + 0.0722 * b    let v = (0.2126 * r + 0.7152 * g + 0.0722 * b >= 100) ? 255 : 128    d[i] = d[i + 1] = d[i + 2] = v  }  return pixels}

返回的pixels便是图片经过反相处理后的ImageData

这里主要是对每个像素的灰度值作过滤,大于等于100的,直接为白色,否则置于128

除此之外,还有黑白,高亮等其他像素处理,具体的可以看

2、渐变处理

有了经过反相处理后的图片的ImageData数据,下一步要做的自然就是渐变赋值了。原生是没有提供相关的API自动达成这种的渐变效果的,所以就需要我们自行实现一遍了,这个会比较麻烦。

用js写过动画的同学都知道,基本上都会使用requestAnimationFrame函数来进行帧处理,这里也不意外。

主要思路是这样,图片经过如下的顺序进行渐变:

图片1----->图片1反相----->图片2----->图片2反相----->图片3......

直接贴上主要代码:

gradualChange () {  // 图片原始的ImageData数据  let oriPixels = this.ctx.getImageData(0, 0, this.width, this.height)  let oriData = oriPixels.data  // 图片反相后的ImageData数据  let nextData = this.nextPixel[0].data  let length = oriData.length  let totalgap = 0  let gap = 0  let gapTemp  for (let i = 0; i < length; i++) {    // 计算每个rgba的差值,同时缩小处理。除的数值代表着渐变速度,越大越慢    gapTemp = (nextData[i] - oriData[i]) / 13    if (oriData[i] !== nextData[i]) {      // 每个rgba值增量处理,简单来说就是各种取整,[-1,1]区间直接取-1或1      gap = gapTemp > 1 ? Math.floor(gapTemp) : gapTemp < -1 ? Math.ceil(gapTemp) : oriData[i] < nextData[i] ? 1 : oriData[i] > nextData[i] ? -1 : 0      totalgap += Math.abs(gap)      oriData[i] = oriData[i] + gap    }  }    // 通过putImageData更新图片  this.ctx.putImageData(oriPixels, 0, 0)  // 总值为0,证明已经渐变完成  if (!totalgap) {    this.nextPixel.shift()    if (!this.nextPixel[0]) {      this.isChange = false    }  }}

上面是渐变过程的主要代码,完整的代码可以查看:

Demo 2:光条高亮移动效果

同样是先看demo

  • 移动端:
  • PC端:

可以见到,移动端的demo中,光条上有几个亮斑在同时移动;而PC端,则是在当鼠标hover上去之后,在光条中有一个圆形光斑的高亮效果,因为图片本身是透明的,所以背景色做了深色处理。

1、像素处理

需要说明的是,要实现这种效果,最好是找一些背景一部分透明,一部分带有带状色条的图片,例如我demo中的图片。这类图片有相当区域像素的rgba值为4个0,我们很容易对其做边界处理

同样的,实现这种效果也是需要对图片像素的rgba值进行处理,但是会比图片反相渐变复杂一些,因为这里需要先实现一个圆形的光斑。

光斑实现

既然是圆形光斑,肯定是先有圆心和半径。在这里,我是在横向的方向上,取光条的中心为圆心,半径取50

实现的代码在demo2的brightener函数里面,理解起来也不困难,给定一个y坐标,然后再遍历一遍在这个y坐标下的像素,找出每条光条初始点和结束点的x坐标。rgba值连续两点不为0的,就认为是仍处在光条中,还没有达到边界值

brightener (y) {  // ....完整请看源代码  for (let x = 0; x < cW; x++) {    sPx = (cY * cW + x) * 4    if (oriData[sPx] || oriData[sPx + 1] || oriData[sPx + 2]) {      startX || (startX = x)      tempX = sPx + 4      if (oriData[tempX] || oriData[tempX + 1] || oriData[tempX + 2]) {        continue      } else {        endX = tempX / 4 - cY * cW        cX = Math.ceil((endX - startX) / 2) + startX        startX = 0        res.push({          x: cX,          y: cY        })      }    }  }  return res}

确定了圆心之后,就可以根据半径确定一个圆,并用一个数组存储这个圆内各个点,以便后续处理。过程也很简单,就是初中学的那一套,两点距离小于半径就可以了

createArea (x, y, radius) {  let result = []  for (let i = x - radius; i <= x + radius; i++) {    for (let j = y - radius; j <= y + radius; j++) {      let dx = i - x      let dy = j - y      if ((dx * dx + dy * dy) <= (radius * radius)) {        let obj = {}        if (i > 0 && j > 0) {          obj.x = i          obj.y = j          result.push(obj)        }      }    }  }  return result}

之后,就是实现一个光斑效果。在这里,我是从圆心向边缘进行一个透明度的衰减渐变

// ...const validArr = this.createArea(x, y, radius)validArr.forEach((px, i) => {  sPx = (px.y * cW + px.x) * 4  // 像素点的rgb值不全为0  if (oriData[sPx] || oriData[sPx + 1] || oriData[sPx + 2]) {    distance = Math.sqrt((px.x - x) * (px.x - x) + (px.y - y) * (px.y - y))    // 根据距离和半径的比率进行正比衰减    gap = Math.floor(opacity * (1 - distance / radius))    oriData[sPx + 3] += gap  }})// 更新ImageDatathis.ctx.putImageData(oriPixels, 0, 0)

到这里,一个光斑就这样实现了

2、移动效果

光斑有了,自然就是让它动起来。这个就简单啦,光斑生成的我们已经完成,那么我们只要把圆心动起来就可以了

在这里,同样是使用requestAnimationFrame函数来进行帧处理。而光斑是从下向上移动的,可以看到startY在不断递减

autoPlay (timestamp) {  if (this.startY <= -25) {    let timeGap    if (!this.progress) {      this.progress = timestamp    }    timeGap = timestamp - this.progress    // 判断间隔时间是否满足    if (timeGap > this.autoPlayInterval) {      this.startY = this.height - 1      this.progress = 0    }  } else {    // 根据Y坐标生成圆心及光斑     const res = this.getBrightCenter(this.startY)    this.brightnessCtx(res, 50, 60)    this.startY -= 10  }  window.requestAnimationFrame(this.autoPlay.bind(this), false)}

可以看到,无非就是循环startY坐标,生成新光斑的过程。而PC上的效果是当鼠标hover上去时有光斑效果,同理去掉这个自动移动的过程,对图片的mousemove事件进行监听,得出xy坐标作为圆心即可

值得注意的是,因为在不断地更新ImageData,所以我们需要一个临时的canvas来存放原始图片的ImageData数据。demo1也是作了同样的处理

完整的代码可以查看: 、

总结

以上便是使用Canvas实现一些图片效果的介绍,权当抛砖引玉,各种看官也可以发挥想象力,实现自己的酷炫效果

参考

转载地址:http://vsjtx.baihongyu.com/

你可能感兴趣的文章
蓝绿发布、滚动发布、灰度发布等部署方案对比与总结
查看>>
Linux Debian8 Rsync+Sersync实现数据实时同步
查看>>
学习五十一
查看>>
面向对象的三个基本特征
查看>>
docker容器中没有vi
查看>>
python wxpython制作计算器
查看>>
Photoshop修复画笔工具、污点修复画笔工具使用方法
查看>>
DNS服务之反向解析
查看>>
SSL虚拟主机
查看>>
Java基础——过滤器和监听器
查看>>
公司要安装电脑监控软件你同意吗?
查看>>
MySQL 5.7 操作命令
查看>>
day 20 第一阶段考试总结
查看>>
day34 awk数组
查看>>
SQL内连接、外连接以及(+)号用法
查看>>
equals与==的区别
查看>>
mysql主从配置实现一主一从读写分离
查看>>
Sql server--数据查询
查看>>
Java异常处理---finally和return的执行顺序
查看>>
IT行业找工作容易吗?
查看>>