最近剛接觸到<canvas>標籤,雖然知道它能夠對像素進行一些處理,但卻想不出實戰中能作點什麼。百度找到了haorooms博客中的一篇文章,還挺實用的,就用本身的理解給他的實例作個註釋方便我這個沙比之後回顧時能快點想起來代碼的做用和這些功能的原理。javascript
注意:使用跟ImageData對象相關的功能的時候,Chrome本地運行會出現跨域的問題,致使JS代碼不生效。有一個解決方法就是把文件丟到Web服務器上,而後經過域名去訪問這個網頁就沒問題了,但這得會搭建服務器,還得開着服務器(虛擬機啊,實體機啊什麼的),仍是挺麻煩的。用IE、Edge和Firefox(我電腦就這四個瀏覽器)就不會出現這個問題,代碼可以正常運行。反正下面的內容要是在本地測試的話別用Chrome打開就行了!css
原文的做者稱呼這個功能叫顏色選擇器,我由於有時候會用到FSCapture中的拾色器功能,就感受效果很相近,都是獲取一個區域中的某個顏色值,仍是下意識的叫它拾色器,標記一下,雖然知道不是一種東西。html
原理分析:java
咱們寫一個JS函數綁定畫布上的鼠標指針移動事件(onmousemove),這個函數會獲取當前鼠標的座標,而後以鼠標的座標做爲ImageData區域的左上角定位點,繪製一個1px的ImageData區域,而後提取這個區域中的顏色值(ImageData.data),獲得顏色值以後就能夠修改文本和指定區域的背景顏色了。web
1 <body> 2 <!-- 顯示在畫布上的圖片 --> 3 <img id="isImg" src = "image/001.png" style="display:none"/> 4 5 <!-- 畫布區域 --> 6 <canvas id="drawEle"> 7 您的瀏覽器不支持該標籤 8 </canvas> 9 10 <!-- 顯示顏色的區域 --> 11 <div id="color"> 12 還沒開始 13 </div> 14 15 <script> 16 //獲取畫布 17 var canvas = document.getElementById("drawEle"); 18 var ctx = canvas.getContext("2d"); 19 //獲取圖像 20 var getImg = document.getElementById("isImg"); 21 //在圖像加載完成後再將圖像繪製到畫布上 22 getImg.onload = function() { 23 ctx.drawImage(getImg,0,0,300,150); 24 } 25 26 //獲取顯示顏色的div區域 27 var color = document.getElementById("color"); 28 29 //建立一個函數,用於獲取鼠標放在元素上面時獲取指定位置像素顏色參數 30 function getColor(event) { 31 //layerX、layerY:鼠標在觸發元素上的座標 32 var x = event.layerX; 33 var y = event.layerY; 34 //鼠標的座標爲基點,獲取一個1px大小的像素區域 35 var pixel = ctx.getImageData(x, y, 1, 1); 36 //由於只有1px大小的區域,因此data中正好是一組RGBA值 37 var data = pixel.data; 38 //在控制檯打印,測試用 39 console.log(data); 40 //將data中分散的RGBA值整合成一個字符串(本來是data[0]對應R,data[1]對應G這樣的格式) 41 var rgbaValue = "rgba("+ data[0] + "," + data[1] + "," + data[2] + "," + data[3] + ")" 42 //設置顯示顏色div區域的背景樣式(修改背景顏色) 43 color.style.background = rgbaValue; 44 //修改顯示顏色div區域中的內容 45 color.textContent = rgbaValue; 46 } 47 48 //將函數綁定在鼠標移動事件(mousemove)上 49 canvas.addEventListener('mousemove',getColor); 50 51 </script> 52 </body>
運行效果以下:chrome
(這個就沒有haorooms加註釋版了,由於我以爲兩個版本都差很少就放一個就好了)canvas
haorooms中這個應用是過濾視頻的純色背景,這裏我就先嚐試過濾圖片背景顏色先。跨域
在看到標題的時候我腦中就有這樣一個想法:在使用getImageData獲取到這個元素以後,使用for循環遍歷一遍全部像素,而後用if判斷哪一個像素是咱們要排除的顏色,而後設置Alpha通道的值爲0來隱藏這個像素。既然有本身的想法就先作一作試試看:數組
1 <head> 2 <meta charset="utf-8" /> 3 <title>過濾純色背景(靈光一現版)</title> 4 <style> 5 body { 6 background-color:#AAAAAA; 7 } 8 canvas { 9 background-color:#CCCCCC; 10 margin:0px 5px; 11 } 12 #newDrawEle { 13 background-image:url(image/002.jpg); 14 background-size:cover; 15 } 16 </style> 17 </head> 18 19 <body> 20 <!-- 顯示在畫布上的圖片 --> 21 <img id="isImg" src = "image/001.png" style="display:none"/> 22 23 <!-- 畫布區域 --> 24 <canvas id="drawEle"> 25 您的瀏覽器不支持該標籤 26 </canvas> 27 28 <!-- 過濾後圖像在的區域 --> 29 <canvas id="newDrawEle"> 30 31 </canvas> 32 33 <script> 34 //獲取畫布1(過濾前的畫布) 35 var canvas1 = document.getElementById("drawEle"); 36 var ctx1 = canvas1.getContext("2d"); 37 38 //獲取畫布2(過濾後的畫布) 39 var canvas2 = document.getElementById("newDrawEle"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //獲取圖像 43 var getImg = document.getElementById("isImg"); 44 //在圖像加載完成後再將圖像繪製到畫布上 45 getImg.onload = function() { 46 ctx1.drawImage(getImg,0,0,300,150); 47 48 //獲取畫布1上的所有區域 49 var zone = ctx1.getImageData(0,0,300,150); 50 51 //遍歷一遍區域中的像素 52 for(var i = 0;i<zone.data.length;i+=4) { 53 //將RGBA值格式化 54 var rgbaValue = "rgba(" + zone.data[i] + "," + zone.data[i+1] + "," + zone.data[i+2] + "," + zone.data[i+3] + ")"; 55 //判斷RGBA值 56 if(rgbaValue == "rgba(0,255,0,255)") { 57 //符合過濾條件就把指定元素的Alpha通道調成0(透明) 58 zone.data[i+3] = 0; 59 } 60 } 61 62 //將畫布1上的區域繪製到畫布2上面 63 ctx2.putImageData(zone,0,0); 64 } 65 </script> 66 </body>
隨便搞了張遊戲截圖,而後把背景搞成了純色綠色背景(rgba(0,255,0,255))試試效果:瀏覽器
有沒處理乾淨的綠色圍繞在人物身邊,效果並非很完美。個人身上有種難以想象的光線
目前想到的解決的方法就是增長顏色斷定範圍,好比g的值小於多少多少這樣,因而誕生了下面這個版本的代碼(僅修改了for循環中的內容):
1 //遍歷一遍區域中的像素 2 for(var i = 0;i<zone.data.length;i+=4) { 3 //獲取rgb值 4 let r = zone.data[i + 0]; 5 let g = zone.data[i + 1]; 6 let b = zone.data[i + 2]; 7 8 //判斷RGBA值 9 if(g > 240 && r >= 0 && b >= 0) { 10 //符合過濾條件就把指定元素的Alpha通道調成0(透明) 11 zone.data[i+3] = 0; 12 } 13 }
效果以下:
跟以前的比起來,人物周圍綠色的部分是少了許多,再調整精細點。。。我得去學點美術相關的知識才好作咕咕咕。
原理分析:
首先它建立了一個對象名爲processor(處理器),全部對視頻的處理的方法等相關的一切都放在processor對象中。
processor中第一個方法:doLoad(進行加載),用於初始化參數,給標籤綁定事件函數。在這個方法中,在js中獲取要進行操做的元素,如<video>標籤、畫布。給<video>標籤的play事件(視頻播放事件)綁定函數,綁定的函數設置視頻繪製到畫布上時的寬度和高度,而後執行一次timerCallback()(計時器回調)方法。
processor中第二個方法:timerCallback(計時器回調)。在裏面作判斷,id=video的標籤是否處於暫停播放(this.video.paused)或者(||)中止播放(this.video.ended)的狀態,是則跳出timerCallback(),不執行後面的內容。若是爲false,則繼續執行後面的內容。爲false,調用一次computeFrame(計算框架)來過濾顏色,而後使用setTimeout()方法來循環調用自身(timerCallback()方法),直到視頻中止播放或者暫停播放爲止。
processor中第三個方法:computeFrame(計算框架),該方法用於過濾視頻純色背景。首先繪製id=video中的畫面到畫布1(不作純色過濾的畫布)上,而後建立一個ImageData對象,對象的內容就是畫布1上的內容。建立一個變量計算區域內像素的數量。而後使用for遍歷一遍區域內的全部像素,使用if對像素進行判斷,判斷要過濾的顏色。若是if爲true,則將這個像素的alpha通道值改成0(透明)。過濾完畢後將處理後的內容打印到畫布2上。
最終,給<body>元素的onload事件綁定processor.doLoad()方法。
下面是加了註釋的源碼:
1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms過濾視頻純色背景</title> 4 <style> 5 body { 6 background: black; 7 color:#CCCCCC; 8 } 9 div { 10 float: left; 11 border :1px solid #444444; 12 padding:10px; 13 margin: 10px; 14 background:#3B3B3B; 15 } 16 </style> 17 <script> 18 //建立一個processor對象,裏面包含全部的要處理的內容 19 let processor = { 20 //processor對象中的第一個方法:doLoad(進行加載) 21 doLoad: function() { 22 //獲取<video>標籤 23 this.video = document.getElementById("video"); 24 //獲取第一個畫布:不作純色過濾的畫布 25 this.c1 = document.getElementById("c1"); 26 this.ctx1 = this.c1.getContext("2d"); 27 //獲取第二個畫布:會作純色過濾的畫布 28 this.c2 = document.getElementById("c2"); 29 this.ctx2 = this.c2.getContext("2d"); 30 //儲存this指向processor內部的狀態(不知道該怎麼介紹這個) 31 let self = this; 32 //綁定id="video"對象 33 this.video.addEventListener("play", function() { 34 //設置視頻繪製到畫布上時的寬度和高度 35 self.width = self.video.videoWidth / 2; 36 self.height = self.video.videoHeight / 2; 37 //執行一次timerCallback()函數 38 self.timerCallback(); 39 }, false); //冒泡階段執行 40 }, 41 42 //processor對象中的第二個方法:timerCallback(計時器回調) 43 timerCallback: function() { 44 //判斷video標籤是否處於暫停或者中止播放狀態,是跳出該函數 45 if (this.video.paused || this.video.ended) { 46 return; 47 } 48 //調用computeFrame(計算框架)作顏色過濾 49 this.computeFrame(); 50 //儲存this指向processor內部的狀態(不知道該怎麼介紹這個) 51 let self = this; 52 53 setTimeout(function () { 54 self.timerCallback(); 55 }, 0); 56 }, 57 58 //processor對象中的第三個方法:computeFrame(計算框架) 59 computeFrame: function() { 60 //繪製video中的畫面到畫布1(不作純色過濾的畫布)上 61 this.ctx1.drawImage(this.video, 0, 0, this.width, this.height); 62 //建立一個ImageData對象,這個對象的內容等於畫布1上的內容 63 let frame = this.ctx1.getImageData(0, 0, this.width, this.height); 64 //計算區域內像素的數量(除以4是由於一個完整的RGBA值由四個參數組成) 65 let l = frame.data.length / 4; 66 //遍歷一遍區域內的像素 67 for (let i = 0; i < l; i++) { 68 let r = frame.data[i * 4 + 0]; 69 let g = frame.data[i * 4 + 1]; 70 let b = frame.data[i * 4 + 2]; 71 //判斷是否符合過濾顏色條件,是則修改alpha通道參數值 72 if (g > 100 && r > 100 && b < 43) { 73 frame.data[i * 4 + 3] = 0; 74 } 75 } 76 //打印到畫布上 77 this.ctx2.putImageData(frame, 0, 0); 78 return; 79 } 80 }; 81 </script> 82 </head> 83 84 <body onload="processor.doLoad()"> 85 <div> 86 <video id="video" src="video/haorooms.ogv" controls="true"> 87 </video></div> 88 <div> 89 <canvas id="c1" width="160" height="96"></canvas> 90 <canvas id="c2" width="160" height="96"></canvas> 91 </div> 92 </body>
效果以下(視頻仍是用人家的視頻,找新的視頻還得設置顏色範圍,我懶_(:з」∠)_):
圖片灰度和反相顏色就像下面這樣:
實現這個只須要將RGB值計算後再繪製到畫布上就好了,關鍵就是RGB值的計算公式。
轉換成灰度圖:
R = (R + G + B) /3
G = (R + G + B) /3
B = (R + G + B) /3
將圖片反色:
R = 255 - R
G = 255 - G
B = 255 - B
灰度圖的公式也能夠用加權運算平衡,詳細可百度,這裏使用相加而後除以3。
haorooms中的例子是用兩個按鈕來控制將圖片反色或者轉換成灰度圖,但並不會將圖片還原。這裏咱們先給haorooms中的例子加上註釋,而後本身再寫一個像上面展現的什麼是灰度圖什麼是反色圖那樣,同時顯示原圖、灰度圖、反色圖。
原理分析:
首先先實例化一個Image對象,而後給它指定一張圖片。
而後建立一個方法draw(),這個方法調用的時候須要傳遞一個img類型的參數。
在draw()內,咱們引用畫布,將圖像繪製到畫布上,而後把原圖隱藏(display:none)。建立ImageData對象,內容爲畫布上的全部像素。
在draw()內建立一個函數invert(),這個函數內使用for循環將全部像素的RGB值轉換成反色值,而後繪製到畫布上。在draw()內建立第二個函數grayscale(),這個函數內使用for循環將RGB值轉換成灰度值,而後繪製到畫布上。
最後將這兩個函數綁定到按鈕的click事件(點擊事件)上
1 <body> 2 <canvas id="canvas" width="600" height="300"></canvas> 3 <br> 4 <input type="button" value="灰度" id="grayscalebtn"> 5 <input type="button" id="invertbtn" value="反向"> 6 7 <script> 8 //建立一個img對象 9 var img = new Image(); 10 //給img對象指定圖片 11 img.src = 'image/001.png'; 12 //給img對象的onload事件綁定一個函數,這個函數調用方法draw() 13 img.onload = function () { 14 //調用draw(),傳遞一個img參數 15 draw(this); 16 }; 17 18 //建立方法draw 19 function draw(img) { 20 //引用畫布 21 var canvas = document.getElementById('canvas'); 22 var ctx = canvas.getContext('2d'); 23 //繪製圖像 24 ctx.drawImage(img, 0, 0); 25 //隱藏原圖 26 img.style.display = 'none'; 27 //建立ImageData對象,獲取畫布上的所有內容 28 var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 29 //獲取ImageData中的data集合 30 var data = imageData.data; 31 32 //轉換成反色的方法 33 var invert = function () { 34 for (var i = 0; i < data.length; i += 4) { 35 data[i] = 225 - data[i]; // red 36 data[i + 1] = 225 - data[i + 1]; // green 37 data[i + 2] = 225 - data[i + 2]; // blue 38 } 39 //繪製圖像 40 ctx.putImageData(imageData, 0, 0); 41 }; 42 43 //轉換成灰度的方法 44 var grayscale = function () { 45 for (var i = 0; i < data.length; i += 4) { 46 var avg = (data[i] + data[i + 1] + data[i + 2]) / 3; 47 data[i] = avg; // red 48 data[i + 1] = avg; // green 49 data[i + 2] = avg; // blue 50 } 51 //繪製圖像 52 ctx.putImageData(imageData, 0, 0); 53 }; 54 55 //獲取反色按鈕 56 var invertbtn = document.getElementById('invertbtn'); 57 //綁定點擊事件 58 invertbtn.addEventListener('click', invert); 59 //獲取灰度按鈕 60 var grayscalebtn = document.getElementById('grayscalebtn'); 61 //綁定點擊事件 62 grayscalebtn.addEventListener('click', grayscale); 63 } 64 </script> 65 </body>
運行效果以下:
那接下來咱們本身作一個三個同時顯示的版本練練手吧:
1 <body> 2 <!-- 原圖 --> 3 <div> 4 <img id="isImg" src = "image/001.jpg" /> 5 </div> 6 <!-- 灰度圖的畫布 --> 7 <canvas id="gray-scale"> 8 您的瀏覽器不支持該標籤 9 </canvas> 10 <!-- 反色圖的畫布 --> 11 <canvas id="invert-color"> 12 您的瀏覽器不支持該標籤 13 </canvas> 14 15 <script> 16 //獲取圖像 17 var getImg = document.getElementById("isImg"); 18 19 //在圖像加載完成後再將圖像繪製到畫布上 20 getImg.onload = function() { 21 draw(this); 22 } 23 24 //繪製圖像的函數 25 function draw(img) { 26 27 //獲取灰度圖的畫布 28 var c1 = document.getElementById("gray-scale"); 29 var ctx1 = c1.getContext("2d"); 30 31 //獲取反色圖的畫布 32 var c2 = document.getElementById("invert-color"); 33 var ctx2 = c2.getContext("2d"); 34 35 //設置兩個畫布繪圖區域的大小 36 c1.width = "100"; 37 c1.height = "100"; 38 c2.width = "100"; 39 c2.height = "100"; 40 41 //繪製圖像到兩個畫布上 42 ctx1.drawImage(getImg,0,0,100,100); 43 ctx2.drawImage(getImg,0,0,100,100); 44 45 //獲取灰度圖中的內容 46 var inGrayscale = ctx1.getImageData(0,0,c1.width,c1.height); 47 //遍歷灰度圖中的內容 48 for(var i = 0;i<inGrayscale.data.length;i+=4) { 49 //計算轉換成灰度值後的顏色值 50 var avg = (inGrayscale.data[i+0] + inGrayscale.data[i+1] + inGrayscale.data[i+2]) /3; 51 //設置成灰度圖 52 inGrayscale.data[i + 0] = avg; 53 inGrayscale.data[i + 1] = avg; 54 inGrayscale.data[i + 2] = avg; 55 } 56 //繪製到灰度圖的畫布上 57 ctx1.putImageData(inGrayscale,0,0); 58 59 //獲取反色圖中的內容 60 var inInvertcolor = ctx2.getImageData(0,0,c2.width,c2.height); 61 //遍歷反色圖中的內容 62 for(var i = 0;i<inInvertcolor.data.length;i+=4) { 63 //進行反色處理 64 inInvertcolor.data[i + 0] = 255 - inInvertcolor.data[i + 0]; 65 inInvertcolor.data[i + 1] = 255 - inInvertcolor.data[i + 1]; 66 inInvertcolor.data[i + 2] = 255 - inInvertcolor.data[i + 2]; 67 } 68 //繪製到反色圖的畫布上 69 ctx2.putImageData(inInvertcolor,0,0); 70 } 71 72 </script> 73 </body>
最終運行效果以下:
(沒錯開始的圖片就是我本身作版本的效果噠!)
縮放的作法其實很好猜到,截取畫布a上的一部分,在畫布b上繪製a的一部分時放大顯示。
反鋸齒其實不太好猜。實際上<canvas>中有自帶的屬性能夠設置是否開啓反鋸齒。imageSmoothingEnable屬性,設置爲true表示圖片平滑(默認值),爲false表示圖片不平滑。
原理分析:
首先先實例化一個Image對象,而後給它指定一張圖片。
而後建立一個方法draw(),這個方法調用的時候須要傳遞一個img類型的參數。
在draw()內,咱們引用原圖畫布,將圖像繪製到原圖畫布上,而後把原圖隱藏(display:none)。引用顯示縮放內容區域,還有控制反鋸齒是否啓動的開關。
在draw()內建立一個函數toggleSmoothing()用於進行反鋸齒的設置。這個函數中將反鋸齒開關的布爾值設置成imageSmoothingEnable的值,爲了兼容性還要給每種內核的imageSmoothingEnable都賦上一樣的值。而後給反鋸齒開關綁定toggleSmoothing()函數。
在draw()內建立一個函數zoom()用於座標縮放。先獲取鼠標的座標,而後將鼠標的座標做爲裁剪區域的中心點(作減法運算,讓左上角的位置變成裁剪區域中心,用Math.abs取絕對值保證不出現負數)。而後把zoom()函數綁定在原圖畫布的mousemove(鼠標移動事件)上。
1 <body> 2 <canvas id="canvas" width="300" height="227"></canvas> 3 <canvas id="zoom" width="300" height="227"></canvas> 4 <div> 5 <label for="smoothbtn"> 6 <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn"> 7 Enable image smoothing 8 </label> 9 </div> 10 <script type="text/javascript"> 11 //建立圖片對象 12 var img = new Image(); 13 //給img對象指定圖片 14 img.src = 'image/001.jpg'; 15 //給img對象的onload事件綁定一個函數,這個函數調用方法draw() 16 img.onload = function () { 17 //調用draw(),傳遞一個img參數 18 draw(this); 19 }; 20 21 //建立方法draw 22 function draw(img) { 23 //引用原圖像的畫布 24 var canvas = document.getElementById('canvas'); 25 var ctx = canvas.getContext('2d'); 26 //繪製圖像 27 ctx.drawImage(img, 0, 0); 28 //隱藏原圖 29 img.style.display = 'none'; 30 //引用顯示縮放內容的區域 31 var zoomctx = document.getElementById('zoom').getContext('2d'); 32 //引用反鋸齒開關 33 var smoothbtn = document.getElementById('smoothbtn'); 34 35 //對顯示縮放區域的內容作反鋸齒處理 36 var toggleSmoothing = function (event) { 37 //imageSmoothingEnabled:設置圖片是否平滑,true表示平滑,false表示不平滑 38 zoomctx.imageSmoothingEnabled = this.checked; 39 //下面是對不一樣瀏覽器的兼容imageSmoothingEnable設置,效果同樣 40 //moz:火狐內核。-moz表明火狐的私有屬性 41 zoomctx.mozImageSmoothingEnabled = this.checked; 42 //webkit:webkit內核,經常使用於safari和chrome。-webkit表明Safari和chrome的私有屬性 43 zoomctx.webkitImageSmoothingEnabled = this.checked; 44 //ms:IE內核。-ms表明IE的私有屬性 45 zoomctx.msImageSmoothingEnabled = this.checked; 46 }; 47 //給反鋸齒開關的change(input標籤發生變化時觸發)事件綁定反鋸齒處理的函數 48 smoothbtn.addEventListener('change', toggleSmoothing); 49 50 //縮放功能的函數 51 var zoom = function (event) { 52 //獲取鼠標座標 53 var x = event.layerX; 54 var y = event.layerY; 55 /* 在顯示縮放區域的位置上繪製圖像 56 裁剪區域左上角的點爲鼠標當前座標-5(Math.abs) 57 裁剪範圍爲10x10 58 在顯示縮放區域的畫布上左上角座標爲0,0 59 大小爲200x200 60 */ 61 zoomctx.drawImage(canvas, 62 Math.abs(x - 5), 63 Math.abs(y - 5), 64 10, 10, 65 0, 0, 66 200, 200); 67 }; 68 //給顯示原圖像的畫布的mousemove(鼠標移動事件)綁定縮放功能的函數 69 canvas.addEventListener('mousemove', zoom); 70 } 71 </script> 72 </body>
運行效果以下:
接下來就是本身動手環節了
1 <body> 2 <!-- 顯示在畫布上的圖片 --> 3 <img id="isImg" src = "image/001.jpg" style="display:none"/> 4 5 <!-- 顯示圖片的畫布區域 --> 6 <canvas id="drawEle"> 7 您的瀏覽器不支持該標籤 8 </canvas> 9 10 <!-- 顯示放大內容的畫布區域 --> 11 <canvas id="zoom"> 12 您的瀏覽器不支持該標籤 13 </canvas> 14 15 <!-- 反鋸齒開關 --> 16 <input type="checkbox" name="smoothBtn" id="smoothBtn" checked='checked'> 17 反鋸齒開關 18 </input> 19 20 <script> 21 //獲取圖像 22 var getImg = document.getElementById("isImg"); 23 24 //在圖像加載完成後執行一系列操做 25 getImg.onload = function() { 26 draw(getImg); 27 } 28 29 //實現縮放功能和反鋸齒功能的函數draw() 30 function draw(img) { 31 //獲取圖像處理前的畫布 32 var canvas1 = document.getElementById("drawEle"); 33 var ctx1 = canvas1.getContext("2d"); 34 35 //將圖像繪製到圖像處理前的畫布上 36 ctx1.drawImage(getImg,0,0,300,150); 37 38 //獲取縮放區域的畫布 39 var canvas2 = document.getElementById("zoom"); 40 var ctx2 = canvas2.getContext("2d"); 41 42 //縮放功能的函數 43 var zoom = function(event) { 44 //獲取鼠標座標 45 var x = event.layerX; 46 var y = event.layerY; 47 /* 在顯示縮放區域上繪製圖像 48 裁剪區域左上角的點座標偏移,爲了讓裁剪中心點處於裁剪區域的中間(用Math.abs求絕對值,防止出現負數) 49 裁剪範圍爲10x10 50 顯示在縮放畫布上左上角座標爲0,0 51 大小爲150x150 52 */ 53 ctx2.drawImage(canvas1, Math.abs(x - 5), Math.abs(y - 5), 10, 10, 0, 0, 150, 150); 54 } 55 //將縮放功能的函數綁定在圖像處理前畫布的mousemove(鼠標移動事件)上 56 canvas1.addEventListener("mousemove",zoom); 57 58 //獲取反鋸齒開關 59 var smoothBtn = document.getElementById("smoothBtn"); 60 61 //對顯示縮放區域中的畫面進行反鋸齒處理 62 var toggleSmoothing = function(event) { 63 //設置通用的imageSmoothingEnabled屬性,true爲平滑,false爲不平滑 64 ctx2.imageSmoothingEnabled = this.checked; 65 //設置兼容火狐的imageSmoothingEnabled屬性 66 ctx2.mozImageSmoothingEnabled = this.checked; 67 //設置兼容Safari和Chrome的imageSmoothingEnabled屬性 68 ctx2.webkitImageSmoothingEnabled = this.checked; 69 //設置兼容IE的imageSmoothingEnabled屬性 70 ctx2.msImageSmoothingEnabled = this.checked; 71 } 72 //將反鋸齒開關功能的函數綁定在反鋸齒開關的change(表單發生變化)事件上 73 smoothBtn.addEventListener("change",toggleSmoothing); 74 } 75 </script> 76 </body>
在體驗了haorooms中實例的效果後,忽然有了靈感,就先按照本身的想法作一遍試試看效果。
關於下載畫布上內容這個功能,咱們會用到canvas中的一個方法:toDataURL(),該方法返回一個包含畫布上內容的data URL,能夠傳遞參數指定導出圖片類型,默認格式爲png,分辨率爲96dpi。咱們就用這個方法來下載咱們繪製到畫布上的內容。
原理分析:
首先先獲取畫布,而後獲取下載用的a標籤。建立一個變量brushesDown,初始值爲false。這個變量是用來判斷畫筆是處於激活(按下鼠標左鍵)仍是禁用(擡起鼠標左鍵)的狀態。
接下來定義一個函數burshesMove(),在這個函數中先獲取鼠標的x軸和y軸座標,而後建立一個ImageData對象(createImageData())做爲筆刷的樣式,用遍歷設置一遍這個對象中的像素爲#000000(黑色)。作一個if判斷,判斷筆刷是否激活(if(brushesDown)),激活則使用putImageData()函數在畫布上鼠標指定的位置繪製內容。最後更新下載用的a標籤的連接。
將burshesMove()函數綁定在畫布的mousemove(鼠標移動)事件上。
給畫布的mousedown(鼠標按下事件)設置一個函數:當鼠標按下時,brshesDown設置爲true。
給畫布的mouseup(鼠標擡起事件)設置一個函數:當鼠標擡起時,brshesUp設置爲false。
1 <body> 2 <canvas id="drawEle"> 3 您的瀏覽器不支持該標籤 4 </canvas> 5 <a id="drawDownload" download="在canvas中手繪並下載圖片.png">下載圖片</a> 6 <script> 7 //引用畫布 8 var canvas = document.getElementById("drawEle"); 9 var ctx = canvas.getContext("2d"); 10 11 //獲取a標籤 12 var imageDownload = document.getElementById("drawDownload"); 13 14 //判斷畫筆的狀態,是按下去仍是擡起來 15 var brushesDown = false; 16 //鼠標在畫布上繪製圖形的方法 17 function brushesMove(event){ 18 //獲取鼠標座標 19 var x = event.pageX; 20 var y = event.pageY; 21 22 //建立筆刷(僞),筆刷大小爲3x3 23 var brushes = ctx.createImageData(3,3); 24 //設置筆刷的樣式 25 for(var i = 0;i<brushes.data.length;i+=4) { 26 brushes.data[i + 0] = 0; 27 brushes.data[i + 1] = 0; 28 brushes.data[i + 2] = 0; 29 brushes.data[i + 3] = 255; 30 } 31 //判斷畫筆有沒有激活 32 if(brushesDown) { 33 //在畫布上繪製內容 34 ctx.putImageData(brushes,Math.abs(x-10),Math.abs(y-10)); 35 } 36 37 //更新下載連接URL 38 imageDownload.href = canvas.toDataURL(); 39 40 } 41 42 //綁定在畫布上繪製圖形的方法在畫布的mousemove(鼠標移動事件)上 43 canvas.addEventListener("mousemove",brushesMove); 44 45 //鼠標按下時激活畫筆 46 canvas.onmousedown = function() { 47 brushesDown = true; 48 } 49 50 //鼠標擡起時禁用畫筆 51 canvas.onmouseup = function() { 52 brushesDown = false; 53 } 54 </script> 55 </body>
運行效果以下:
下載的圖片:
定位鼠標的時候有對座標進行減法運算,是由於我在實際測試的時候,若是直接使用鼠標座標,返回的座標是鼠標的中心點,我但願筆刷的中心點在鼠標箭頭上,因此就作了個減法運算。
接下來咱們來分析haorooms中的例子吧:
(這個例子我下載在本地運行的時候是會顯示兩個a標籤的,第二個a標籤是用js生成的。在它網站上運行的例子沒有兩個a標籤,本地就有。)
1 <head> 2 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 3 <title>haorooms_canvas應用</title> 4 <style type="text/css"> 5 /* 設置畫布樣式 */ 6 canvas { 7 /* 設置畫布的背景顏色爲桃紅色 */ 8 background: peachpuff; 9 } 10 11 /* 設置有download屬性的a標籤 */ 12 a[download]{ 13 display: block; 14 width: 280px; 15 background: #369; 16 color: #fff; 17 text-decoration: none; 18 padding: 5px 10px; 19 } 20 21 /* 設置有download屬性的a標籤和 22 h1標籤中的span標籤 */ 23 a[download], h1 span { 24 opacity: 0; 25 } 26 27 /* 設置painted類中 有download屬性的a標籤和 28 painted類中 h1標籤下的 span標籤*/ 29 .painted a[download], .painted h1 span { 30 opacity: 1; 31 transition: 0.5s; 32 } 33 </style> 34 </head> 35 36 <body class="painted"> 37 38 <a href="http://resource.haorooms.com/uploads/demo/canvas/drawanddownload.html#" download="haorooms.png">下載圖片</a> 39 <canvas width="300" height="300"></canvas> 40 41 <script> 42 //給瀏覽器窗口的load(加載完成)事件綁定函數 43 window.addEventListener('load', function(ev) { 44 //使用css選擇器查找文檔中第一個img元素(但是這個變量後面沒用上啊= =) 45 var sourceimage = document.querySelector('img'); 46 //獲取文檔中第一個canvas標籤 47 var canvas = document.querySelector('canvas'); 48 //獲取文檔中第一個a標籤 49 var link = document.querySelector('a'); 50 //獲取畫布的繪圖環境 51 var context = canvas.getContext('2d'); 52 //聲明五個變量並賦值:mouseX(鼠標x軸座標)、mouseY(鼠標y軸座標)、width(畫布寬度)、height(畫布高度)、mousedown(鼠標是否按下) 53 var mouseX = 0, mouseY = 0, 54 width = 300, height = 300, 55 mousedown = false; 56 //設置畫布的寬度和高度 57 canvas.width = width; 58 canvas.height = height; 59 //設置畫筆的顏色 60 context.fillStyle = 'hotpink'; 61 //定義在畫布上鼠標按下(mousedown)後繪製線條的方法 62 function draw(ev) { 63 //判斷mousedown(鼠標是否按下)是否爲true 64 if (mousedown) { 65 //獲取鼠標的座標,做爲畫筆的中心點 66 var x = ev.layerX; 67 var y = ev.layerY; 68 //修改中心點的位置,本來鼠標的中心點在鼠標正中間,這樣計算後在Chrome中是在鼠標左上角,但在Firefox中在鼠標下方(我的測試) 69 x = (Math.ceil(x / 10) * 10) - 10; 70 y = (Math.ceil(y / 5) * 5) - 5; 71 //繪製內容到畫布上,連續拖拽就能夠造成線條。這個畫筆寬10px,高5px。 72 context.fillRect(x, y, 10, 5); 73 } 74 } 75 //建立一個a標籤 76 var link = document.createElement('a'); 77 //設置a標籤顯示的文本 78 link.innerHTML = '下載圖片'; 79 //設置a標籤的連接 80 link.href = "#"; 81 //設置這個a標籤點擊後會下載文件,這個文件的名字爲haorooms.png 82 link.download = "haorooms.png"; 83 //在body元素中插入咱們剛剛建立的a標籤,插入在canvas標籤前面 84 document.body.insertBefore(link, canvas); 85 //給畫布的mouseover(鼠標放置在元素上方)事件綁定一個函數 86 canvas.addEventListener('mouseover', function(ev) { 87 //給body元素插入名稱爲"painted"的類(但是你body已經屬於painted類了啊= =) 88 document.body.classList.add('painted'); 89 }, false); //冒泡階段運行 90 91 //給畫布的mousemnove(鼠標移動事件)事件綁定draw(鼠標按下後繪製線條的函數)函數,冒泡階段運行 92 canvas.addEventListener('mousemove', draw, false); 93 //給畫布的mousedown(鼠標按下事件)事件綁定一個函數,這個函數修改mousedown(鼠標是否按下)的值 94 canvas.addEventListener('mousedown', function(ev) { 95 //修改mousedown(鼠標是否按下)的值爲true 96 mousedown = true; 97 }, false ); //冒泡階段運行 98 99 //給畫布的mouseup(鼠標擡起事件)事件綁定一個函數,這個函數修改mousedown(鼠標是否按下)的值 100 canvas.addEventListener('mouseup', function(ev) { 101 //修改a標籤的下載連接爲畫布內容的dataURL,點擊後就能夠下載畫布的內容 102 link.href = canvas.toDataURL(); 103 //修改mousedown(鼠標是否按下)的值爲false 104 mousedown = false; 105 }, false ); //冒泡階段運行 106 } ,false); //冒泡階段運行 107 </script> 108 </body>
在Chrome中運行效果以下:
在Firefox中運行效果以下:
下載下來是沒有什麼問題,就不展現了。
好的,在haorooms上的五個例子都分析過了一遍,雖然有些功能並非很完美,但咱這篇文章的目的並非完善這些功能,就到此爲止啦。我哪裏註釋寫錯了啊或者是哪裏代碼有問題請留言告訴我,你們相互交流,一塊兒進步嘛!
參考資料:haorooms博客 - canvas的ImageData對象的介紹:https://www.haorooms.com/post/canvas_imageData
MDN - 像素操做:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
MDN - CanvasRenderingContext2D.imageSmoothingEnable:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality
MDN - HTMLCanvasElement.toDataURL:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL