今天这个章节主要是利用电脑的视讯镜头,以canvas为基底,去改变我们的画面色彩、透明度等等。
而我就依照比较重要的重点来分享。
canplay:当影片播放时,触发这个事件。
// 监听是否video正在播放('canplay')video.addEventListener('canplay', paintToCanvas);
navigator.mediaDevices.getUserMedia(constraints):
navigator.mediaDevices
可用来串联与麦克风、摄影机或共享萤幕的连结。getUserMedia()
可以用来显示是否允许开启连结至其设备。getUserMedia(constraints)
的constraints参数有2个,分别为video和audio,此处我们只需要影片不需要音档
且getUserMedia()会返回一个Promise物件
,我们需要利用.then来取得其返回的MediaStream物件
取得MediaStream物件后,我们去处理video的来源,由于作者提示说Chorme、firefox已经不支援video.src = window.URL.createObjectURL(localMediaStream)这个方法,而是改採video.srcObject = localMediaStream;
,但由于还要顾及其他浏览器,如IE因此我们利用try、catch来写兼容写法。
// 获取video并开始播放function getVideo() { // 要video但不要audio // MediaDevices.getUserMedia() // 并且会提示说是否要开启视讯镜头 navigator.mediaDevices.getUserMedia({ video: true, audio: false }) .then(localMediaStream => { console.log(localMediaStream); // MediaStream // active: true // id: "sbREnyqNcRJEaadztxfa2cbVz38AnbBmIyiJ" // onactive: null // onaddtrack: null // oninactive: null // onremovetrack: null try { video.srcObject = localMediaStream; } catch (err) { console.error(`OH NO!!!`, err); video.src = window.URL.createObjectURL(localMediaStream); } // 原始画面 video.play(); })}
HTMLCanvasElement.getContext(): 返回canvas 的上下文,如果上下文没有定义则返回 null。
利用上下文创建一个二维的画布。
const canvas = document.querySelector('.photo');// ctx为我们所要画的对象const ctx = canvas.getContext('2d');
CanvasRenderingContext2D.drawImage():将图像,画布或视频绘製到画布上。
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
image:放canvas的图像来源。
dx, dy:起始位置(x,y)。
dWidth, dHeight:设定宽高。
CanvasRenderingContext2D.getImageData():取出相片每个像素rgba,且会返回一个ImageData物件
ImageData ctx.getImageData(sx, sy, sw, sh);
sx:複製左上角的x座标。
sy:複製左上角的x座标。
sw:複製的宽度。
sh:複製的高度。
ctx.putImageData(imageData, dx, dy): 把某块区域的像素值呈现在指定的位址上
getImageData 跟putImageData是一组的,它可以让你取得每个像素的rgba值然后作变化放回去
。
此处我们设置一个名为pixels的变数,来观察普通面积与pixels的差别,一个点会被拆分成rgba,也就是pixels为area的四倍,可观察出面积与点的关係会差四倍
,会看到我们储存像素点的地方是一个叫做Uint8ClampedArray的array,其内共有1228800个数值所组成,4个为一组,表示一个rgba的像素点要呈现的颜色
。
let pixels = ctx.getImageData(0, 0, width, height); console.log(pixels); ImageData {data: Uint8ClampedArray(1228800), width: 640, height: 480} console.log(`Area: ${width * height},Pixels ${pixels.data.length}`); Area: 307200,Pixels 1228800 console.log(pixels.data[0], pixels.data[1], pixels.data[2], pixels.data[3]); // 106 114 55 255 console.log(pixels.data[0 + 4], pixels.data[1 + 4], pixels.data[2 + 4], pixels.data[3 + 4]); // 109 116 57 255
因为影片会一直更新,所以设置计时器让他一直跑,我们所做的视讯效果也是在这边呼叫。
return setInterval(() => { // 画一个video,从起始位置(0,0),画width,height的宽高 ctx.drawImage(video, 0, 0, width, height); // take the pixels out let pixels = ctx.getImageData(0, 0, width, height); // mess with them // pixels = redEffect(pixels); // console.log(pixels.data[0], pixels.data[1], pixels.data[2], pixels.data[3]); // pixels = rgbSplit(pixels); // ctx.globalAlpha = 0.8; pixels = greenScreen(pixels); // put them back ctx.putImageData(pixels, 0, 0); }, 16);
insertBefore(newnode,existingnode): 在特定的 DOM 元素前面插入新的子节点。
strip.insertBefore(link, strip.firstChild)
,在父元素下,将每次新产的link插入至第一个子元素的最前面
。
download: < a download="filename" >,为a标籤的一个属性,当点击其元素就会跳出另存新档
canvas.toDataURL(type, encoderOptions): 这个语法可以把图片转成 base64,回传含有图像和参数设置特定格式的 data URIs (预设 PNG).,回传的图像解析度为 96 dpi。
type:图像格式,预设为 image/png.
,表示 image/jpeg或是image/webp的图像品质,不加就为预设。
此处为拍照功能的部分。
function takePhoto() { // played the sound snap.currentTime = 0; snap.play(); // 做截图的功能 // canvas.toDataURL(type, encoderOptions); // type:图像格式 预设为 image/png. 表示 image/jpeg 或是 image/webp 的图像品质 不加就为预设。 // 回传含有图像和参数设置特定格式的 data URIs (预设 PNG). 回传的图像解析度为 96 dpi const data = canvas.toDataURL('image/jpeg'); const link = document.createElement('a'); link.href = data; // 创建一个download属性,其预设值为handsome,当点击其元素就会跳出另存新档 link.setAttribute('download', 'handsome'); // 将图加至link中 link.innerHTML = `<img src="${data}" alt="Handsome Man" />`; // Node.insertBefore() 方法将一个节点插入至参考节点之前 // parentNode.insertBefore(newNode, referenceNode); // 将每次新产的link插入至第一个子元素的最前面 strip.insertBefore(link, strip.firstChild);}
以下几种功能,主要都是以四个色板rgba对应0,1,2,3下去做调整,i+=4的原因是因为rgba四个为一组,所以每次跳四个才会到下一个rgba。
红色效果
将r部分的颜色提高,其余减少。
function redEffect(pixels) { // console.log(pixels.data.length); // 1228800 for (let i = 0; i < pixels.data.length; i += 4) { // 在什么位置对其做调色 pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue } return pixels;}
色彩分离
将rgb不同的颜色移动到不同的位置即可。
function rgbSplit(pixels) { for (let i = 0; i < pixels.data.length; i += 4) { // 固定的颜色对不同位置去做变化(渐层) pixels.data[i - canvas.width * 4 * 50] = pixels.data[i + 0]; // RED pixels.data[i - canvas.width * 4 * 30] = pixels.data[i + 1]; // GREEN pixels.data[i - canvas.width * 4 * 10] = pixels.data[i + 2]; // Blue } return pixels;}
过滤去背
只要颜色落在指定区间,就让他变透明(a变为0)。
function greenScreen(pixels) { const levels = {}; document.querySelectorAll('.rgb input').forEach((input) => { levels[input.name] = input.value; }); // 获取对应色板 for (i = 0; i < pixels.data.length; i = i + 4) { red = pixels.data[i + 0]; green = pixels.data[i + 1]; blue = pixels.data[i + 2]; alpha = pixels.data[i + 3]; // 如果红绿蓝介于最大与最小之间, if (red >= levels.rmin && green >= levels.gmin && blue >= levels.bmin && red <= levels.rmax && green <= levels.gmax && blue <= levels.bmax) { // 将alpha设为0 pixels.data[i + 3] = 0; } } return pixels;}
retro风格
加重黄红比例即可。
function oldStyle(pixels) { for (let i = 0; i < pixels.data.length; i += 4) { pixels.data[i + 0] += 150; //red pixels.data[i + 1] += 60; //green pixels.data[i + 2] += 60; //blue pixels.data[i + 3] *= 0.8; //alpha } return pixels;}
负片效果
将颜色变成互补色,利用255去减掉原本取得的颜色即可。
function negativeEffect(pixels) { for (let i = 0; i < pixels.data.length; i += 4) { pixels.data[i + 0] = 255 - pixels.data[i + 0]; //red pixels.data[i + 1] = 255 - pixels.data[i + 1]; //green pixels.data[i + 2] = 255 - pixels.data[i + 2]; //blue } return pixels;}