昨天提到了 ImageBitmap
是一種能夠被繪製到 canvas
元素上的位置圖像,今天我們要來講另一個很像的用法 ImageData,ImageData
不同於 ImageBitmap
的地方是,它可以對圖片裡面的每一個像素點進行操作,而 ImageData
通常搭配著與 canvas
一起使用,下面我們來看看他的用法:
利用 createImageData 創建,以下 ImageData
的寬、高跟原始的 canvas 一樣都是 100*100,而 data
是有 40000 個元素的 Uint8ClampedArray 矩陣
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.rect(0, 0, 100, 100);
ctx.fill();
// ImageData
// { width: 100, height: 100, data: Uint8ClampedArray[40000] }
console.log(ctx.createImageData(100, 100));
那為什麼有 40000 個元素呢?40000 / (100 * 100) = 4,這代表著圖片上的每個像素點都用了 4 個 Uint8ClampedArray
型別的資料儲存,而每個像素的 4 個資料點分別對應的就是 RGBA,數值範圍都是 0~255,其中最後的 A 指的是 alpha,0 是最透明,255 是最不透明
const Uint8ClampedArray = [
// 第一個像素點: R: 255, G: 255, B: 255, A: 255 => 純白色
255, 255, 255, 255,
...
];
在 ImageData
裡像素是由左到右、上到下依序排列的,第 1 個像素點對應的是左上角,接著的第 2 個像素點會往右邊走,走到 100 個像素點時,會從第 2 列開始,因此以上範例中,第 2 列的第 1 個像素點儲存的資料位在 Uint8ClampedArray[401~404]
使用 getImageData 可以取得 canvas
的圖片像素資訊,以下將整張圖片畫在 canvas
上,接著利用 getImageData
方法取得像素資訊:
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 從 (0,0) 原點將整張圖片畫在 canvas 上
ctx.drawImage(img, 0, 0, width, height);
// 取得像素資訊
const imageData = ctx.getImageData(0, 0, width, height);
接續上面,將第一個像素點設為完全透明的,並更新回 canvas
上:
const imageData = ctx.getImageData(0, 0, width, height);
imageData[3] = 0; // 將第一個元素的 alpha 設為 0
ctx.putImageData(imageData, 0, 0);
ImageData
保存著圖片中每個像素的數據,可以改變每個像素點的顏色及透明度。
Uint8ClampedArray
跟 Uint8Array
可以保存的數據類型是一樣的,差別是當其中數值超出邊界(小於 0, 大於 255) 時要怎麼處理,Uint8ClampedArray 利用 截斷(clamp) 的方式來處理數值,會將超出範圍的數字,轉換為上下限的數值:// [0, 255, 0]
const uInt8 = new Uint8Array([0, -1, 256]);
// [0, 0, 255]
const uInt8 = new Uint8ClampedArray([0, -1, 256]);
而超出邊界的這種狀況叫做 溢位(overflow),計算的方式請參考:Overflow
Stackoverflow 上有人進行過分析,ImageData
因為額外存有像素資訊,所以比使用 ImageBitmap
慢多了,所以當不需要修改圖片的像素時,用 ImageBitmap
在效能上會好很多