使用 canvas 操作影片
透過結合 video 元素和 canvas 的功能,您可以即時操作影片資料,為顯示的影片新增各種視覺效果。本教程演示瞭如何使用 JavaScript 程式碼執行色度鍵控(也稱為“綠色螢幕效果”)。
文件內容
下面顯示了用於渲染此內容的 HTML 文件。
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>Video test page</title>
<style>
body {
background: black;
color: #cccccc;
}
#c2 {
background-image: url("media/foo.png");
background-repeat: no-repeat;
}
div {
float: left;
border: 1px solid #444444;
padding: 10px;
margin: 10px;
background: #3b3b3b;
}
</style>
</head>
<body>
<div>
<video
id="video"
src="media/video.mp4"
controls
crossorigin="anonymous"></video>
</div>
<div>
<canvas id="c1" width="160" height="96"></canvas>
<canvas id="c2" width="160" height="96"></canvas>
</div>
<script src="processor.js"></script>
</body>
</html>
從中可以提取的關鍵點是:
- 此文件設定了兩個
canvas元素,ID 分別為c1和c2。Canvasc1用於顯示原始影片的當前幀,而c2用於顯示應用色度鍵控效果後的影片;c2預載入了將用於替換影片中綠色背景的靜態影像。 - JavaScript 程式碼從名為
processor.js的指令碼中匯入。
JavaScript 程式碼
processor.js 中的 JavaScript 程式碼包含三個方法。
初始化色度鍵控播放器
當 HTML 文件初始載入時,會呼叫 doLoad() 方法。此方法負責準備色度鍵控處理程式碼所需的變數,並設定事件監聽器,以便我們能夠檢測使用者何時開始播放影片。
const processor = {};
processor.doLoad = function doLoad() {
const video = document.getElementById("video");
this.video = video;
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
this.c2 = document.getElementById("c2");
this.ctx2 = this.c2.getContext("2d");
video.addEventListener("play", () => {
this.width = video.videoWidth / 2;
this.height = video.videoHeight / 2;
this.timerCallback();
});
};
此程式碼獲取 HTML 文件中特別感興趣的元素(即 video 元素和兩個 canvas 元素)的引用。它還獲取兩個 canvas 中每個 canvas 的圖形上下文的引用。這些將在我們實際執行色度鍵控效果時使用。
然後呼叫 addEventListener() 來開始監視 video 元素,以便在使用者按下影片播放按鈕時獲得通知。為響應使用者開始播放,此程式碼獲取影片的寬度和高度,並將每個值減半(我們將在執行色度鍵控效果時將影片大小減半),然後呼叫 timerCallback() 方法開始監視影片並計算視覺效果。
計時器回撥
計時器回撥最初在影片開始播放時(當發生“play”事件時)被呼叫,然後負責安排自己定期被呼叫,以便為每一幀啟動鍵控效果。
processor.timerCallback = function timerCallback() {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
setTimeout(() => {
this.timerCallback();
}, 0);
};
回撥做的第一件事是檢查影片是否正在播放;如果不是,回撥將立即返回而不做任何事情。
然後它呼叫 computeFrame() 方法,該方法對當前影片幀執行色度鍵控效果。
回撥做的最後一件事是呼叫 setTimeout() 來安排自己儘快再次被呼叫。在實際應用中,你可能會根據影片的幀率來安排這項工作。
操作影片幀資料
下面顯示的 computeFrame() 方法負責實際獲取幀資料並執行色度鍵控效果。
processor.computeFrame = function () {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
const frame = this.ctx1.getImageData(0, 0, this.width, this.height);
const data = frame.data;
for (let i = 0; i < data.length; i += 4) {
const red = data[i + 0];
const green = data[i + 1];
const blue = data[i + 2];
if (green > 100 && red > 100 && blue < 43) {
data[i + 3] = 0;
}
}
this.ctx2.putImageData(frame, 0, 0);
};
當呼叫此例程時,video 元素正在顯示最新的影片資料幀,其外觀如下:

該影片幀被複制到第一個 canvas 的圖形上下文 ctx1 中,指定的高度和寬度是我們之前儲存的用於以一半大小繪製幀的值。請注意,您可以將 video 元素傳遞到上下文的 drawImage() 方法中,將當前影片幀繪製到上下文中。結果如下:

對第一個上下文呼叫 getImageData() 方法會獲取當前影片幀的原始圖形資料的副本。這提供了 32 位畫素影像原始資料,我們可以對其進行操作。然後,我們透過將幀影像資料的總大小除以四來計算影像中的畫素數。
for 迴圈遍歷幀的畫素,提取每個畫素的紅色、綠色和藍色值,並將這些值與預定數字進行比較,這些數字用於檢測將被替換為從 foo.png 匯入的靜態背景影像的綠色螢幕。
在幀影像資料中找到的每個屬於綠色螢幕引數範圍內的畫素,其 alpha 值將被替換為零,表示該畫素完全透明。因此,最終影像的整個綠色螢幕區域將 100% 透明,當它使用 ctx2.putImageData 繪製到目標上下文時,結果將是疊加在靜態背景上。
生成的影像看起來像這樣:

這會隨著影片的播放反覆進行,從而使一幀接一幀地處理並以色度鍵控效果顯示。