iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0

Day14-課題內容

在今天的課題當中,我們要將鏡頭攝影機所取得的影像資料,轉到 canvas 的畫布上,再將 canvas 上的像素資料透過一些方法,將我們的圖片加上特效。[1]
實作連結-正常版
實作連結-幽靈版
實作連結-消失版-可調整上方的bar來變化消失程度哦!

取得影像

首先利用 mediaDevices.getUserMedia() 方法,詢問使用者是否允許使用視訊或音訊的輸入設備,例如鏡頭或麥克風。許可的話,就回傳一 Promise 對象,之後將 MediaStream 作為此 Promise 的回傳函数参數。[2]
再來利用 window.URL.createObjectURL() 將輸入的資訊建立成一個帶有 URL 的 DOMString 以代表參數中所傳入的物件。[3]
最後將我們所建立的 <video> 元素來源,指向前面所建立的 URL 物件,就可以將我們的鏡頭錄到的影片於<video> 中播放出來:

function getVideo() {
    navigator.mediaDevices.getUserMedia({ video: true, audio: false })
    .then(localMediaStream => {
        video.src = window.URL.createObjectURL(localMediaStream);
        video.play();
    })
    .catch(err => {
        window.alert(`OH~ No camera!!!`);
    });
};

投影至 canvas

再來要將取得的影像於 <canvas> 呈現,因此要來介紹下幾個 canvas image 的方法:

  1. drawImage():此方法可以用來在畫布上畫影像。[4]
ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

  1. getImageData():此方法會將目前矩形 canvas 元素上每個像素的數據以 RGBA 格式複製出來,然後回傳一個 ImageData 物件,此物件的 Data 屬性中為包含所有像素資料的陣列。[5]
R - 紅 (0-255)
G - 綠 (0-255)
B - 藍 (0-255)
A - 透明色版 (0-255; 0 是透明的,255 是完全不透明)
[R1, G1, B1, A1, R2, G2, B2, A2,....., Rn, Gn, Bn, An]
  1. putImageData():此方法能將已有的 ImageData 物件資料,繪製到 <canvas> 元素上。
ctx.putImageData(imagedata, dx, dy);
imgData:指定的 ImageData 物件。 
dx:定義 ImageData 物件左上角原點的X座標。
dy:定義 ImageData 物件左上角原點的Y座標。
  1. toDataURL():此方法會回傳含有圖像和參數設置特定格式的 data URIs。(URI 統一資源標識符:是一個用於標識某一網際網路資源名稱的字符串。)

Normal 版本

首先在函式中,將 <canvas> 元素的寬、高設定與 <video> 元素的寬、高相同,避免 <canvas> 元素中印出來的圖形失去原本的比例。然後透過 setInterval() 方法,以每秒16毫秒執行一次將 <video> 元素目前的截圖,透過 drawImage() 方法繪製到 <canvas> 元素上。

function normalMode() {
    const width = video.videoWidth;
    const height = video.videoHeight;
    canvas.width = width;
    canvas.height = height;
    let painting =  setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
  }, 16);
    return painting;
};

拍照

在拍照按鈕觸發事件的函式當中,先將目前 <canvas> 元素的圖形利用 toDataURL() 方法,轉成我們想要的圖檔,然後將目前的 URL 指定給新產生的 <a> 元素,並賦予其 download 屬性,讓 <a> 元素被點擊的時候,就下載 URL 所指的資料:

function takePhoto() {
  // take the data out of the canvas
  const data = canvas.toDataURL('image/jpeg');
  const link = document.createElement('a');
  link.href = data;
  link.setAttribute('download', 'handsome');
  link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;
  strip.insertBefore(link, strip.firsChild);
};

幽靈版 & 消失版

以上是最基礎的作法,了解之後再來我們要來玩弄一下取得的圖像。
setInterval()方法中,透過重組 imageData 中的色素陣列,我們可以將取得的影像處理完之後,在印製到 <canvas> 中,如此一來就可以創造出特殊效果的影像。

let painting =  setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
    // take the pixels out
    let pixels = ctx.getImageData(0, 0, width, height);
    // 執行重組 imageData 中的色素陣列的函式
    pixels = rgbSplit(pixels);
    // 印製到 <canvas> 上 
    ctx.putImageData(pixels, 0, 0);
  }, 16);

然後將陣列中的像素元素,以4個為一組,分別將 R、G、B、A 像素資訊依照我們想做出的特效排列,最後再回傳出來,就可以達成特效!
幽靈版:

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

消失版:

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) {
      // take it out!
      pixels.data[i + 3] = 0;
    }
  };
  return pixels;
};

總結

今天學到以下的方法:

  1. 從自己的鏡頭取的影片
  2. canvas image 的相關方法
  3. 將 canvas 中的圖片資訊儲存成圖片

結合以上的方法,我們將影像繪製在 canvas 元素上,並做出特殊鏡頭的效果,透過修改 imageData 中的像素資料,我們可以做出更多酷炫的濾鏡特效,就讓我們一起來發揮創意吧!

參考資料

  1. javascript30
  2. MediaDevices.getUserMedia()
  3. URL.createObjectURL()
  4. CanvasRenderingContext2D.drawImage()
  5. HTML canvas getImageData() Method

上一篇
JS30-Day18-Add Up Times with Reduce
下一篇
JS30-Day20-Speech Detection
系列文
新手也能懂的JS3030

尚未有邦友留言

立即登入留言