用canvas實現圖片背景替換、摳圖


在這里插入圖片描述

演示地址
代碼倉庫

關鍵技術

canvas apielement-ui (el-color-picker、el-upload)

原理解析

1. 用戶從本地上傳一張圖片

我們拿到圖片數據並在頁面渲染出來,這一步用到了elementui的el-upload

el-upload.avatar-uploader(
  ref="logoUpload",
  accept="image/*",
  action="#",
  :auto-upload="false",
  :on-change="handleStatusChange"
)
  el-button(size="small", type="primary") 點擊上傳

這里實際上不需要真正上傳到服務器,只需要獲取到圖片的在內存中的url

handleStatusChange(file) {
  // console.log(file);
  this.originImg = URL.createObjectURL(file.raw);
},

拿到url之后可以先把圖片用img標簽渲染在頁面上,這樣做的目的是為了獲取圖片的實際尺寸,方面我們等比例縮放在我們的canvas上。

// 圖片加載完成
    loadImg(e) {
      const img = e.target;
      const width = img.offsetWidth;
      const height = img.offsetHeight;
      this.imgWidth = width;
      this.imgHeight = height;
      this.canHeight = (height / width) * this.canWidth;
    }

canvas的width可以根據外層容器來獲取

this.$nextTick(() => {
      const contentWidth = document.querySelector(".origin-box").offsetWidth;
      this.canWidth = Math.min(contentWidth - 12, this.canWidth);
});

2. 繪制canvas

繪制圖片到canvas,這一步比較簡單,

//這里需要縮放一下,因為我們的畫布已經被縮放了
this.originCtx.scale(this.canWidth / width, this.canWidth / width);
this.originCtx.drawImage(img, 0, 0);

3. 選中顏色

點擊canvas,我們可以拿到該點上的顏色值,獲取方式

ctx.getImageData(targetX,targetY,1,1)

拿到的是imagedate對象,{data, width, height},data即為我們要的顏色值。

4. 遍歷原圖片的顏色值,匹配到選中顏色之后,做對應顏色的替換即可

核心api: getImageData putImageData

//獲取data
const data = this.imageData.data || [];
//遍歷並替換
for (let i = 0; i < data.length; i += 4) {
        const similar = this.isSimilar(_fromColor, data.slice(i, i + 4));
        if (similar) {
          data[i] = _toColor[0];
          data[i + 1] = _toColor[1];
          data[i + 2] = _toColor[2];
          data[i + 3] = _toColor[3] * 255;
        }
 }
 //繪制到目標容器上
 this.transCtx.putImageData(this.imageData, 0, 0);

isSimilar方法用來判斷兩個顏色是否相似或相等,這個可以通過參數調整(類似於ps的容差概念),容差值越小,匹配越精准。

顏色值是rgba格式的,即4個數組為一組,數值都在[0,255]之間,由於el-picker返回的rgba,透明度用的是【0,1】表示的,所以要轉換到0-255區間

5. undo&redo

撤銷、前進、后退功能還是很有必要的,重復替換操作,可返回歷史操作步驟。
創建一個隊列(這里用數組代替),每次有新的數據變化添加到隊列里,用unshift表示入列pop從隊列后面刪除。可以設置上限10,隊列過大會占用較大內存,不建議設置過大。
維護一個index,理解成指針,表示當前回退的數據在隊列中的位置。

undo() {
  this.index++;
  this.redrawImg();
},
redo() {
  this.index--;
  this.redrawImg();
},
redrawImg() {
  const preImageData = JSON.parse(this.imgStock[this.index]).data;
  this.imageData = this.transCtx.createImageData(
    this.canWidth,
    this.canHeight
  );
  for (let i = 0; i < this.imageData.data.length; i++) {
    this.imageData.data[i] = preImageData[i];
  }
  this.transCtx.putImageData(this.imageData, 0, 0);
},

需要注意的細節:當回退到某一條歷史記錄,比如index=5,這時候再執行手動操作替換,此時就“穿越”了,需要把index = 5之前的歷史都移除。(可能描述有點繞~)

至此,就完成了核心功能了~

TODO LIST

  1. 增加邊緣識別,去除毛邊
  2. 增加容差選項
  3. 嘗試視頻摳圖和替換
  4. 。。。

演示地址
代碼倉庫

歡迎提意見&star!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM