CSS&JS/⚡Thinkers

[JS] canvas로 video 크로마키 하기

arancia_ 2026. 3. 11. 11:24

codepen 예제는 무료유저라 메인스레드 쓰는 방식으로 했음.
하단 코드는 웹워커 방식

 

메인스레드 방식

See the Pen [canvas] video Chroma by Oh Ikmyeong (@dpffpself) on CodePen.

웹워커 방식

HTML + JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>7.2.3.크로마키</title>
</head>
 
<body>
    <h1>7.2.3.크로마키</h1>
    <p>크로마키 해보자긔</p>
 
    <video id="vid" width="400" autoplay muted controls loop crossorigin="anonymous">
        <!-- <source src="/media/chromacat.mp4" type='video/mp4;'> -->
        <source src="https://cdn.pixabay.com/video/2022/05/11/116648-708909801_large.mp4" type='video/mp4;'>
    </video>
    <canvas id="cv" width="400" height="260"
        style="border:1px solid black; background:url(/media/night.jpg) no-repeat center center / cover;"></canvas>
    <canvas id="scratch" width="300" height="300" style="border:1px solid #ccc; background:#fff;"></canvas>
    <!--  -->
    <script>
        const $vid = document.getElementById("vid");
        const $cv = document.getElementById("cv");
        const ctx = $cv.getContext("2d");
        const $scratch = document.getElementById("scratch");
        const ctxsc = $scratch.getContext("2d");
 
        const worker = new Worker("./worker723.js");
 
        $vid.addEventListener("play", () => {
            const per = 5;
            $scratch.width = $vid.videoWidth / per;
            $scratch.height = $vid.videoHeight / per;
            $cv.width = $vid.videoWidth / per;
            $cv.height = $vid.videoHeight / per;
            post_frame();
        }, { once: true });
 
        worker.addEventListener("message", draw_frame);
 
        function post_frame() {
            const cw = $scratch.width;
            const ch = $scratch.height;
 
            /* 일단 그리고 */
            ctxsc.drawImage($vid, 00, cw, ch);
            /* 프레임 */
            const frame = ctxsc.getImageData(00, cw, ch);
 
            /* webworker 보낼 준비 */
            const arg = {
                frame: frame,
                height: ch,
                width: cw
            }
            /* worker에 보내기 */
            worker.postMessage(arg);
        }//post_frame
 
        function draw_frame(e) {
            const outframe = e.data;
            ctxsc.putImageData(outframe, 00);
 
            ctx.imageSmoothingEnabled = false;
            ctx.clearRect(00, $cv.width, $cv.height);
            ctx.drawImage($scratch, 00, $cv.width, $cv.height);
            /* loop */
            requestAnimationFrame(post_frame);
        }//draw_frame
    </script>
</body>
 
</html>
cs

 

worker723.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
self.onmessage = (event=> {
    /* 이미지 데이터 가져오기 */
    const { data, origin, source, ports } = event;
    const { frame, height, width } = data; //내가 보낸 값
 
 
    for (let i = 0; i < frame.data.length; i += 4) {
        /* 많은 부분이 변한 픽셀 칠하기 */
        const r = frame.data[i];
        const g = frame.data[i + 1];
        const b = frame.data[i + 2];
 
        /* 녹색은 */
        if (g > 100 && g > r * 0.96 && g > b * 1.5) {
            /* 투명하게 */
            frame.data[i + 3= 0;
        }
    }//for
 
    /* 전송 */
    postMessage(frame, [frame.data.buffer]);
}//onmessage
cs