JS30 Day 19 筆記


Posted by GL on 2023-05-25

功能

navigator.mediaDevices.getUserMedia 取得使用者的鏡頭影像,并有拍照與濾鏡效果

Demo

播放

step 1 : 取得頁面 HTML 元素

const video = document.querySelector('.player');// 使用者鏡頭顯示的影像
const canvas = document.querySelector('.photo'); // 鏡頭影像渲染以及產生特效的區塊
const ctx = canvas.getContext('2d'); // canvas 的内容
const strip = document.querySelector('.strip'); // 拍照後顯示照片的區塊
const snap = document.querySelector('.snap'); // 拍照時產生的音效

step 2 : 獲取影像

MediaDevices: getUserMedia() method:在安全連線下使用(HTTPS、Localhost),並且瀏覽器會向使用者取得攝影鏡頭的權限

function getVideo() {
  // navigator.mediaDevices.getUserMedia 拿到使用者的鏡頭影像,並回傳 Promise
  navigator.mediaDevices.getUserMedia({
      // 要影像,不要聲音
      video: true,
      audio: false
    })
    // 將回傳的 MediaStream 帶入 video 的src 中
    .then(localMediaStream => {
      /* console.log(localMediaStream); */
      video.srcObject = localMediaStream
      // 使用 play() 方法拿到連續的影格
      video.play();
    })

    .catch(err => {
      console.error(`OH NO~: `, err);
    })
}

getVideo()

step 3 : 將取得的影像資料畫在 canvas 元素中

MDN-CanvasRenderingContext2D.drawImage()

function paintToCanavas() {
  // 設置 canvas 區塊的寬高為 video 的寬高
  const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;

  // 用setInterval來持續取得目前的影像資訊
  return setInterval(() => {
    // 將畫面擷取:drawImage(img, x, y, width, heigth)
    ctx.drawImage(video, 0, 0, width, height);
    // 每幀數 60 FPS,每個影格約為 16 毫秒(1 / 60 )
  }, 16)
}

step 4 : 拍照功能

HTMLCanvasElement: toDataURL() method
insertBefore()

function takePhoto() {
  // 將拍照音效 snap 跳到第 0 秒
  snap.currentTime = 0;
  // 播放音效
  snap.play();
  // toDataURL() 可以把 canvas 轉為 base64 編碼的圖檔
  const data = canvas.toDataURL('image/jpeg');

  // 變數 link 建立 a 元素
  const link = document.createElement('a');

  // link 的位置設爲 base64 的圖檔 URL
  link.href = data;

  // 設置 link 具有 download 屬性,點擊後即可下載檔案,檔案名稱 handsomeMan
  link.setAttribute('download', 'handsomeMan');

  // 將圖片顯示在 link 中
  link.innerHTML = `<img src="${data}" alt="photo" />`;

  // 在 strip 元素内放入 link(在第一筆的位置)
  strip.insertBefore(link, strip.firstChild);
}

step 5 : 紅色濾鏡功能

CanvasRenderingContext2D: getImageData() method
CanvasRenderingContext2D: putImageData() method

function paintToCanavas() {
  // ...
  return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
    // 拿到 canvans 中所有像素的 r,g,b,alpha資訊
    let pixels = ctx.getImageData(0, 0, width, height);

    // 帶入紅色濾鏡效果
    pixels = redEffect(pixels); 

    // 色相分離
    pixels = rgbSplit(pixels);
    ctx.globalAlpha = 0.8;

    // 綠屏效果,需搭配 slide bar
    pixels = greenScreen(pixels);

    // 將特效處理後 data 再丟回繪製成圖像:putImageData(img, x, y)
    ctx.putImageData(pixels, 0, 0);
  }, 16)
}

getVideo();
// 監聽 canplay 事件,鏡頭準備使用時觸發,執行 paintToCanvas function
video.addEventListener('canplay', paintToCanvas);
// 紅色濾鏡效果
function redEffect(pixels) {
  // for 迴圈獲得所有像素資料,
  // i +=4 是由於每個像素有四筆資料(r,g,b,alpha)
  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;
}

// rgb 色相分離
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;
}

// 綠屏效果:搭配 html 中的 range bar 調整
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) {
      // 、將 alpha 值設爲 0 
      pixels.data[i + 3] = 0;
    }
  }
  return pixels;
}

參考資料:


#JS 30







Related Posts

從實際案例看 class 與 function component 的差異

從實際案例看 class 與 function component 的差異

JS Advanced --pass by value && pass by reference && pass by sharing

JS Advanced --pass by value && pass by reference && pass by sharing

MTR04_0630

MTR04_0630


Comments