|
| 1 | +> 在Github上看到了[wesbos](https://twitter.com/wesbos)的一个Javascript30天挑战的[repo](https://github.com/wesbos/JavaScript30),旨在使用纯Js来进行练习,不允许使用任何其他的库和框架,该挑战共30天,我会在这里记录下自己练习的过程和遇到的问题。 |
| 2 | +
|
| 3 | +## Day19 - Webcam Fun |
| 4 | + |
| 5 | +第十九天的练习是使用浏览器的摄像头,实时记录影像,并输出到canvas中,并用canvas对图像进行滤镜的处理。 |
| 6 | +[线上例子](http://htmlpreview.github.io/?https://github.com/winar-jin/JavaScript30-Challenge/blob/master/19%20-%20Webcam%20Fun/index.html)。 |
| 7 | +> 当你看浏览器查看这个在线例子的时候,你会发现并不能看到页面上出现你的视频画面,打开console面板,你会发现如下提示: |
| 8 | +``` |
| 9 | +getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details. |
| 10 | +``` |
| 11 | +意思就是只有在安全的连接模式下,才可以使用getUserMedia()的api获取到摄像头的视频信息,那么什么是安全连接呢,主要有HTTPS,localhost,wss,file,chrome-extension等。 |
| 12 | +更多有关安全连接的信息,请查阅[参考文档](https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features). |
| 13 | + |
| 14 | +对于我们的这份例子,我们通过搭建本地localhost服务器,达到安全连接的方式比较方便,因此我们首先收件本地服务器,打开我们项目中的`package.json`文件,会发现里面包含了唯一一个依赖`browser-sync`,可以创建一个本地的localhost服务器,并实时的检测页面文件的变化。(关于browser-sync,更多的可以查阅[参考文档](https://browsersync.io/docs)),使用`npm install`安装browser-sync依赖,安装成功后运行`npm start`即可运行本地localhost服务器,并实时的检测文件的变化,实时刷新。 |
| 15 | +## 主要思路 |
| 16 | +* 获取到浏览器的摄像头的影像 |
| 17 | +* 将影像的记录导出到canvas中 |
| 18 | +* 通过获取canvas中的图片信息,对图片添加滤镜 |
| 19 | + |
| 20 | +## 获取影像 |
| 21 | +```javascript |
| 22 | +function getVideo(){ |
| 23 | + navigator.mediaDevices.getUserMedia({video:true,audio:false}) |
| 24 | + .then(videostream => { |
| 25 | + console.log(videostream); |
| 26 | + video.src = URL.createObjectURL(videostream); // 创建url(creates a URL for the specified object) |
| 27 | + video.play(); |
| 28 | + }) |
| 29 | + .catch((err) => { |
| 30 | + console.error('OH,Don\'t have permission to use your local cam!',err); |
| 31 | + }); |
| 32 | +} |
| 33 | +``` |
| 34 | +* `navigator.mediaDevices.getUserMedia()`方法提示用户允许使用视频或者音频设备,如果用户点击允许,则返回一个Promise对象,MediaStream对象作为此Promise对象的Resolved[成功]状态的回调函数参数;但如果用户点击拒绝或者媒体可以用的时候,同样返回一个Promise对象,且PermissionDeniedError或者NotFoundError作为此Promise的Rejected[失败]状态的回调函数参数。但是,用户也可以直接取消选择,不同意也不拒绝,所以返回的Promise对象可能既不会触发resolve 也不会触发 reject。参数为一个对象,包含要请求的视频和音频情况,布尔类型,请求权限的话为true,vice via。 |
| 35 | +更详细的内容还请进一步查阅[参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia)。 |
| 36 | + |
| 37 | +* `URL.createObjectURL()`方法是为了创建一个 DOMString 包含一个表示参数中给定的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示着指定的 File 对象或者 Blob 对象。 |
| 38 | +(DOMString 是一个UTF-16字符串。由于JavaScript已经使用了这样的字符串,所以DOMString直接映射到一个String。) |
| 39 | +更详细的内容请进一步查看[参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL)。 |
| 40 | + |
| 41 | +## canvas绘图 |
| 42 | +```javascript |
| 43 | +function printToCanvas(){ |
| 44 | + let width = video.videoWidth; |
| 45 | + let height = video.videoHeight; |
| 46 | + canvas.height = height; |
| 47 | + canvas.width = width; // 勿忘:设置canvas的宽和高 |
| 48 | + console.log(width,height); |
| 49 | + return setInterval(() => { |
| 50 | + ctx.drawImage(video,0,0,width,height); |
| 51 | + |
| 52 | + // get the image data |
| 53 | + let imagedata = ctx.getImageData(0,0,width,height); |
| 54 | + // console.log(imagedata.data); |
| 55 | + |
| 56 | + // mess the image data |
| 57 | + // imagedata = redEffect(imagedata); |
| 58 | + // imagedata = rgbsplit(imagedata); |
| 59 | + // ctx.globalAlpha = 0.2; |
| 60 | + imagedata = greenScreen(imagedata); |
| 61 | + |
| 62 | + // put the image data back |
| 63 | + ctx.putImageData(imagedata,0,0); |
| 64 | + },16); |
| 65 | +} |
| 66 | +``` |
| 67 | +* `ctx.drawImage()`更够将当前的视频流(video)中的一帧画在canvas中。 |
| 68 | +* `ctx.getImageData()`返回一个ImageData对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。[参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData) |
| 69 | +* `ctx.putImageData()`:该方法是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了脏矩形,只能绘制矩形的像素。 [参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData) |
| 70 | +* imagedata中有大量的数据,其中分别代表了图片的颜色信息,分别为red,green,blue,alpha的值,因此我们可以同添加自定义滤镜,通过改变颜色的rgba的值,控制页面的效果。 |
| 71 | + |
| 72 | +## 摄像记录导出到canvas中 |
| 73 | +```javascript |
| 74 | +function takePhoto(){ |
| 75 | + // 播放音效 |
| 76 | + snap.currentTime = 0; |
| 77 | + snap.play(); |
| 78 | + |
| 79 | + // 获取图像数据 |
| 80 | + let data = canvas.toDataURL('image/jpeg'); |
| 81 | + // console.log(data); |
| 82 | + let link = document.createElement('a'); |
| 83 | + link.href = data; |
| 84 | + link.setAttribute('downlond','handsome'); |
| 85 | + link.innerHTML = `<img src=${data} alt=handsome>` |
| 86 | + strip.insertBefore(link,strip.firstChild); |
| 87 | +} |
| 88 | +``` |
| 89 | +* 在没次点击照相的时候,都要求播一遍音效,并且为了模拟现实情况,我们在用户点击时,设置当前的播放时间为0,再播放音效。 |
| 90 | +* `canvas.toDataURL('image/jpeg');`方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。 [参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL) |
| 91 | +* 接下来新建一个a元素,设置其href的值为data。在插入在文档中。实现截图成功的效果。 |
| 92 | + |
| 93 | +## 自定义滤镜 |
| 94 | +```javascript |
| 95 | +// 红色特效滤镜 |
| 96 | +function redEffect(imagedata){ |
| 97 | + for(let i = 0;i<imagedata.data.length;i+=4){ |
| 98 | + imagedata.data[i + 0] += 200; // red |
| 99 | + imagedata.data[i + 1] -= 50; // green |
| 100 | + imagedata.data[i + 2] *= 0.5; // blue |
| 101 | + } |
| 102 | + return imagedata; |
| 103 | +} |
| 104 | + |
| 105 | +// RGB分离 |
| 106 | +function rgbsplit(imagedata){ |
| 107 | + for(let i = 0;i<imagedata.data.length;i+=4){ |
| 108 | + imagedata.data[i - 100] = imagedata.data[i + 0]; // red |
| 109 | + imagedata.data[i + 150] = imagedata.data[i + 1]; // green |
| 110 | + imagedata.data[i - 150] = imagedata.data[i + 2]; // blue |
| 111 | + } |
| 112 | + return imagedata; |
| 113 | +} |
| 114 | + |
| 115 | +// 绿屏(部分消失) |
| 116 | +function greenScreen(pixels) { |
| 117 | + const levels = {}; |
| 118 | + |
| 119 | + document.querySelectorAll('.rgb input').forEach((input) => { |
| 120 | + levels[input.name] = input.value; |
| 121 | + }); |
| 122 | + |
| 123 | + for (i = 0; i < pixels.data.length; i = i + 4) { |
| 124 | + red = pixels.data[i + 0]; |
| 125 | + green = pixels.data[i + 1]; |
| 126 | + blue = pixels.data[i + 2]; |
| 127 | + alpha = pixels.data[i + 3]; |
| 128 | + |
| 129 | + if (red >= levels.rmin |
| 130 | + && green >= levels.gmin |
| 131 | + && blue >= levels.bmin |
| 132 | + && red <= levels.rmax |
| 133 | + && green <= levels.gmax |
| 134 | + && blue <= levels.bmax) { |
| 135 | + // take it out! |
| 136 | + pixels.data[i + 3] = 0; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + return pixels; |
| 141 | +} |
| 142 | +``` |
| 143 | +这部分主要定义了三个滤镜,由于我们通过`ctx.getImageData`可以获取到页面颜色的rgba的值,,因此我们添加滤镜的原理也是这样,通过循环改变一张图片中的所有rgba的值。就不在具体的聊各个滤镜是怎么实现的了。 |
| 144 | + |
| 145 | +## tips |
| 146 | +* `debugger`在源程序中添加debugger,可以使程序在运行时,在此处停止,进入调试模式。 |
| 147 | + |
| 148 | +OK,这样就可以啦!😀 |
0 commit comments