React 웹캠 - 3. Canvas
React 웹캠 시리즈입니다.
- React 웹캠 - 1. Promise 비동기 함수의 이해
- React 웹캠 - 2. getUserMedia
- React 웹캠 - 3. Canvas
- React 웹캠 - 4. Select webcam
HTML5의 강력한 엘리먼트 중 하나인 canvas는 그래픽을 그리는 엘리먼트로서 게임, 애니메이션 및 비디오 영상 처리 등에 사용됩니다. 최근에는 WebGL 즉 웹에서 3D를 그릴 수 있는 성능이 대폭 강화되면서, canvas는 이제 없어서는 안 될 중요한 엘리먼트가 되었습니다.
난 편에서 getUserMedia()로 받은 웹캠 영상을 1초마다 canvas에 그린 뒤 그 위에 몇 가지 덧칠을 해보겠습니다.
내용을 보면서 canvas를 언제 쓰면 좋을지 생각해보세요.
먼저 결과부터 볼까요?
입력 받은 웹캠 영상(왼쪽)을 캔버스(오른쪽)로 그려주는데, 좌우 대칭해서 그려줌과 동시에 Ruben Choi라는 글자와 흰색 사각형을 함께 그려줍니다. Draw to Canvas 버튼은 버튼을 눌렀을 때 한 번만 그려주고 Repeat/Stop 버튼은 200ms (0.2초) 간격으로 계속 그려줍니다. 프라이버시 보호를 위해 얼굴 대신 손금을 보였습니다.
코드를 보며 자세히 살펴봅시다.
예제 코드: Sample Code
소스 위치: src/tutorial/Tutorial05-WebcamCanvas.js
코드 분석 : drawToCanvas()
지난 편에서 설명한 부분들(e.g. getUserMedia, getWebcam 등)은 스킵합니다.
const drawToCanvas = () => {
try {
const ctx = canvasRef.current.getContext('2d');
canvasRef.current.width = videoRef.current.videoWidth;
canvasRef.current.height = videoRef.current.videoHeight;
if (ctx && ctx !== null) {
if (videoRef.current) {
ctx.translate(canvasRef.current.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
ctx.fillStyle = "white";
ctx.fillRect(10, 10, 100, 50);
ctx.font = "15px Arial";
ctx.fillStyle = "green";
ctx.fillText("Ruben Choi", 15, 30);
}
} catch (err) {
console.log(err);
}
}
먼저 살펴볼 함수는 drawToCanvas입니다. 비디오 영상을 캔버스에 1회 그려주는 함수인데, 일반적인 자바스크립트 코드입니다. 다만 이 React 컴포넌트의 <canvas> 엘리먼트를 참조하기 위해 canvasRef.current 및 videoRef.current 문법이 들어가있습니다.
canvasRef.current와 videoRef.current 대신 함수 파라미터로 받아서 처리하도록 하면 순수 자바스크립트 함수가 되겠죠. 핵심은 Line 12: drawImage인데요, drawImage의 문법은 다음과 같습니다.
- drawImage(입력, 시작점 x좌표, 시작점 y좌표, 넓이, 높이)
여기서 주의 깊게 봐야 할 부분은, Line 5~6에서 canvas 넓이/높이를 입력 비디오 넓이/높이에 맞추는 것과 이후 drawImage 부분에서 (입력 비디오 사이즈와 같아진) canvas 넓이/높이만큼 그려주는 부분입니다.
이 순서대로 하지 않으면 영상이 잘리거나 일부만 그려지거나 납작하거나 홀죽하게 그려지는 등 많은 오류가 발생합니다. 직접 해보시면서 겪어보시는 것도 공부에 도움이 되겠습니다.
Line 10~13의 drawImage를 둘러싼 translate, scale, setTransform 코드는 화면을 좌우대칭으로 그리는 코드인데요, 가령 비디오 영상이 좌우 반대로 들어와 사용하기 어려운 경우가 종종 발생하므로 알고 있으면 유익한 코드입니다.
그 다음 fillRect에서는 넓이 100, 높이 50의 하얀색 사각형을 (10, 10) 위치에 그려주고 마지막으로 fillText에서 "Ruben Choi" 문구를 (15, 30) 위치에 그리게 됩니다.
코드 분석 : startOrStop()
그 다음으로 사용할 함수는 반복적으로 그려주는 함수인데요, Timer에 대해서 언급한 적이 없으니 소개 차원에서 함께 살펴보겠습니다. 먼저 타이머는 두 종류가 있는데요,
- setTimeout( expired() , TIME) : TIME 시간이 지난 뒤, expired()를 호출하는 함수
- setInterval( expired(), TIME) : TIME 간격으로, expired()를 반복 호출하는 함수
둘 다 timer를 가리킬 수 있는 객체를 반환하고 이 객체를 이용해서 타이머 만료 전에 액션을 취소할 수 있습니다.
- clearTimeout(timer)
- clearInterval(timer)
const startOrStop = () => {
if (!timer) {
const t = setInterval(() => drawToCanvas(), 200);
setTimer(t);
} else {
clearInterval(timer);
setTimer(undefined);
}
}
우리 함수는 지금 돌고 있는 타이머가 없으면 200ms 단위로 drawToCanvas()를 호출하고 타이머가 있으면 타이머를 종료하는 함수입니다.
코드 분석 : render()
마지막으로 render 부분에서 <video>와 <canvas>를 어떻게 구성하는지 살펴봅시다. 오랜만에 <table> 태그를 사용해봤는데요, React에서는 반드시 thead (table head)와 tbody (table body)를 사용해야 콘솔에 경고가 나지 않습니다.
<table>
<thead>
<tr>
<td>Video</td>
<td>Canvas</td>
</tr>
</thead>
<tbody>
<tr>
<td><video ref={videoRef} autoPlay style={Styles.Video} /></td>
<td><canvas ref={canvasRef} style={Styles.Canvas} /></td>
</tr>
</tbody>
</table>
코드는 직관적이라 설명할 게 없네요.