canvas核心技術-如何繪製圖片和文本

這篇是學習和回顧canvas系列筆記的第三篇,完整筆記詳見:canvas 核心技術javascript

經過上一篇canvas核心技術-如何繪製圖形的學習,咱們知道了如何繪製任意多邊形以及圖片的填充規則。在canvas中應用比較多的還有繪製圖片和文本。這篇文章,咱們就來詳細聊聊圖片和文本的繪製。css

圖片

在canvas中,咱們能夠把一張圖片直接繪製到canvas上,跟使用img標籤相似,不一樣的是,圖片是繪製到canvas畫布上的,而非獨立的html元素。canvas提供了drawImage方法來繪製圖片,這個方法能夠有三種形式的用法,以下,html

  • void drawImage(image,dx,dy);直接將圖片繪製到指定的canvas座標上,圖片由image傳入,座標由dx和dy傳入。
  • void drawImage(image,dx,dy,dw,dh);同上面形式,只不過指定了圖片繪製的寬度和高度,寬高由dw和dh傳入。
  • void drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);這個是最複雜,最靈活的使用形式,第一參數是待繪製的圖片元素,第二個到第五個參數,指定了原圖片上的座標和寬高,這部分區域將會被繪製到canvas中,而其餘區域將忽略,最後四個參數跟形式二同樣,指定了canvas目標中的座標和寬高。

根據參數個數,咱們會分別調用不一樣形式的drawImage,第一種形式最簡單,就是將原圖片直接繪製到目標canvas指定座標處,圖片寬高就是原圖片寬高,不會縮放。第二種形式呢,指定了目標canvas繪製區域的寬高,那麼圖片最終被繪製在canvas上的寬高被固定了,圖片會被縮放,若是指定的dw和dh與原圖片的寬高不是等比咧的,圖片會被壓縮或者拉伸變形。第三種形式,分別指定了原圖片被繪製的區域和目標canvas中的區域,經過sx,sy,sw,sh咱們可只選擇原圖片中某一部分區域,也能夠指定完整的圖片,經過dx,dy,dw,dh咱們待繪製的目標canvas區域。java

let img = document.createElement('img'); //建立img元素
img.src = './learn9/google.png'; //指定img的src
img.addEventListener(
  'load',
  () => {
    ctx.drawImage(img, 0, 0); // 將img元素調用drawImage(img,dx,dy)繪製出來
  },
  false,
);
複製代碼

上面這個示例,這張Google圖片的原始大小是544*184,而canvas區域的大小是默認的300*150。咱們調用了第一種形式,直接將圖片繪製到canvas的座標原點處,圖片沒有被縮放,超出了canvas區域,超出的部分,會被canvas忽略的。有一點須要注意的是,我是在圖片的onload事件中才開始繪製的,由於圖片沒有加載完畢,直接繪製圖片是無效的。下面的代碼示例,我都將只貼出onload事件裏的代碼,圖片加載部分代碼都相同,就省略了。git

let canvasWidth = canvas.width; //獲取canvas寬度
let canvasHeight = canvas.height; //獲取canvas高度
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight); 
複製代碼

咱們把目標canvas區域指定爲canvas的寬高,圖片老是會被繪製在整個canvas中,同時也能夠看到繪製出來的圖片變形了。咱們能夠經過計算出原圖片的寬高比,根據canvas目標區域的寬度來計算出canvas目標區域的高度,或者根據canvas目標區域的高度來計算出canvas目標區域的寬度。github

let imgWidth = img.width; //獲取圖片的寬度
let imgHeight = img.height; //獲取圖片的高度
let targetWidth = canvasWidth; //指定目標canvas區域的寬度
let targetHeight = (imgHeight * targetWidth) / imgWidth; //計算出目標canvas區域的高度
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
複製代碼

從圖能夠看到,根據圖片寬高比計算出來的目標canvas區域,最終,圖片繪製出來的效果是等比例縮放,沒有變形。canvas

咱們再來看看最爲複雜,且最爲靈活的第三種方式。使用這種方式,咱們能夠把Google這張圖片中的紅色的那個o部分繪製出來。數組

ctx.drawImage(img, 143, 48, 90, 90, 0, 0, 90, 90);
複製代碼

