上一章节介绍了关于设备的检测,这节就来看看怎么使用设备。先小试牛刀,实现一个拍照功能。
之前也已经介绍过了如何使用摄像头,想一下,使用摄像头拍照是不是也能实现呢?当然是可以实现的。只是需要一点小小的帮助,比如要用到canvas
。
首先,要理解一个事情,视频就是很多张连续的图片快速的播放,由于间隙非常小,肉眼无法分辨出来,所以才有了连续动画的视频效果。从媒体设备中获取到的视频流就是一张张连续的图片,然后在video
标签中播放就成了视频。
要实现拍照功能,就是从那一系列连续的图片中选出想要的那张即可。
此时,你可能会有点疑问,这么多张连续的图片,我要怎么才能拿到想要的那张呢?非常简单,就像是用手机拍照一样,摆好姿势,保持不动,然后点击拍照,最后那张就是了。什么?你说你想要中间的那几张?那请你翻到下一节,看看怎么录制视频,然后将录制好的视频放到剪辑软件上剪辑。
根据以上描述,可将拍照分为三个步骤:
- 拿到视频
- 拿到图片
- 保存图片
首先从摄像头/外部视频流中拿到视频流,放到video
中进行播放,然后利用canvas
进行绘制图片,最后利用a标签来下载图片。非常简单。
拿到视频
这个是已经讲过了,在《摄像头的基本使用》中有具体代码介绍。这里就简单列出来代码吧,也不多说,还不会的往前翻翻,多操作几遍。
这里还加上了模式的选择,有多种滤镜可以使用。
<div class="content">
<div class="box1">
<video muted autoplay playsinline ref="videoRef" class="video1"></video>
<div>
选择模式:
<el-select v-model="type">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<el-button @click="takePhoto">拍照</el-button>
</div>
<div class="box2">
<canvas ref="canvasRef"></canvas>
<el-button @click="save">保存</el-button>
</div>
</div>
// 在拍照完之后,再选择滤镜处理,最后进行保存
// 喜欢的可以再拓展拓展
const options = [
{
label: "无",
value: "none",
},
{
label: "模糊",
value: "blur",
},
{
label: "灰色",
value: "grayscale",
},
{
label: "反色",
value: "invert",
},
{
label: "色斑",
value: "sepia",
},
];
const constraints = {
video: { width: 450, height: 300 },
audio: false,
};
const init = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
videoRef.value.srcObject = stream;
} catch (error) {
console.error(error);
}
};
这部分就是拿到视频流并在video
标签中播放。约束条件可以自由选择,想要更清晰点可以将分辨率放大。音频的话,可有可无,没啥影响。
拿到图片
这个就要用到canvas
了,需要亿点点相关知识。没有使用过canvas
的,可以去看看这里。canvas
的功能强大,但这里只需要使用到其中一个小功能drawImage
。
先简单介绍下drawIamge
drawImage(image, x, y, width, height)
image参数有几种方式:
- HTMLImageElement: 由
Image()
函数构造出来的,或者任何的<img>
元素 - HTMLVideoElement:用
<video>
元素作为图片源,从当前视频中抓取到当前帧作为一个图像。 - HTMLCanvasElement: 用另一个
<canvas>
元素作为图片源,套娃处理。
x,y参数则是代表canvas
里的起始坐标,一般都是0,0
。
width,height 参数则是代表宽高,即canvas
的大小。
这里我们重点要用到的就是image
参数中的HTMLVideoElement,正好适用于我们的场景,直接传入video
元素即可。
还有一个方法toDataURL
,也是将要用到的:
canvas.toDataURL(type, encoderOptions);
type参数可选,表示图片格式,默认为 image/png,也可以是 image/jpeg 或 image/webp encoderOptions参数也是可选,在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
注意,如果canvas
的高度或宽度是 0,那么会返回字符串“data:,”,要记得设置宽高。
这个方法会返回一个包含 data URL
的字符串,就是 base64格式。
const canvas = document.getElementById("canvas");
const dataURL = canvas.toDataURL();
console.log(dataURL);
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
// blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"
下面来实操看看效果,代码如下:
<div class="box2">
<canvas ref="canvasRef" style="display: none;"></canvas>
<img v-if="takePhotoUrl" :src="takePhotoUrl" alt="Captured Photo">
</div>
// 这是滤镜效果,在绘制canvas是要传入
const filerMap = {
blur: "blur(3px)",
grayscale: "grayscale(1)",
invert: "invert(1)",
sepia: "sepia(1)",
};
// 按下拍照时执行
const takePhoto = () => {
const context = canvasRef.value.getContext("2d");
const videoWidth = videoRef.value.videoWidth;
const videoHeight = videoRef.value.videoHeight;
canvasRef.value.width = videoWidth;
canvasRef.value.height = videoHeight;
context.filter = filerMap[type.value];
context.drawImage(videoRef.value, 0, 0, videoWidth, videoHeight);
takePhotoUrl.value = canvasRef.value.toDataURL();
};
当调用了drawImage
后,再将绘制的图片变成base64格式
,保存在takePhotoUrl.value
中,以便保存时使用。
这里有一个点要注意,就是要给canvas
设置宽高,下面这两行别忘了,不然绘制出来的图片只会是其中一部分。我刚开始用的时候就忘了设置了,还奇怪,咋一直都是绘制了不全的。
canvasRef.value.width = videoWidth;
canvasRef.value.height = videoHeight;
然后还用到了context.filter
,给canvas
添加滤镜效果。当然,还可以添加更多效果,可自行拓展。
效果如下:
保存图片
照片都拍完了,那肯定得保存到系统里好好欣赏才行。浏览器当然也提供了方法让我们能保存图片,主要是使用a
标签。代码如下:
<div class="box2">
<canvas ref="canvasRef" style="display: none;"></canvas>
<img v-if="takePhotoUrl" :src="takePhotoUrl" alt="Captured Photo">
<el-button @click="save">保存</el-button>
</div>
const save = () => {
if (!takePhotoUrl.value) {
return;
}
const el = document.createElement("a");
el.download = "photo"; // 设置下载的文件名,默认是'下载'
el.href = takePhotoUrl.value; // 将takePhotoUrl赋值给a标签的href属性
document.body.appendChild(el);
el.click();
el.remove(); // 移除a标签
};
在点击时动态创建一个a
标签,然后设置下载的文件名,并且将通过canvas
生成的takePhotoUrl
赋值给a
标签的href
属性。然后再自动调用点击事件进行下载,最后再将a
标签给移除掉。
这里download
属性指示浏览器该下载而不是打开该文件,同时该属性值即下载时的文件名。但是,要想使这个属性生效,必须是同源下才可以。即同域名、同协议、同端口号时,设置的download
属性才可以生效,不然就是默认值了。
效果:
可以看到下载下来的图片效果还是挺不错的,是不是感觉还挺好玩的,赶快试试吧。
小节
这节内容主要是介绍了如何利用摄像头来进行拍照,并且将其保存在本地。过程还是挺简单的,重点在于第二个步骤,利用canvas
来绘制图片,掌握了这个,其他的都好理解。
但是,这节还有一个非常重要的点需要知道,为什么可以利用canvas
来绘制图片呢?根本在于从媒体设备中拿到的视频流,其实就是一张张连续、不间断播放的图片,然后canvas
在调用drawImage
时会从video
元素中拿到当前正在显示的图片进行绘制,就是这句话HTMLVideoElement:用video元素作为图片源,从当前视频中抓取到当前帧作为一个图像
。
利用媒体设备拍照片只是一个非常基础的功能。在此基础上,还能实现比如与模型结合,替换背景图或者替换人脸;或是与其他媒体流混合在一起播放等,都是需要用到拍照片中的前两个步骤。区别在于拍照只是利用canvas
绘制一次,而其他的则是要连续不断的绘制。这些功能都会在之后的章节进行介绍,当然,你也可以先动手尝试一下。
下一节来介绍如何录制视频,并保存到本地。
项目地址
项目功能如下:
感兴趣的可以直接去体验一下,欢迎star和提pr