最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Day 19 – Webcam Fun

XAMPP下载 admin 649浏览 0评论
 今天要做的作品是使用 web cam、加上濾鏡效果並提供使用者下載圖像。

今天要做的項目可以說是至今最困難的了,途中遇到很多瓶頸也看了影片才學會做出成品,但在那之前花了許多時間研究 Canvas 以及瀏覽器使用 web cam 的方法,使得看影片時有茅塞頓開的感覺,所以也鼓勵你先試著自己查資料做做看,做出成品後會非常有成就感!

我們一步一步開始吧!

播放 web cam 影片
首先要先取用 web cam 並播放至 HTML 中的 video tag,這段程式碼我是參考 MDN 文件的。使用 navigator.mediaDevices.getUserMedia(..) 時要傳入 video & audio 的參數(例如 true 或是 false 或是 video 的尺寸),並要使用 then 與 catch 指定成功與失敗時的動作

function getVideo() {
navigator.mediaDevices.getUserMedia({video: true, audio: false} )
.then((stream) => {
video.srcObject = stream;
video.onloadedmetadata = function(e) {
video.play();
};
})
.catch(function(err) { console.log(err.name

+ “: ” + err.message); }); // always check for errors at the end.
}
接著可以直接在全域執行,使開啟網頁時就使用 web cam

// global scope
getVideo();
將影片印至 Canvas
由於無法直接在 web cam 的內容無法直接儲存或操作,若要進一步使用需要先將相片/影片印至 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 毫秒將攝影機畫面「印」至 canvas
}, 16)
}
首先先將 canvas 寬、高設定成 video 的寬高(這裡已在 CSS 將 canvas 寬設定 100%,所以看起來很寬),並以每 16 毫秒的頻率將圖像印至 canvas,如果不用 setInteval(..),則只會是靜態的一張圖像

最後要綁定事件,在取得 web cam 使用權並在 video 播放時執行 paintToCanvas

video.addEventListener(‘canplay’, paintToCanvas);  // 當影片可播放時執行
拍照功能
接下來要能「拍下」 canvas 的圖像並放在 strip tag 當中供使用者下載
這裡再細分成幾個子項目

播放音效(HTML 已有 audio)
取得圖像
新增至 .strip
將圖像存至 a tag(當點擊 a 時下載圖像)、a 當中再包著 img
新增至 .strip
播放音效
如同第一天做的,播放音效/影片前要先把時間設為 0,否則預設會播放完才播放第二次,這裡已預先把 click 事件綁定在 button 了

function takePhoto() {
// 播放音效
snap.currentTime = 0;
snap.play();
}
取得圖像
要取得圖像要使用 canvas 的方法 toDataURL(..),這會 return 一個 data: 開頭的連結,我想要儲存成 png 檔案,所以寫成:

function takePhoto() {
// 上略

// 取得相圖像連結
const data = canvas.toDataURL(“image/png”);
}
新增至 .strip
在 HTML 創建新的 a,並結連結設定成剛剛產生的連結

function takePhoto() {
// 上略

// 取得相圖像連結
const data = canvas.toDataURL(“image/png”);
const link = document.createElement(‘a’);
link.href = data;
}
建立 a 下載時的檔名,並在 a tag 當中放入圖片,並放到 .strip 當中

function takePhoto() {
// 上略

// 取得相圖像連結
const data = canvas.toDataURL(“image/png”);
const link = document.createElement(‘a’);
link.href = data;
link.setAttribute(‘download’, ‘Handsome.png’);  // 下載時的檔名
link.innerHTML = `<img src=”${link}” alt=”handsome guy/girl”/>` // 在 a 當中新增 img
strip.insertBefore(link, strip.firstChild);  // 最新的照片會在最前面,使用 appendChild 會放在最後面
}
這裡看到最後將 a 加進 .strip 的方法是 insertBefore,這能使新的圖像永遠在第一位;如要把新的項目放在最後則使用 appendChild

製作濾鏡
大致功能完成了,剩下濾鏡的部分了。這裡用的方法是 ctx.getImageData(..) 用這個方法可以取出相片每個像素的 RGB,我們就是要用這點來做調整

取得圖像 RGB
目的是在 canvas 顯示圖像時同步套用濾鏡,因此要在剛剛的 setInteval 印畫面後套用濾鏡

function paintToCanvas() {
// 上略

return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);  // 每 16 毫秒將攝影機畫面「印」至 canvas

     // 取得圖像資訊,imgData.data 會是一類陣列,imgData.data[0] => red, imgData.data[1] => green, imgData.data[2] => blue, imgData.data[3] => alpha 以此四個一組類推
let pixels = ctx.getImageData(0, 0, width, height);
}, 16)
}
在 pixels 中的 data 可以看到一個巨大的 array-like,這就是我們要的東西

如同上面註解所說,data 中第一項是紅色、第二項是綠色、第三項是藍色、第四項是透明度,以此四個一組類推

接下來使用迴圈改變 RGB 排列順序或值(在全域宣告)

function invertEffect(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i] = 255 – pixels.data[i];         // RED
pixels.data[i + 1] = 255 – pixels.data[i + 1]; // GREEN
pixels.data[i + 2] = 255 – pixels.data[i + 2]; // BLUE
pixels.data[i + 3] = 255;
}
return pixels;
}
這個效果會反轉原本圖像的顏色排序(RGB 的值從 0~255),要注意 i 一次是加 4 而不是加 1

接著在 setInteval 中執行並使用 ctx.putImageData(..)覆寫原本的顏色

function paintToCanvas() {
// 上略

return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);  // 每 16 毫秒將攝影機畫面「印」至 canvas
// 從 (0, 0) 開始複製,範圍為 canvas.width & canvas.height
// 取得圖像資訊,imgData.data 會是一類陣列,imgData.data[0] => red, imgData.data[1] => green, imgData.data[2] => blue, imgData.data[3] => alpha 以此四個一組類推
let pixels = ctx.getImageData(0, 0, width, height);
// 加上濾鏡
pixels = invertEffect(pixels);
// 輸出至 canvas
ctx.putImageData(pixels, 0, 0);  // 從 (0,0) 開始寫入
}, 16)
}
接著再說明成品 RGB 分離的 function

function rgbSplit(pixels) {
for (let i = 0; i < pixels.data.length; i+=4) {
pixels.data[i – 150] = pixels.data[i];         // RED
pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
pixels.data[i – 550] = pixels.data[i + 2]; // BLUE
}
return pixels;
}
與上一個 function invertEffect 相同,只是在操縱 RGB 的數值而已,這裡把當前的 R、G、B換成前/後的顏色。把 pixels = invertEffect(pixels); 換成 pixels = rgbSplit(pixels); 就可以看到效果啦!

转载请注明:XAMPP中文组官网 » Day 19 – Webcam Fun

您必须 登录 才能发表评论!