純前端實現圖片背景透明化

前言

不管是作一些2d的小遊戲,或者製做小圖標,或者摳圖都須要用到這個功能,對圖片的背景進行透明化,是咱們常常須要用到的一個功能。css

一般狀況下咱們都會去下載PS或者美圖秀秀這樣的軟件去製做。html

可是我真的不想僅僅爲了作個透明圖像就去下載這些軟件,這些軟件不只體積大,要下載個半天,放在電腦上也佔空間。前端

最重要的是每次我作這個事情,都須要去臨時百度一下製做透明圖片的方法。git

這些軟件當然強大,可是功能的衆多或者須要一些基礎知識,每每形成了一些門檻。github

簡單點說,雖然瑞士軍刀很6,可是我如今只須要一把起子,我不想知道什麼蒙版圖層,不想在一堆什麼美顏什麼各類濾鏡之中找半天,我就想上傳個圖片點兩下就行了。chrome

那麼能不能在線對圖片進行背景透明化呢?canvas

固然是有的,下面是網址數組

http://www.aigei.com/bgremover/瀏覽器

你覺得我是來推薦網站的?固然不是。app

我之因此提到這個網站,是由於我之前就是用這個作一些處理的,可是真的不是很給力啊。

我並不知道它的原理,也沒有看過它的代碼,可是它的缺陷很明顯:

  1. 不能對指定顏色進行透明化
  2. 當須要對色差很大的多種顏色進行透明化時,無能爲力
  3. 對一些圖片的透明化處理不夠完美,會出現鋸齒,可是又沒法進行進一步處理
  4. 對複雜圖片徹底無能爲力

有問題就解決問題唄,因而就有了今天的小玩意。

做品

與以前的做品同樣,直接將功能寫在這篇博客裏了,因此能夠直接在博客園中使用。

使用方法:

  1. 上傳圖片
  2. 點擊圖片,將以鼠標點擊處的顏色爲標準,對色差20以內的顏色進行透明化處理。(若是想調整色差標準,能夠在控制檯下設置transparentConfig.colorDiff)
  3. 對出現透明化處理有有誤的地方,能夠開啓恢復模式,再次移動鼠標到圖片上,此時鼠標會變成紅色小方框,小方框區域內會顯示原始圖像。點擊後會將紅色小方框區域內的圖像恢復爲原始圖像。(若是想調整小方框尺寸,能夠在控制檯下經過transparentConfig.setRecoverSize(30)的方式進行修改)
  4. 下載圖片,搞定。

固然按照本懶人的慣例,仍是隻在chrome瀏覽器下實現,因此若是您用其它瀏覽器的話可能沒法正常操做。

不過本應用的核心功能與以往同樣都是能夠在現代瀏覽器中實現的,只是須要您調一下兼容性。

若是您有閒情逸致,想研究一下的話,這是本項目的 GitHub地址,爲了能方便複製進博客園,因此代碼是直接寫在html中的。

少說廢話,如下爲應用:

技術點

本應用依然只使用純前端實現,涉及到的技術點以下:

  1. 獲取圖片文件
  2. 將文件轉換爲圖片,並放入canvas中
  3. 點擊canvas獲取點擊處的顏色信息
  4. 根據指定顏色,對圖像中在色差範圍內的顏色進行透明化處理
  5. 自定義鼠標,在鼠標上顯示指定區域內原始圖像
  6. 對圖像上指定區域,進行圖像還原操做
  7. 下載圖片

其中技術點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樣式的話,會發現兩個小坑點:

  1. 在樣式裏面對恢復圖像和canvas上的鼠標樣式都設置了隱藏,緣由是避免當鼠標拖動過快時鼠標會出如今恢復圖像上,出現鼠標閃爍狀況
  2. 恢復圖像自己就有一個top:1px和left:1px的初始值,這是由於咱們的canvas有一個1px的border,而絕對定位的位置是相對於canvas的父級的。

而後再來看看點擊恢復圖像的代碼:

/**
 * 恢復模式下,點擊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的方式放入數組便可。

總結

這個小應用如今能夠知足個人需求了,可是它依然存在不少不足與改進空間,好比:

  1. 兼容性
  2. 色差的調整,如今是放在控制檯之中。即便是拿出來也可能只是一個輸入框。實際上這個地方是能夠作的更加完美,在應對一個複雜圖片時,當咱們點擊一個像素後,能夠保存這個像素,並出現一個調整色差的滑動條,拖動這個滑動條圖像會針對色差在原基礎上實時進行透明化和恢復圖像的處理。
  3. 恢復圖像尺寸的調整,能夠經過鼠標滑輪滾動的方式進行處理
  4. 返回操做。能夠增長回退功能。
  5. 依然存在的一些小bug以及優化空間,以及仍然不夠智能不夠完美的圖像處理,由於我真的只想點一下而後就自動處理好。

本期分享結束,依然是一個小應用,依然是一堆你可能知道也可能不知道的小知識點。

最後例行說明,右下角的精靈球是點贊,這已是我連續幾篇文章說明了,而且那麼大幾個字已經寫明瞭。
由於有幾個園友引用了這個精靈球,因此大家應該也造成了精靈球便是點讚的心理預期和用戶習慣了吧。
若是你不當心點錯了,請刷新頁面,那麼精靈球那裏會出現取消點讚的按鈕。

下次不會再解釋了。

相關文章
相關標籤/搜索