canvas相关方法介绍
1.ctx.getImageData(sx, sy, sw, sh):其中sx、sy分别是起点的横纵坐标,sw和sh分别为将要提取的数据对应的矩形图像的宽度和高度;其返回值是一个imageData对象,包含宽度、高度、data(Uint8ClampedArray类型的数组);
2.ctx.putImageData(imagedata, dx, dy)或者ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)用于将一个imageData对象绘制到指定的位图中;其中dx/dy是目标图像相对源图像的坐标偏移;dirtyX/dirtyY是截取的源图像的起始坐标,默认为(0,0);dirtyWidth/dirtyHeight则是目标图像的宽和高;
3. 通过ctx的getImageData和putImageData方法可以精细控制每帧图像的每个像素值;
4. 该过程中的视频处理其实就是对每一帧图像处理的一个连续过程,因此是一个递归;
demo —— green screen effect
直接看代码有点无趣,可以先看效果图增加点乐趣:
视频素材此处获取。需要注意的是:视频本身的宽高比和html中定义的视频框的宽高比并不同。视频自身的宽高可以通过video.videoWidth和video.videoHeight获取;注意代码中canvas的绘制尺寸也是以视频尺寸为基准的,因此需要根据视频本身的尺寸调整画布自身的尺寸,从而才能完整地绘制出视频的每一帧;
Green Screen Effect:使用纯色的背景可以很好地区分前景物体和背景,从而可以很容易地提取背景色特征并根据需求对背景进行相应的替换;具体处理可以使用PS取色器或其他工具从R、G、B三个通道综合分析前景色与背景色的非严格临界条件,从而从像素级别对背景进行替换;(效果图有些粗糙,不想花太多时间分析,原谅我太懒^_^);
Imagedata使用的是四通道的RGBA颜色模式,因此像素数是imagedata长度的1/4,通过对R、G、B进行条件提取,将对应的Alpha通道设置为0(透明),从而将这部分满足特征的背景隐藏,使得替换的canvas背景图得以显现出来。
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: black;
color:#CCCCCC;
}
#c2 {
background-image: url(a.png);
background-repeat: no-repeat;
}
div {
float: left;
border :1px solid #444444;
padding:10px;
margin: 10px;
background:#3B3B3B;
}
</style>
</head>
<body>
<div>
<video id="video" src="chicken.mp4" controls="true"/>
</div>
<div>
<canvas id="c1" width="480" height="270"></canvas>
<canvas id="c2" width="480" height="270"></canvas>
</div>
<script type="text/javascript">
let processor = {
timerCallback: function() {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
let self = this;
setTimeout(function () {
self.timerCallback();
}, 0);
},
doLoad: function() {
this.video = document.getElementById("video");
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
this.c2 = document.getElementById("c2");
this.ctx2 = this.c2.getContext("2d");
let self = this;
this.video.addEventListener("play", function() {
self.width = self.video.videoWidth / 4;
self.height = self.video.videoHeight / 4;
self.timerCallback();
}, false);
},
computeFrame: function() {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
let l = frame.data.length / 4;
for (let i = 0; i < l; i++) {
let r = frame.data[i * 4 + 0];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
if (g > 100 && r < 50)
frame.data[i * 4 + 3] = 0;
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
};
document.addEventListener("DOMContentLoaded", () => {
processor.doLoad();
});
</script>
</body>
</html>
运行结果:
更新:
CSS提供了强大的属性——mix-blend-mode
混合特效模式,可以帮助我们实现类似的功能。和PS的混合模式有异曲同工之妙,低层都是使用了图像算法对混合后图像的像素点进行了重新计算。
mix-blend-mode
属性用于描述元素的内容与父元素的内容及元素的背景以什么样的方式进行融合;该属性可以应用到所有的HMTL元素,默认不继承;
该属性值定义的混合方式众多,可以实现很多奇幻的特效。默认值为normal
,可选值包括multiply
、screen
、overlay
、darken
、hard-light
等;
这里我们使用的是screen
模式,screen
为滤色特效(opposite of multiply
),混合特效后的像素点的值为:
f
(
a
,
b
)
=
255
−
(
255
−
a
)
(
255
−
b
)
/
255
=
a
+
b
−
a
b
/
255
;
f(a,b) = 255 - (255 - a)(255-b)/255 = a + b - ab/ 255;
f(a,b)=255−(255−a)(255−b)/255=a+b−ab/255;
// a为基础层像素值,b为顶层像素值;a/b均为归一化的RGB颜色值;
具体的算法可以参考wikipedia
这里需要注意的是,根据公式我们可以得到,当混色模式前的一个颜色为黑色时(比如b为#000),f(a,b) =a
;当其中一种颜色为白色(比如b为#fff
),f(a,b)=255
;即:
- 混色的其中一种颜色A为黑色,那么混色后的像素值为混色前的颜色B;
- 混色的其中一种颜色A为白色,那么混色后的像素值均为白色;
- 任何颜色和其他颜色执行滤色模式混合后的颜色会更浅,有点类似漂白的效果。
针对第3点,根据公式我们可以看到f(a, b) = a + b - ab/255
这个值是大于等于a且大于等于b的;因此混合后的像素值相比于原像素值更加接近255,因此看起来类似于漂白效果;
因此,如果需要混色后保持原本的色彩,混色的前景图背景色需要为黑色;
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: black;
color:#CCCCCC;
}
.wrapper {
background-image: url('../../bg.jpg'); // 设定父元素的背景图
background-repeat: no-repeat;
background-size: 100% auto;
}
video {
mix-blend-mode: screen; // 设置视频元素的混合模式
}
div {
float: left;
border :1px solid #444444;
padding:10px;
margin: 10px;
background:#3B3B3B;
}
</style>
</head>
<body>
<div class="wrapper">
<video id="video" src="../../crastle.mp4" controls="true"/>
</div>
<div>
<canvas id="c1" width="360" height="640"></canvas>
<canvas id="c2" width="360" height="640"></canvas>
</div>
</body>
</html>
父元素使用的背景图片如下
最终效果如下:
参考文献:
- http://www.w3school.com.cn/tags/canvas_getimagedata.asp
- https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData
- https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData
- https://www.videvo.net/stock-video-footage/green%20screen/?page=1
- mix-blend-mode