[JS30] -19) Webcam Fun

GY·2021년 11월 4일
0

Javascript 30 Challenge

목록 보기
19/30
post-thumbnail

🍰 웹캠 실행하기

function getVideo() {
  navigator.mediaDevices.getUserMedia({ video: true, audio: false })
  .then(localMediaStream => {
    console.log(localMediaStream);
    video.play();
  });
}


🥛 MediaDevices.getUserMedia()

MediaDevices 인터페이스의 getUserMEdia()메서드 :

  • 사용자에게 미디어 입력 장치 사용 권한 요청
  • 사용자가 수락시 요청한 미디어 종류의 트랙을 포함한 MediaStream 객체로 이행하는 Promise 반환
  • 스트림은 카메라,비디오 녹화장치, 스크린 공유장치 등 비디오 트랙, 오디오 등의 스트림 포함

🥛 URL.createObjectURL()

const objectURL = URL.createObjectURL(object)

URL.createObjectURL() 정적 메서드는 주어진 객체를 가리키는 URL을 DOMString으로 반환한다. 해당 URL은 자신을 생성한 창의 document가 사라지면 함께 무효화된다.

이 방법으로는 에러가 나서 하지 않고, video.srcObject를 사용했다.



🍰 캔버스에 웹캠 영상 표시하기

웹캠 영상을 받아와 재생한 뒤 캔버스에 표시하도록 만들었었는데,
다른 방법도 있었다.

function getVideo() {
  navigator.mediaDevices.getUserMedia({ video: true, audio: false })
  .then(localMediaStream => {
    console.log(localMediaStream);
    video.srcObject = localMediaStream;
    video.play();
    paintToCanvas();
  })

🥛 HTMLMediaElement: canplay

canplay 이벤트는 user agent 가 media 를 재생할 수 있을 때 발생된다.

이것을 이용해 받아온 영상이 재생할 준비가 되었을 때 캔버스에 표시한다.

video.addEventListener('canplay', paintTocanvas)

function paintToCanvas() {
  const width = 640;
  const height = 480;
  canvas.width = width;
  canvas.height = height;
  console.log(width,height)
  return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
  }, 16);
}

🥛 CanvasRenderingContext2D.drawImage()

캔버스에 이미지를 그리는 다양한 방법을 제공한다.

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

image

컨텍스트에 그릴 요소
모든 캔버스 이미지 소스( CanvasImageSource), 특히 a CSSImageValue, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement, an HTMLCanvasElement, an ImageBitmap 등을 허용한다.

이미지 소스를 그릴 수 있기 때문에 영상은 다음과 같은 방식으로 표시한다.
paintToCanvas 함수에서 setInterval로 16밀리세컨드마다 캔버스에 video를 그린다.



🍰 영상 캡처하기

  const data = canvas.toDataURL('image/jpeg');
  const link = document.createElement('a');
  link.href = data;
  link.setAttribute('download', 'downloadImgd');
  link.textContent = 'Download Image';
  strip.insertBefore(link, strip.firstChild);

🥛 HTMLCanvasElement.toDataURL()

canvas.toDataURL(type, encoderOptions);
이 메서드는 HTML5 Canvas에서 제공하는 함수 중의 하나로, 캔버스의 내용을 data URL문자열로 변환한다.
원하는 인코딩 타입으로 해당 데이터를 변환할 수 있다.

  const data = canvas.toDataURL('image/jpeg');
  console.log(data);

  1. 캔버스의 내용을 data URL문자열로 변환한다. type은 image, encoderOption은 jpeg로 설정한다.

  2. link태그의 href를 이 문자열로 할당한다.

  const data = canvas.toDataURL('image/jpeg');
  const link = document.createElement('a');
  link.href = data;

🥛 Element.setAttribute()

Element.setAttribute(name, value);


🥛 HTML <a> download attribute

HTML의 <a> 앵커요소는 href특성을 통해 다른 페이지 혹은 url로 연결할 수 있는 하이퍼링크를 만든다.

download

<a href="/images/myw3schoolsimage.jpg" download>
download속성을 사용하면 링크로 이동하는 대신 사용자에게 url을 저장할 것인지 물어본다.
download 속성을 설정할 때 setAttribute의 value는 다운로드 받은 뒤 설정될 파일의 이름이다.
Element.setAttribute('download','다운로드된 이미지의 이름')

  link.setAttribute('download', 'downloadImgd');

🥛 Node.insertBefore()

참조된 노드 앞에 특정 부모 노드의 자식 노드를 삽입한다.

  const data = canvas.toDataURL('image/jpeg');
  const link = document.createElement('a');
  link.href = data;
  link.setAttribute('download', 'downloadImgd');
  link.textContent = 'Download Image';
  strip.insertBefore(link, strip.firstChild);

캡처하면 다운로드 받을 이미지가 가장 최근 것부터 첫번째에 보여야 하므로 가장 첫번째 자식노드의 전에 새로 생성된 노드를 넣는다.


