前阵子因营业需求,须要对图片举行一些特别处置惩罚,比方反相,高亮,是非等,都是运用Canvas
来完成
ImageData
要完成上述所说的种种结果,最中间的事变就是对图片的ImageData
对象举行修改。
ImageData
对象是一个用来形貌图片属性的一种数据对象,它有三个属性,分别是data
、width
、height
。后两个代表的是图片的宽高,不必多说。最重要的就是data
属性,它是一个Uint8ClampedArray
(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>'
相识了ImageData后,我们来看看结果demo
Demo 1:图片反相渐变
先看demo:demo-1
1、像素处置惩罚
能够见到,图片先是渐变成反相的模样,再渐变成下一张图片,是不是是很酷炫。要实际这个,主如果用到getImageData
及putImageData
这两个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
能够见到,挪动端的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
}
})
// 更新ImageData
this.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
事宜举行监听,得出x
,y
坐标作为圆心即可
值得注意的是,由于在不停地更新ImageData
,所以我们须要一个暂时的canvas
来寄存原始图片的ImageData
数据。demo1也是作了一样的处置惩罚
总结
以上就是运用Canvas
完成一些图片结果的引见,权当举一反三,种种看官也能够发挥想象力,完成本身的酷炫结果