Google這張圖中,紅色字母o在原圖片中的座標是(143,48),寬高是90*90,咱們簡單的把這個字母繪製在了canvas的(0,0)座標處,寬高也是90*90。能夠再來複雜點,把這個紅色的字母o,讓它的高度跟canvas的高度同樣,且等比例放大寬度,且圓心正好在canvas中心,實現以下,ide

let oWidth = 90; //獲取字母o的寬度
let oHeight = 90; //獲取字母o的高度
let targetHeight = canvas.height; //指定目標canvas區域的高度
let targetWidth = (oWidth * targetHeight) / oHeight; //計算出目標canvas區域的寬度
let targetX = (canvas.width - targetWidth) / 2; //移動目標canvas座標X
ctx.drawImage(img, 143, 48, oWidth, oHeight, targetX, 0, targetWidth, targetHeight);
複製代碼

drawImage返回的第一參數image,不只能夠是圖片元素,實際上還能夠是canavs元素,video元素。常見的離屏canvas的使用,依就是將離屏不可見的canvas繪製到當前顯示屏幕canvas上。離屏幕canvas這一部分將會在後續遊戲部分中說到,這裏不詳細說了。函數

圖像像素

跟圖片繪製有關的函數還有3個,它們分別是getImageDataputImageDatacreateImageData。這些函數是直接能夠改變圖像中某一個具體的像素值,從而能夠對圖片作一些操做,好比濾鏡。

咱們先來看看getImageData方法,它的調用方式是let imgData = ctx.getImageData(sx,sy,sw,sh),接受四個參數,表示canvas區域的某一個矩形區域,這個矩形區域的左上角座標是(sx,sy),寬高是sw 和sh,它的返回值是一個ImageData類型的對象,包含的屬性有widthheightdata

  • ImageData.width,無符號長整型,表示這個圖像區域的像素的寬度。

  • ImageData.height,無符號長整型,表示這個圖像區域的像素的高度。

  • ImageData.data,一個Uint8ClampedArray數組,數組裏每4個單元,表示一個像素值。一個像數值用RGBA表示的,這4個單元分別表示R,G,B,A,表示意思是紅,綠,藍,透明度,取值範圍是0~255。

須要注意的是,若是咱們在調用ctx.getImageData(sx,sy,sw,sh),參數表示的矩形區域超出了canvas的區域,那麼超出的部分將是用黑色的透明度爲0的RGBA值表示,也就是(0,0,0,0)。

let imgWidth = img.width; //獲取圖片的寬度
let imgHeight = img.height; //獲取圖片的高度
let targetWidth = canvasWidth; //指定目標canvas區域的寬度
let targetHeight = (imgHeight * targetWidth) / imgWidth; //計算出目標canvas區域的高度
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
let imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
console.log(`canvas.width = ${canvasWidth}`);
console.log(`canvas.height = ${canvasHeight}`);
console.log(imgData);
複製代碼

能夠看到,咱們的canvas默認寬高是300*150,經過ctx.getImageData獲取整個canvas區域的像素數據值,獲得的ImageData的設備像素的寬高也是300*150 ,Imagedata.data 的數組的長度是180000,這個是由於,這個imgData的像素數是300*150,而每一個像素是由4個份量表示的,因此300*150*4 = 180000了。

當咱們經過getImageData獲得canvas某一個矩形區域的像素數據以後,咱們能夠經過改變這個imageData.data數組裏的顏色份量值,再將改變後的ImageData經過putImageData方法繪製到canvas上。putImageData的用法有2種調用形式,以下,

  • ctx.putImageData(imgData,dx,dy),這種方式,將imgData繪製到canvas區域(dx,dy)座標處,繪製到canvas的區域的矩形大小就是imgData的矩形的大小。
  • ctx.putImageData(imgData,dx,dy,dirtyX,dirtyY,dirtyW,dirtyH),不只指定了canvas區域(dx,dy),也指定了imgData髒數據區域的(dirtyX,dirtyY)和寬高dirtyW,dirtyH。這種形式,能夠只將imgData種某一塊區域繪製到canvas上。
