不管是作一些2d的小遊戲,或者製做小圖標,或者摳圖都須要用到這個功能,對圖片的背景進行透明化,是咱們常常須要用到的一個功能。css
一般狀況下咱們都會去下載PS或者美圖秀秀這樣的軟件去製做。html
可是我真的不想僅僅爲了作個透明圖像就去下載這些軟件,這些軟件不只體積大,要下載個半天,放在電腦上也佔空間。前端
最重要的是每次我作這個事情,都須要去臨時百度一下製做透明圖片的方法。git
這些軟件當然強大,可是功能的衆多或者須要一些基礎知識,每每形成了一些門檻。github
簡單點說,雖然瑞士軍刀很6,可是我如今只須要一把起子,我不想知道什麼蒙版圖層,不想在一堆什麼美顏什麼各類濾鏡之中找半天,我就想上傳個圖片點兩下就行了。chrome
那麼能不能在線對圖片進行背景透明化呢?canvas
固然是有的,下面是網址數組
http://www.aigei.com/bgremover/瀏覽器
你覺得我是來推薦網站的?固然不是。app
我之因此提到這個網站,是由於我之前就是用這個作一些處理的,可是真的不是很給力啊。
我並不知道它的原理,也沒有看過它的代碼,可是它的缺陷很明顯:
有問題就解決問題唄,因而就有了今天的小玩意。
與以前的做品同樣,直接將功能寫在這篇博客裏了,因此能夠直接在博客園中使用。
使用方法:
固然按照本懶人的慣例,仍是隻在chrome瀏覽器下實現,因此若是您用其它瀏覽器的話可能沒法正常操做。
不過本應用的核心功能與以往同樣都是能夠在現代瀏覽器中實現的,只是須要您調一下兼容性。
若是您有閒情逸致,想研究一下的話,這是本項目的 GitHub地址,爲了能方便複製進博客園,因此代碼是直接寫在html中的。
少說廢話,如下爲應用:
本應用依然只使用純前端實現,涉及到的技術點以下:
其中技術點1,2,7在以前的一篇博客中有涉及到,因此這裏就再也不贅述,不瞭解的能夠去看一下我以前寫的那篇博客:在博客園裏給圖片加水印(canvas + drag)
那麼,接下來就讓咱們看一下具體的實現吧。
經過type爲file類型的input獲取到文件,而後經過FileReader讀取文件信息後放入到canvas中。
這是前兩步所作的工做,如今咱們須要作的是點擊圖像(其實是canvas)獲取到點擊處的顏色信息。
首先咱們須要獲取到原始圖像的像素信息,並保存下來,這一步在圖片加載時實現,部分代碼以下:
var ctx = document.getElementById('target_canvas').getContext('2d'); imgDataArr = ctx.getImageData(0, 0, imgWidth, imgHeight).data;
小課堂開始:
canvas的getImageData會獲取canvas中指定區域內的圖像信息,返回一個ImageData對象。
ImageData對象的data屬性的值是一個Uint8ClampedArray對象,而這個對象就是圖像的像素信息。
Uint8ClampedArray看名字能夠了解到,它是一個定型數組,裏面的值都是0-255範圍以內的值。
假如咱們有一個圖片只有四個像素,長2px,寬2px。左上角的像素和右下角的像素爲黑色,而右上角和左下角的像素爲白色。
以下圖:
那麼這張圖片會以怎樣的形式存儲在Uint8ClampedArray數組中呢?
首先咱們瞭解到白色的RGBA值爲rgba(255,255,255,255),黑色的RGBA值爲rgba(0,0,0,255)。
那麼這張圖片的分解爲rgba值,分別爲
rgba(0,0,0,255) rgba(255,255,255,255) rgba(255,255,255,255) rgba(0,0,0,255)
那麼顏色值將會以從左到右,從上至下的方式存儲到Uint8ClampedArray數組中,以下:
[0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,255]
小課堂講解完畢,回到正題。
如今咱們已經拿到了原始圖像的像素信息了,並存放在了imgDataArr這個Uint8ClampedArray數組中。
如何獲取鼠標點擊處的像素信息呢?代碼以下:
/** * 獲取圖像數據中指定偏移處的顏色信息 */ function getColorInfo(imgDataArr, offsetX, offsetY) { var pos = canvasInfo.width * 4 * offsetY + offsetX * 4; return { rValue: imgDataArr[pos], gValue: imgDataArr[pos + 1], bValue: imgDataArr[pos + 2], aValue: imgDataArr[pos + 3] } } /** * 非恢復模式下,點擊canvas,以點擊處顏色爲標準,去掉顏色色差在指定色差範圍內的顏色 */ function transparetModeCanvasClick(e) { if (imgDataArr.length === 0) { return; } if (resultImgDataArr.length === 0) { resultImgDataArr = imgDataArr.slice(0) } var clickColorInfo = getColorInfo(resultImgDataArr, e.offsetX, e.offsetY) ... }
咱們會給canvas綁定回調函數爲transparetModeCanvasClick的click事件,那麼,在鼠標點擊canvas後,咱們就能夠獲取到鼠標相對於canvas左上角的點擊位置。
imgDataArr裏面保存的是原始的圖像像素信息,以後還會用到,因此這裏不作處理。
那麼就copy數據到當前像素信息數組resultImgDataArr中。
而後獲取像素信息時須要計算像素在一維數組中的位置:
var pos = canvasInfo.width * 4 * offsetY + offsetX * 4;
根據上面的表達式獲得點擊的那個像素在一維數組中的位置,若是有仔細閱讀以前Uint8ClampedArray存儲像素信息的方式,這個表達式應該不難理解。
在獲取到點擊像素的顏色信息後,咱們須要去遍歷整個canvas的像素信息,對於色差小於指定範圍的顏色作透明化處理。代碼以下:
/** * 獲取圖像數據指定位置顏色與指定顏色的色差 */ function getColorDiff(imgDataArr, pos, colorInfo) { var value = Math.pow(imgDataArr[pos] - colorInfo.rValue, 2) + Math.pow(imgDataArr[pos + 1] - colorInfo.gValue, 2) + Math.pow(imgDataArr[pos + 2] - colorInfo.bValue, 2); return Math.pow(value, 0.5); } /** * 設置圖像數據指定位置爲透明色 */ function setTransparent(imgDataArr, pos) { imgDataArr[pos] = 0; imgDataArr[pos + 1] = 0; imgDataArr[pos + 2] = 0; imgDataArr[pos + 3] = 0; } /** * 非恢復模式下,點擊canvas,以點擊處顏色爲標準,去掉顏色色差在指定色差範圍內的顏色 */ function transparetModeCanvasClick(e) { if (imgDataArr.length === 0) { return; } if (resultImgDataArr.length === 0) { resultImgDataArr = imgDataArr.slice(0) } var clickColorInfo = getColorInfo(resultImgDataArr, e.offsetX, e.offsetY) // 若是是透明顏色則不作處理 if (clickColorInfo.aValue === 0) { return; } var ctx = document.getElementById('target_canvas').getContext('2d'); for (var pos = 0, len = canvasInfo.width * canvasInfo.height * 4; pos < len; pos = pos + 4) { if (getColorDiff(resultImgDataArr, pos, clickColorInfo) < transparentConfig.colorDiff) { setTransparent(resultImgDataArr, pos); } } ctx.putImageData(new ImageData(resultImgDataArr, canvasInfo.width, canvasInfo.height), 0, 0); setCanvasImgToDownloadLink(); }
色差計算公式爲將rgb三個值的顏色相減,將他們的平方和進行開方便可。
這裏其實存在一個優化點:
一、一般咱們設爲透明色的顏色,每次實際上都比較單一,那麼在這裏能夠設一個臨時數組存放已經比較過色差的顏色。再次要對一個顏色比較色差前,能夠查看顏色是否在這個數組中,從而避免再次計算色差,所以對於大圖像而言,存在不少像素點,這種大量計算能儘可能減小的話可讓操做更快。
二、這裏的循環也能夠進行優化。倒序循環 + Duff's Device能夠進行優化。
之因此在這裏強調這一點,是由於在這裏對大圖進行消除單一顏色爲透明色時,確實須要消耗更多時間。(不過我還能接受,因此暫時不理了)
至於設置圖片爲透明色,實際上只須要將
imgDataArr[pos + 3] = 0;
便可。
可是爲了和通常聲明的透明顏色保持一致,仍是將其餘幾個值都設爲0。
將數據設爲透明色的數據以後,須要調用canvas的putImageData方法將整個圖像的數據設置到canvas中。
至此,咱們完成了對圖像進行透明化處理的整個過程。
可是仍然不夠,由於對於複雜圖像而言,這種方式處理起來太過粗暴,沒法作到精細化的處理。
因此接下來咱們要實現恢復模式,對於處理的很差的地方進行恢復原始圖像的操做。
在恢復模式下,當咱們鼠標移動到canvas上時,鼠標顯示爲一個小方框,小方框內是原始圖像。
當咱們點擊鼠標時,小方框內的圖像會從新覆蓋到當前圖像上,從而達到恢復原有圖像的效果。
全篇下來,其實在這個地方纔開始顯得有趣。
最初方案(隱藏鼠標 + canvas)
在想到經過這個辦法進行精細化操做以後,第一反應是隱藏鼠標,並在移動鼠標時跟隨一個小的canvas,這個小canvas中顯示的是原始圖像。
事實上最開始也是這麼作的,若是各位有興趣的話能夠參考我github上的提交方案,上面有這個方案的實現。
雖然這個方案能夠實現效果,可是存在一個很明顯的性能問題,操做起來會有頓卡的感受。
緣由就是在進行鼠標移動的時候,會頻繁的計算圖像信息,再寫入到小canvas中。
雖而後來加了個防抖函數,並將移動方框放在防抖函數外,使得紅色小框能夠即時移動,其中的內容不會出現頓卡,可是由於防抖函數的存在,必然會有一點小小的延遲。
做爲一個懶人其實我以爲作到這裏就能夠了,由於這樣我也能夠用了。
然而,糾結了半天仍是改了,這種卡頓實在是蛋疼得緊。
如今的方案(隱藏鼠標 + background-image)
如今的方案是在鼠標移動時,隱藏鼠標,並在鼠標那裏加一個div,div裏面設置原始圖像的背景圖片。
在移動鼠標時,不只會對鼠標的位置進行從新計算(說是位置,實際上用的是translate,而不是top+left),還會對背景圖片的位置進行從新計算,這樣就能夠實現一樣的效果了。
經過這種方案,在個人電腦上移動紅框和計算恢復圖像的方式很流暢,徹底感受不到卡頓。
若是在低配電腦上有卡頓的狀況,這裏也能夠加一個防抖函數來處理。
那麼讓咱們如今來看一看恢復模式下鼠標在canvas上移動時的代碼:
// 根據鼠標的偏移位置獲取recover_img位置 function getRecoverImgPos(e) { // 給鼠標位置+1,是爲了讓recover_img不會出如今鼠標下方,從而使得鼠標點擊時不會點擊在recover_img上 return { x: e.offsetX + 1, y: e.offsetY + 1 } } /** * 恢復模式下,鼠標在canvas上移動,呈現原先圖像 */ function recoverModeCanvasMove(e) { if (imgDataArr.length === 0) { return; } var $recoverImg = $("#recover_img"); var recoverImgPos = getRecoverImgPos(e) if (recoverImgPos.x > canvasInfo.width - recoverSize || recoverImgPos.y > canvasInfo.height - recoverSize) { $recoverImg.hide(); return; } else { $recoverImg.show(); } $recoverImg.css({ transform: 'translate(' + recoverImgPos.x + 'px,' + recoverImgPos.y + 'px)', 'background-position': (-recoverImgPos.x - 1) + 'px ' + (-recoverImgPos.y - 1) + 'px' }); }
在上面的代碼中咱們會根據鼠標的位置從新計算恢復圖像所在的div的位置,而後判斷是否觸邊來決定是否隱藏。
接着再來計算其位置。
這裏有一個坑點在註釋裏面也寫了,就是恢復圖像實際上並無和鼠標重疊,以防止咱們在點擊時點到恢復圖像上而不是canvas上。
另外若是你們細心查看css樣式的話,會發現兩個小坑點:
而後再來看看點擊恢復圖像的代碼:
/** * 恢復模式下,點擊canvas,將點擊處指定範圍內圖像恢復原樣 */ function recoverModeCanvasClick(e) { if (imgDataArr.length === 0) { return; } var recoverImgPos = getRecoverImgPos(e); for (var i = 0, ylen = recoverSize; i < ylen; i++) { var pos = canvasInfo.width * 4 * (recoverImgPos.y + i) + recoverImgPos.x * 4; for (var j = pos, xlen = pos + recoverSize * 4; j < xlen; j++) { resultImgDataArr[j] = imgDataArr[j] } } var ctx = document.getElementById('target_canvas').getContext('2d'); ctx.putImageData(new ImageData(resultImgDataArr, canvasInfo.width, canvasInfo.height), 0, 0); setCanvasImgToDownloadLink() }
一樣是先根據鼠標位置獲取恢復圖像的位置,而後根據偏移量去計算位置。
咱們得注意到雖然咱們的恢復圖像是一塊完整的相鏈接的區域,可是在
Uint8ClampedArray數組中的數據並非相鏈接的,須要咱們去計算。
將最開始咱們保存初始圖像像素數組imgDataArr賦值到當前處理的圖像像素數組中。
最後將處理好的像素數組以putImageData的方式放入數組便可。
這個小應用如今能夠知足個人需求了,可是它依然存在不少不足與改進空間,好比:
本期分享結束,依然是一個小應用,依然是一堆你可能知道也可能不知道的小知識點。
最後例行說明,右下角的精靈球是點贊,這已是我連續幾篇文章說明了,而且那麼大幾個字已經寫明瞭。
由於有幾個園友引用了這個精靈球,因此大家應該也造成了精靈球便是點讚的心理預期和用戶習慣了吧。
若是你不當心點錯了,請刷新頁面,那麼精靈球那裏會出現取消點讚的按鈕。
下次不會再解釋了。