🥛 캡처한 이미지로 다운로드 표시

  link.setAttribute('download', 'downloadImg');
  link.innerHTML = `<img src="${data}" alt="download Img"/>`
  strip.insertBefore(link, strip.firstChild);


🍰 영상 효과 적용하기

🥛 CanvasRenderingContext2D.getImageData()

캔버스 ImageData의 지정된 부분에 대한 기본 픽셀 데이터를 나타내는 개체를 반환한다.
ctx.getImageData(sx, sy, sw, sh);

    let pixels = ctx.getImageData(0, 0, width, height);
console.log(pixels)

하나의 픽셀들이 보인다.
픽셀은 번갈아가며 red,green,blue,alpha 순으로 반복된다.


🥛 CanvasRenderingContext2D.putImageData()

void ctx.putImageData(imageData, dx, dy);
주어진 ImageData 객체의 데이터를 캔버스에 그린다.

    ctx.putImageData(pixels, 0, 0)

이 때 16밀리세컨드마다 pixels를 redEffect함수의 반환값으로 할당한다.


🥛 redEffect()

function paintToCanvas() {

  return setInterval(() => {
    //ctx.drawImage(video, 0, 0, width, height);
    let pixels = ctx.getImageData(0, 0, width, height);
    pixels = redEffect(pixels);
    ctx.putImageData(pixels, 0, 0)
  }, 16);
}

function redEffect(pixels) {
  for (let i = 0; i < pixels.data.length; i+=4) {
    pixels.data[i+0] = pixels.data[i+0] + 100; //red
    pixels.data[i+1] = pixels.data[i+1] - 50; //green
    pixels.data[i+2] = pixels.data[i+2] * 0.5; //blue
  }
  return pixels;
}

redEffect()

픽셀의 색상은 4개가 번갈아 나타나므로 4개 간격으로 돌면서 첫번째 빨간색 픽셀 값을 키우고, 나머지 색상은 감소시킨다.

paintToCanvas()

  • 영상을 재생하며 getImageData()로 받아오는 pixel값을
  • redEffect로 만든 붉은 색이 두드러지는 pixel값으로 다시 덮어씌운다.
  • 그 다음 이 새로운 pixel값을 putImageData로 캔버스에 씌운다.

    주의할 점은, 위 이미지를 보면 알 수 있듯이 pixels.data의 length로 for문을 설정해야 한다.


🥛 rgbSplit()

function 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;
}

🥛 greenScreen()

html

      <div class="rgb">
        <label for="rmin">Red Min:</label>
        <input type="range" min=0 max=255 name="rmin">
        <label for="rmax">Red Max:</label>
        <input type="range" min=0 max=255 name="rmax">

        <br>

        <label for="gmin">Green Min:</label>
        <input type="range" min=0 max=255 name="gmin">
        <label for="gmax">Green Max:</label>
        <input type="range" min=0 max=255 name="gmax">

        <br>

        <label for="bmin">Blue Min:</label>
        <input type="range" min=0 max=255 name="bmin">
        <label for="bmax">Blue Max:</label>
        <input type="range" min=0 max=255 name="bmax">
      </div>

greenScreen()

function greenScreen(pixels) {
  const levels = {};

  document.querySelectorAll('.rgb input').forEach((input) => {
    levels[input.name] = input.value;
  });

  for (i = 0; i < pixels.data.length; i = i + 4) {
    red = pixels.data[i + 0];
    green = pixels.data[i + 1];
    blue = pixels.data[i + 2];
    alpha = pixels.data[i + 3];

    if (red >= levels.rmin
      && green >= levels.gmin
      && blue >= levels.bmin
      && red <= levels.rmax
      && green <= levels.gmax
      && blue <= levels.bmax) {
      pixels.data[i + 3] = 0;
    }
  }

  return pixels;
}

각 DOM input요소의 이름별로 value를 넣어 levels객체를 만들었다.
levels를 출력해보면 다음과 같다.

각각 rgb의 최솟값과 최댓값을 벗어나지 않을 때 alpha값만 0으로 만든다.


🥛 RGBA - alpha

alpha는 rgb색상에 추가된 값으로, 0.0(완전투명)과 1.0(완전 불투명) 사이의 값을 가진다.

이미지와 같이 색상별로 최솟값과 최댓값을 지정하는 input 태그에서 value를 받아왔다.

    if (levels.rmin <= red <= levels.rmax
      && levels.gmin <= green <= levels.gmax
      && levels.bmin <= blue <= levels.bmax) {
        pixels.data[i + 3] = 0;
    }

지정한 최솟값과 최댓값 사이에 있는 pixel.data 중의 색상 픽셀의 alpha(투명도)를 0으로 만든다.

이런 방식으로 크로마키 천을 사용해서 초록색 부분만 투명하게 만들 수 있다.




Reference

profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.

0개의 댓글