let canvasWidth = canvas.width;
let canvasHeight = canvas.height;
let img = document.createElement('img');
img.src = './learn9/google.png';
img.addEventListener(
 'load',
 () => {
   let imgWidth = img.width; //獲取圖片的寬度
   let imgHeight = img.height; //獲取圖片的高度
   let targetWidth = canvasWidth; //指定目標canvas區域的寬度
   let targetHeight = (imgHeight * targetWidth) / imgWidth; //計算出目標canvas區域的高度
   ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
   //操做ImageData像素數據
   let imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
   oprImageData(imgData, (r, g, b, a) => {
     if (a === 0) {
       return [r, g, b, 255]; //將透明的黑色像素值改變爲不透明
     }
     return [r, g, b, a];
   });
   //將imgData繪製到canvas的中心。超出canvas區域將被自動忽略
   ctx.putImageData(imgData, canvasWidth / 2, canvasHeight / 2);
 },
 false,
);

// 遍歷像素數據
function oprImageData(imgData, oprFunction) {
 let data = imgData.data;
 for (let i = 0, l = data.length; i < l; i = i + 4) {
   let pixel = oprFunction(data[i], data[i + 1], data[i + 2], data[i + 3]);
   data[i] = pixel[0];
   data[i + 1] = pixel[1];
   data[i + 2] = pixel[2];
   data[i + 3] = pixel[3];
 }
}
複製代碼

上面,咱們遍歷了ImageData中data數組,並將透明度爲0的像素值的透明度變爲1(255/255=1)。在遍歷像素數組時,咱們每便利一次,i 的值加4,這個是由於一個像素值是用4個數組單元值表示的,分別爲R,G,B,A,咱們能夠只改變某一個像素值的某一個份量值,例如透明度。

ctx.putImageData(imgData, canvasWidth / 2, canvasHeight / 2, 79, 27, 50, 50);
複製代碼

咱們經過指定了ImageData中髒數據區域,只繪製了紅色字母o,其餘部分忽略。上面在調用putImageData以前,咱們經過遍歷像素數據改變了部分像素值的透明度,這種能夠操做像素值的方式,在圖像處理等領域是很是有用的,例如常見的圖像灰度和反相顏色等。

//操做ImageData像素數據
let imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
ctx.clearRect(0, 0, canvasWidth, canvasHeight); //清除canvas
oprImageData(imgData, (r, g, b, a) => {
    return [255 - r, 255 - g, 255 - b, a]; //反相顏色
});
ctx.putImageData(imgData, 0, 0);
複製代碼

將顏色份量的RGB值都用255減去原顏色份量值,能夠看到,Google每一個字母的顏色都與原圖片的顏色不同了。這個在改變每一個顏色份量的值,用不通的邏輯計算,就能夠獲得不一樣的處理後的圖片。

//操做ImageData像素數據
let imgData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
ctx.clearRect(0, 0, canvasWidth, canvasHeight); //清除canvas
oprImageData(imgData, (r, g, b, a) => {
    let avg = (r + g + b) / 3;
    return [avg, avg, avg, a]; //灰度
});
ctx.putImageData(imgData, 0, 0);
複製代碼

經過取RGB的平均值,原圖片的每一個字母都是灰色的了,固然,在計算的時候,能夠給每一個份量加一個係數,例如公式let avg = 0.299r + 0.587g + 0.114b,具體應用能夠查看Grayscale

最後來看看createImageData,這個很好理解了,就是建立一個ImageData 對象了,有兩種形式,以下,

  • ctx.createImageData(width,height),能夠指定寬高,建立一個ImageData對象,ImageData.data中的像素值都是一個透明的黑色,也就是(0,0,0,0)。
  • ctx.createImageData(imgData),能夠指定一個已經存在的ImageData 對象來建立一個新的ImageData對象,新建立的ImageData對象的寬高與參數中的ImageData 的寬高同樣,可是像素值就不同了,新建立出來的ImageData的像素值都是透明的黑色,也就是(0,0,0,0)。

文本

在canvas中,咱們不只能夠繪製圖形,圖片,還能夠繪製文本。繪製文本比較簡單了,先設置當前ctx的畫筆的文本樣式,例如,字體大小,字體樣式,對其方式等,跟css中比較類似。

