
關鍵技術
canvas api、 element-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
- 增加邊緣識別,去除毛邊
- 增加容差選項
- 嘗試視頻摳圖和替換
- 。。。
歡迎提意見&star!
