너무 추워요. 방금 집들어오다가 안경에 김이 안사라져서 개짜증나네요.
마스크..! 이놈의 마스크 너무 화나는 밤이에요!
오늘은 Day 19 project 포스팅을 하려고 해요. 벌써 19번째..!
dog 기쁩니다 :)
이번 포스팅은 꽤나 길지만.. 재밌는 글이 될거에요! 긴거 개싫음 개귀찮
먼저 양해의 말씀 전해드립니다. 보여드려야해서 어쩔수 없이 이 지경까지 이르렀네요.
webcam 을 작동시킨다. 작동되는 비디오를
canvas
로 가져와서, 사진을 찍어 저장가능하게 하며, 특수효과도 가능하게 한다.
나름.. 규모가 컸던 과제였어요. 허나 이게 유용할 것 같진 않고.. 일단 정리만 해두면 언젠가 사용하겠지, 라는 의미에서 즐겁게 해봤습니다.
const getVideo = () => {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(localMediaStream => {
video.srcObject = localMediaStream;
video.play();
})
.catch(err => {
console.error(`OH NO!!!`, err);
});
}
const paintToCanvas = () => {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;
setInterval( () => {
ctx.drawImage(video, 0, 0, width, height)
// take the pixels out,
let pixels = ctx.getImageData(0, 0, width, height);
// and then mess with them,
// pixels = redEffect(pixels);
// pixels = RGBSplit(pixels);
// ctx.globalAlpha = 0.8;
// and then finally put them back in to ctx.
ctx.putImageData(pixels, 0, 0);
}, 16)
}
const takePhoto = () => {
snap.currentTime = 0 ;
snap.play();
const data = canvas.toDataURL("image/jpeg");
const link = document.createElement("a");
link.href = data;
link.setAttribute("download", "Derek");
link.innerHTML = `<img src = "${data}" alt = Derek />`;
strip.insertBefore(link, strip.firstChild);
}
getVideo();
video.addEventListener("canplay", paintToCanvas);
크게 3가지 함수가 돌아갑니다. 특수효과를 주는 함수는 따로 마지막에 정리할게요 :)
하나하나 함수 보겠습니다.
video
재생함수const getVideo = () => {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(localMediaStream => {
video.srcObject = localMediaStream;
video.play();
})
.catch(err => {
console.error(`OH NO!!!`, err);
});
}
getVideo();
말 그대로, 웹캠으로 비디오를 켜서 재생하는 함수입니다.
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
이 친구를 수행하면 미디어 입력장치를 허용하게 됩니다. MDN 사이트 설명에 보시면 알 수 있어요.
여기서는 promise
를 직접 사용하여 스트림을 사용했습니다.
사실 정확한 원리는 잘 이해가 가진 않지만, 정해 놓은 틀처럼, 웹캠을 사용하고 싶으면 위와 같이 작성하면 쉽게 될 것 같습니다. 🤗
Canvas
로 옮기기재생하는 비디오를 Canvas
로 옮기는 작업이에요! 08번 project에서 다루었던 내용을 참고하시면 더욱 좋을 것 같습니다 :)
const paintToCanvas = () => {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;
setInterval( () => {
ctx.drawImage(video, 0, 0, width, height)
// take the pixels out,
let pixels = ctx.getImageData(0, 0, width, height);
// and then mess with them,
// pixels = redEffect(pixels);
// pixels = RGBSplit(pixels);
// ctx.globalAlpha = 0.8;
// pixels = greenScreen(pixels);
// and then finally put them back in to ctx.
ctx.putImageData(pixels, 0, 0);
}, 16)
}
paintToCanvas
함수는 재생되는 비디오를 캔버스에서 재생이 되도록 하는 함수에요! 다시 한번 눈갱 죄송합니다.
위 움짤을 보시면 우측 상단에 getVideo
함수가 실행된 것이고, 큰 화면은 getVideo
함수를 canvas
로 가져온 것 입니다.
그렇게 하기 위해서, 일단 너비와 높이를 맞춰주었어요. (4줄)
그러고 나서 익명 함수를 16 밀리초마다 실행을 시키는데, video
재생되는 것을 이미지로 따서, 설정한 캔버스에 아주 짧은 시간마다 보여주는 모습입니다. 즉, 빠르게 스샷을 찍어서 canvas
에 보여주니 비디오가 재생되는 것 처럼 보이는 것이죠!
setInterval( () => {
ctx.drawImage(video, 0, 0, width, height)
// take the pixels out,
let pixels = ctx.getImageData(0, 0, width, height);
// and then mess with them,
// pixels = redEffect(pixels);
// pixels = RGBSplit(pixels);
// ctx.globalAlpha = 0.8;
// pixels = greenScreen(pixels);
// and then finally put them back in to ctx.
ctx.putImageData(pixels, 0, 0);
}, 16)
이 함수가 그 부분입니다. drawImage
함수로 이미지를 만들고, 보여주는 함수에요.
중간부터 pixels
는 필터를 주기 위한 작업이라, 추후에 말씀드리겠습니다.
takePhoto
함수다음은 사진을 찍는 함수입니다!
const takePhoto = () => {
snap.currentTime = 0 ;
snap.play();
const data = canvas.toDataURL("image/jpeg");
const link = document.createElement("a");
link.href = data;
link.setAttribute("download", "Derek");
link.innerHTML = `<img src = "${data}" alt = Derek />`;
strip.insertBefore(link, strip.firstChild);
}
아 참고로, snap
은, audio
태그입니다. 사진찍기 버튼 누를때마다 찰칵 소리가 나도록 했어요.
<audio class="snap" src="./snap.mp3" hidden></audio>
이 snap
을 const snap = document.querySelector('.snap')
로 조정했습니다.
1번째 플젝을 보시면 audio
재생에 대해서 자세히 다루고 있어요! 참고 부탁드립니다. :)
앞에서 다뤘듯이, canvas
에 순간순간 사진을 빠르게 보여주니 비디오처럼 보이는 것을 활용합니다.
해당 버튼을 누르면, 사진을 data
로 가져와요!
그래서 단순히 a
태크를 활용해서, setAttribute
함수를 사용하고, innerHTML
와 백틱을 사용해서 HTML
에 추가하여, 보여주는 모습입니다.
다음은 간단하게 언급만 하려는 필터 기능입니다!
const redEffect = (pixels) => {
for(let i = 0 ; i < pixels.data.length; i += 4) {
pixels.data[i] = pixels.data[i] + 70;
pixels.data[i + 1] = pixels.data[i + 1] - 50;
pixels.data[i + 2] = pixels.data[i + 2] * 0.5;
}
return pixels;
}
const 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;
}
이렇게 3개의 필터를 넣을 수가 있어요. 이 함수들은 상단에 paintToCanvas
함수에 주석처리 된 곳을 하나씩만 풀어서 적용할 수 있습니다.
간단히 설명하자면, pixels.data
는 RGBA
값으로, pixels.data[i]
하나하나가 R
값, G
값, B
값, A
값입니다. A
값은 색의 표현이 아니기 때문에 redEffect
함수와 RGBSplit
함수에서는 3개 픽셀만 다루는 모습입니다.
모든 픽셀에 대해서 for문
을 돌며 각각의 픽셀을 R
값은 R
값 끼리 조정하고, G
값은 G
값 끼리만 조정하고.. 그런 로직을 따라갑니다.
RedEffect
함수 : 단순히 각각 픽셀을 조정합니다.RGBSplit
함수 : 3개의 픽셀값을 다른 곳으로 이동시켜, 3개로 분리되게 보입니다. (하단 참조)RGBSplit
함수를 적용시켰을때는,이렇게 웹캠을 사용해보는 과제도 정리해보았습니다!
이런 내용은 굳이 암기하여 숙지 할 필요는 없어보여요. 단순히 필요할때만 구글링해서 찾아보는게 나을 것 같습니다 :) 저는 정리를 목적으로 포스팅 해보았어요!
틀린내용이나 수정할 내용이 있다면 언제든지 피드백 부탁드립니다!
감사합니다!🤗
옷의 RGB 컬러가 인상적이네요.
코드 잘보고갑니다