跟文本相關的方法有三個,以下,

  • strokeText(text,x,y,maxWidth?),用描邊的形式繪製指定的文本text,其中也指定了繪製的座標(x,y), 還有最後一個可選參數,最大的寬度,若是所繪製的文本超過了指定的maxWidth,則文本會按照最大的寬度來繪製,那麼文字之間的間距就將減小,文字可能被壓縮。
  • fillText(text,x,y,maxWidth?),同strokeText同樣,只不過,是用填充的形式繪製文本,其參數含義同樣。
  • measureText(text),在當前的文字樣式下,測量繪製文本text會佔據的寬度值,返回一個對象,這個對象有一個width屬性。主要注意的是,必須先設置文本樣式,再來測量纔是準確的。

跟文本直接相關的屬性設置,以下,

  • font,同css中含義同樣,能夠指定文本的字體大小,字體集,字體樣式等。但在canvas中,line-height被強制設置爲normal,會忽略其餘設置的值。
  • textAlign,設置文本的水平對其方式,可選值有:leftrightcenterstartend。默認值是start。各個含義參見textAlign取值
  • textBaseline,設置文本的垂直對齊方式,可選值有:tophangingmiddlealphabeticideographicbottom。默認值是alphabetic。各個含義參見textBaseline取值

固然了,還有一些其餘的屬性也會影響到文本最終繪製出來的效果,好比給當前ctx添加陰影效果,或者設置fillStyle的樣式能夠是圖片或者漸變等。這些算是全局的屬性設置,會影響到canvas全部其餘的繪製,而不單單是文本,因此在這裏,就不詳細討論了。

let textAligns = ['left', 'right', 'center', 'start', 'end']; //textAlign的取值
let colors = ['red', 'blue', 'green', 'orange', 'blueviolet']; //描邊顏色
ctx.font = '18px sans-serif'; //設置font
for (let [index, textAlign] of textAligns.entries()) {
  ctx.save();
  ctx.textAlign = textAlign; // 設置textAlign
  ctx.strokeStyle = colors[index]; //設置描邊顏色
  ctx.strokeText(textAlign, width / 2, 20 + index * 30); //使用描邊繪製文本
  ctx.restore();
}
複製代碼

咱們把textAlign的各個屬性全都設置了一遍,看到startleft的效果同樣,endright的效果同樣,這個是由於startend是與當前本地文字開始方向有關的,若是是左到右開始,那麼startleft同樣,而若是是右到左開始,那麼start是與right效果同樣了。

let textBaselines = ['top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom'];
let colors = ['red', 'blue', 'green', 'orange', 'blueviolet', 'cyan']; //描邊顏色
ctx.font = '18px sans-serif'; //設置font
for (let [index, textBaseline] of textBaselines.entries()) {
  ctx.save();
  ctx.textBaseline = textBaseline; // 設置textBaseline
  ctx.strokeStyle = colors[index]; //設置描邊顏色
  ctx.strokeText('abj', 10 + index * 50, height / 2); //使用描邊繪製文本
  ctx.restore();
}
複製代碼

咱們又把textBaseline的各個值全設置了一遍,看到的效果如上圖。用到最多的應該是topmiddlealphabeticbottom了,其中默認值是alphabetic

measureText在實際業務中也是用到比較多的一個方法了,這個方法能夠測量出在當前設置的文本樣式下,繪製指定的text會佔據的寬度。特別是在繪製表格數據,或者一些分析圖時,須要繪製說明提示性文本,可是又想根據當前鼠標位置來決定文本繪製的座標,以避免超出canvas可見區域。這個方法使用比較簡單,會返回一個帶有width屬性的對象,這個width屬性值就是測量出來的結果。在canvas沒有測量文本高度的方法,然而,在實際時,經常會以W字母測量出來的寬度值加上一點點,就能夠大體認爲是當前文本的高度值了。

ctx.font = '18px sans-serif'; //設置font,必定得先設置font屬性,才能測量準確
let textWidth = ctx.measureText('W').width;
let textHeight = textWidth + textWidth / 6;
console.log(`當前文本W的寬度:${textWidth}`);
console.log(`當前文本W的高度:${textHeight}`);
複製代碼

小結

這篇文章主要是學習了canvas中如何使用drawImage來繪製圖片,以及如何使用getImageDataputImageData來對圖像像素值作處理,好比常見的圖片灰度處理,或者反相顏色等。也回顧了在canvas中繪製文本的一些相關方法和屬性,這些知識在css中比較相似,理解起來也比較容易和簡單。

相關文章
相關標籤/搜索