使用ImageData物件,可直接對pixel陣列資料讀(read)和寫(write)。本單元內容以影像平滑化(反鋸齒)為例,並說明如何將影像保存在canvas中。
ImageData物件可用來存取canvas區中的基礎像素,內含唯獨屬性:
width:影像寬度,以pixels為單位
height:影像高度,以pixels為單位
data:Uint8ClampedArray型態為一維陣列,包含RGBA格式,其整數值介於0~255。每個pixel用4個1byte值分別代表紅、綠、藍、透明度(即RGBA格式),每個顏色值介於0~255。每個pixel在此一維陣列中為連續索引,從左上角pixel的紅色值為陣列中索引為0開始,綠、藍、透明度索引分別為1~3,pixel排列順序為從左到右,由上到下,直到陣列右下角最後一個pixel為止。Uint8ClampedArray包含width*height*4 bytes,索引值從0~(width*height*4 - 1)。例如,讀取影像中的pixel位置為第200 colum,第50 row,藍色成分值,方式如下。
blueComponent = imageData.data[((50*(imageData.width*4)) + (200*4)) + 2];
若要取得影像陣列大小,可使用Uint8ClampedArray的length屬性,如下。
var numBytes = imageData.data.length;
可以使用createImageData()方法創造一個全新空白的ImageData物件。以下提供2種createImageData方法:
var myImageData = ctx.createImageData(width, height);
上述方法有規定大小尺寸,所有pixels的預設值為透明黑色,即所有pixels的RGBA四個數值均為0。
接著介紹第二種使用anotherImageData為參考尺寸,由ImageData物件創造一個新的同樣大小的物件,所有pixels的預設值均為透明黑色。
var myImageData = ctx.createImageData(anotherImageData);
可以使用getImageData()方法,取得canvas內容中ImageData物件的資料,包含所有pixel數據。
var myImageData = ctx.getImageData(left, top, width, height);
上述方法為回傳ImageData物件,此物件代表canvas ctx區域中所有pixel數據。此區域的左上、右上、左下、右下四個角落的pixel位置分別為(left, top)、(left+width, top)、(left, top+height)、(left+width, top+height)。這些座標被稱為canvas的空間座標。
注意:在ImageData物件中,任何超出canvas的pixels均會回傳為透明黑色形式。
本範例使用getImageData()方法顯示鼠標位置的顏色。首先,需要一個正確的滑鼠位置layerX和layerY,再以getImageData()取出pixel陣列中(array)該點的pixel數據(data),最後,使用該陣列pixel數據(array data)在<div>中設置背景顏色和文字,呈現該顏色的資訊。
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
img.onload = function() {
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
};
var color = document.getElementById('color');
function pick(event) {
var x = event.layerX;
var y = event.layerY;
var pixel = ctx.getImageData(x, y, 1, 1);
var data = pixel.data;
var rgba = 'rgba(' + data[0] + ', ' + data[1] +
', ' + data[2] + ', ' + (data[3] / 255) + ')';
color.style.background = rgba;
color.textContent = rgba;
}
canvas.addEventListener('mousemove', pick);
然而getImageData()會產生下列錯誤訊息
SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
原因為rhino.jpg來自於不同的網域,瀏覽器認定為跨來源(cross-origin),會認定canvas已被汙染,並以安全理由阻擋getImageData()取得canvas內容以確保使用端(client)的安全。
若以file://執行html程式,即在本機以瀏覽器執行html檔案,即使將rhino.jpg放置於與html在同一資料夾,仍會被視為跨來源(cross-origin)資源。
故,必須以http://執行html程式,即必須存取http Server,rhino.jpg也需要放置於相同的Server,故若將rhino.jpg與html及js程式放置於http伺服器相同位置,img.src設定修改如下:
img.src='rhino.jpg';
加入exception偵測getImageData()錯誤
try {
var px = ctx.getImageData(x, y, 1, 1);
} catch(err) {
alert(err);
}
若將rhino.jpg與ex10-01.html及ex10-01.js放置於D:\www資料夾中,瀏覽器url執行如下:
http://localhost:8080/www/ex10-01.html
範例10-01: ex10-01.html ex10-01.js rhino.jpg
可以使用putImageData()方法,將自訂的pixel數據(data)放入影像內容中,如下:
ctx.putImageData(myImageData, dx, dy);
以dx及dy作為myImageData的左上角的pixel座標,將myImageData放入canvas中。
舉例來說,若要將myImageData放入整個canvas中,則使用canvas左上角座標,如下。
ctx.putImageData(myImageData, 0, 0);
在本例中,程式反覆處理並改變所有的pixel數值,接著使用putImageData()將改變後的pixel陣列放回canvas中。負片函式只是將每一個顏色值變更為255減去原本的數值,灰階函式則是將每一個pixel的每一個顏色值,以紅、綠、藍三種顏色值的平均數取代。實驗者也可以用權重後的平均值計算公式,如下:
x=0.299r + 0.578g + 0.114b
其中r, g, b分別為pixel中的紅、綠、藍數值。
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
img.onload = function() {
draw(this);
};
function draw(img) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
var invert = function() {
for (var i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red
data[i + 1] = 255 - data[i + 1]; // green
data[i + 2] = 255 - data[i + 2]; // blue
}
ctx.putImageData(imageData, 0, 0);
};
var grayscale = function() {
for (var i = 0; i < data.length; i += 4) {
var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
ctx.putImageData(imageData, 0, 0);
};
var invertbtn = document.getElementById('invertbtn');
invertbtn.addEventListener('click', invert);
var grayscalebtn = document.getElementById('grayscalebtn');
grayscalebtn.addEventListener('click', grayscale);
}
範例10-02:ex10-02.html ex10-02.js rhino.jpg
藉著drawImage()方法、第二個canvas以及imageSmoothingEnabled特徵值,可以將圖局部放大觀察細節。
取得滑鼠位置並將往上下左右各5 pixels區域裁剪,接著以一個區域接著一個區域複製畫布,並調整影像大小符合需求。在放大畫布中將從原畫布中裁剪的10*10 pixel調整為200*200。
zoomctx.drawImage(canvas,
Math.abs(x - 5), Math.abs(y - 5),
10, 10, 0, 0, 200, 200);
因為預設為啟動平滑化(anti-aliasing),但是若要取消平滑化以便可以看清楚每個像素,可以使用切換勾選框(checkbox),驗證imageSmoothingEnabled特徵的效果(需要不同瀏覽器的字首)
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
img.onload = function() {
draw(this);
};
function draw(img) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
var zoomctx = document.getElementById('zoom').getContext('2d');
var smoothbtn = document.getElementById('smoothbtn');
var toggleSmoothing = function(event) {
zoomctx.imageSmoothingEnabled = this.checked;
zoomctx.mozImageSmoothingEnabled = this.checked;
zoomctx.webkitImageSmoothingEnabled = this.checked;
zoomctx.msImageSmoothingEnabled = this.checked;
};
smoothbtn.addEventListener('change', toggleSmoothing);
var zoom = function(event) {
var x = event.layerX;
var y = event.layerY;
zoomctx.drawImage(canvas,
Math.abs(x - 5),
Math.abs(y - 5),
10, 10,
0, 0,
200, 200);
};
canvas.addEventListener('mousemove', zoom);
}
範例10-03:ex10-03.html ex10-03.js rhino.jpg
HTMLCanvasElement提供的toDataURL()方法,可以用來儲存影像。他會回傳一組data URI包括影像資料格式由type參數設定(預設為PNG),回傳影像的解析度為96 dpi。
canvas.toDataURL('image/png')
為預定設定,可以建立一個PNG影像。
canvas.toDataURL('image/jpeg', quality)
建立一組JPG影像,可以提供品質設定,選項範圍從0到1,其中1表示最佳品質,而0表示幾乎無法辨識,但是有最小的檔案大小。
從canvas產生一組data URI後,可以用來當作任何<image>的來源,或是當作一個超連結的下載參數作為存入硬碟。
也可以從canvas建立一個Blob,如下。
canvas.toBlob(callback, type, encoderOptions)
建立一個Blob物件代表canvas中的影像。
範例10-04: ex10-04.html ex10-04.js rhino.jpg