[JS30] Day19: Unreal Webcam Fun
這一天的目標是要透過使用者的鏡頭、video 元素、Canvas 元素來完成,會看到自己的視訊,並可以加上不同的濾鏡,以及有截圖下載的功能。
要使用到使用者硬體設備,需要透過navigator這個介面指令,我們看到 MDN 上的解釋
The Navigator interface represents the state and the identity of the user agent. It allows scripts to query it and to register themselves to carry on some activities.
這告訴 我們navigator會有使用者的各項設施的狀態,我們透過 script 去要求他們提供資訊、進而去操作那些設施。
HTML
<div class="photobooth">
<div class="controls">
<button onClick="takePhoto()">Take Photo</button>
<div class="rgb">
<label for="rmin">Red Min:</label>
<input type="range" min="0" max="255" name="rmin" />
<label for="rmax">Red Max:</label>
<input type="range" min="0" max="255" name="rmax" />
<br />
<label for="gmin">Green Min:</label>
<input type="range" min="0" max="255" name="gmin" />
<label for="gmax">Green Max:</label>
<input type="range" min="0" max="255" name="gmax" />
<br />
<label for="bmin">Blue Min:</label>
<input type="range" min="0" max="255" name="bmin" />
<label for="bmax">Blue Max:</label>
<input type="range" min="0" max="255" name="bmax" />
</div>
</div>
<canvas class="photo"></canvas>
<video class="player"></video>
<div class="strip"></div>
</div>
<audio class="snap" src="./snap.mp3" hidden></audio>
JavaScript
首先,我們要先建立一個 server,而要取得使用者的錢鏡頭視訊資訊,必須要在secure origin的 server 才能執行,因此我們先要下載browser-sync簡單的 server
npm init -y // 建立npm專案
npm i --save-dev browser-sync
--save-dev: https://medium.com/itsems-frontend/nodejs-npm-dependencies-devdependencies-8934f641c8ef
再到package.json
加入啟動 server 的指令
"scripts": {
"start": "browser-sync start --server --files \"*.css, *.html, *.js\""
}, // npm run start
變數抓取,canvas 元素抓取後,透過getContext("2d")才可以進行 2d 的 API 操作。
const video = document.querySelector(".player"); // 放入視訊內容的影片
const canvas = document.querySelector(".photo");
const ctx = canvas.getContext("2d");
const strip = document.querySelector(".strip"); // 截圖
const snap = document.querySelector(".snap"); // 喀擦聲
navigator.mediaDevices
在有了簡單的 server 之後,我們就來要求使用者提供視訊資訊吧
function getVideo() {
navigator.mediaDevices
.getUserMedia({ video: true, audio: false }) // 參數僅帶入影像資訊,getUserMedia會回傳promise
.then((localMediaStream) => {
console.log(localMediaStream);
video.srcObject = localMediaStream; // 放入video元素
video.play(); // 播放視訊
})
.catch((err) => {
console.error(`OH NO!!!`, err);
});
}
getVideo();
canvas
如此一來,我們就成功在螢幕上顯示出我們的前鏡頭視訊,接下來要透過 canvas 的 API 把視訊放到 canvas 元素上,才可以進行調色、截圖、下載等功能。
function paintToCanvas() {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;
return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);
}, 16);
}
video.addEventListener("canplay", paintToCanvas); // paintToCanvas will trigger when the video inits
透過getImageData和putImageData進行調色
// take the pixels out
let pixels = ctx.getImageData(0, 0, width, height);
// mess with them
//
// pixels = redEffect(pixels);
// pixels = rgbSplit(pixels);
// ghosting
// ctx.globalAlpha = 0.1;
// pixels = greenScreen(pixels);
// put them back
ctx.putImageData(pixels, 0, 0);
定義調色的函式
function redEffect(pixels) {
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;
}
function rgbSplit(pixels) {
for (let i = 0; i < pixels.data.length; i += 4) {
pixels.data[i - 150] = pixels.data[i + 0]; // RED
pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
pixels.data[i - 550] = pixels.data[i + 2]; // Blue
}
return pixels;
}
function greenScreen(pixels) {
// hold minimum and maximum rgb;
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
) {
// take it out! By setting alpha, the fourth pixel to 0!
pixels.data[i + 3] = 0;
}
}
截圖函式(takePhoto)
<button onClick="takePhoto()">Take Photo</button>
function takePhoto() {
// play the sound
snap.currentTime = 0;
snap.play();
// take the data out of the canvas
const data = canvas.toDataURL("image/jpeg");
const link = document.createElement("a");
link.href = data;
link.setAttribute("download", "handsome"); // 下載的檔名為handsome
link.innerHTML = `<img src="${data}" alt="Handsome Man" />`; // data-url可以被放入img的src屬性之中
strip.insertBefore(link, strip.firstChild);
}