技術博客--微信小程序canvas實現圖片編輯
我們的這個小程序不僅僅是想給用戶提供一個保存和查找的平台,還希望能給用戶一個展示自己創意的舞台,因此我們實現了圖片的編輯部分。我們對對圖片的編輯集成了很多個不同的模塊。根據用戶的需求,我們主要實現了6個不同的功能,包括添加文字,圖片塗鴉,添加濾鏡,拼接圖片,宣傳圖片,裁切圖片。
雖然這些都是比較基礎的圖片的編輯能力,但是通過我們的優化,我們能夠比較方便直觀的修改我們的表情。通過結合多個不同的編輯操作,我們可以比較方便的實現出很多酷炫的效果。我們實現了一個比較有邏輯的平台,用戶對於表情的編輯都會累加到上面,因此,用戶只需要不斷的編輯,效果就會累加,直到用戶滿意為止。
下面,我們將重點的分享一下幾個比較重點的處理方法。
1、前置知識
需要掌握有小程序中canvas的相關的方法使用,例如:
const ctx = wx.createCanvasContext("canvasId");//創建上下文
//scaleX水平縮放,scaleY垂直縮放,skewX水平傾斜,skewY垂直傾斜,translateX水平移動,translateY垂直移動,這個可以做縮放和移動
ctx.transform(scaleX, skewX, skewY, scaleY, translateX, translateY);
ctx.setFillStyle("red");//設置填充顏色為red
ctx.setStrokeStyle("red");//設置線條顏色
ctx.setLineWidth(10);//設置線條為10像素
ctx.fillRect(0,0,100,100);//在(0,0)位置畫一個100*100像素的填充矩形
ctx.strokeRect(100,100,100,100);//在(100,100)位置畫一個100*100像素的不填充矩形
ctx.moveTo(0, 100);//畫線,起點
ctx.lineTo(100, 100);//畫線,到哪
ctx.scale(2,2);//放大兩倍,控制縮放
ctx.setFontSize(10);//設置字體大小
//在(100,100)的位置寫"hello world",最后一個100是文本最大寬度,可以省略
ctx.fillText("hello world",100,100,100);
ctx.stroke();//對當前路徑進行描邊,說白了就是畫線
這只是一些使用的簡單例子,更加詳細的使用說明可以看官方文檔或者自行百度學習。
在設置好之后就可以使用ctx.draw()方法進行繪制,draw()方法可以直接使用,也可以加參數使用,參數情況下有兩個參數,第一個是reserve,boolean類型,默認為false,表示是否清空當前畫布進行重畫,還有一個就是callback回調函數,注意在需要導出圖片時最好是在draw內部使用wx.canvastotempfilepath函數作為回調函數來進行指定畫布區域的內容的導出,不然很有可能由於畫布上還未進行渲染而導出失敗。
當然,對圖片進行編輯最首要的是先獲得圖片,我們可以直接通過wx.chooseImage方法來從相冊獲得想要編輯的圖片,參數信息如下:
2、圖片旋轉
圖片旋轉的實現較為簡單,主要的方式就是使用canvas的旋轉的接口rotate。rotate方法具體是以原點為中心旋轉canvas的坐標軸,默認情況之下原點是canvas的左上角,因此要是不管不顧的直接使用rotate旋轉某個角度就會出現部分圖片旋轉出了畫布的顯示范圍,因此在使用時要根據我們需要旋轉的角度調整好原點的位置和圖片畫布的大小,才能夠完整的顯示出整個圖片。在我們的小程序中我們固定一次旋轉是旋轉90度,因此需要使用translate()方法來調整原點的位置到畫布的右上角
cxt.translate(that.data.cWidth,0); //cWidth是canvas的寬
然后為了保證旋轉后的圖片完整的出現(長方形圖片旋轉后無法完整出現在畫布中),對圖片進行了一定的壓縮,代碼如下:
cxt.rotate(90 * Math.PI / 180);
cxt.drawImage(that.data.curImage, 0, 0, cHeight, cWidth); //改變繪制的長寬
cxt.draw(false,wx.canvasToTempFilePath({
canvasId: 'edit',
//destWidth: 3*cWidth,
//destHeight: 3*cHeight,
success: function(res) {
console.log("success!更新curImage")
console.log(res.tempFilePath);
that.setData({
curImage: res.tempFilePath
})
}
}));
3、濾鏡實現
以下幾個濾鏡的實現主要用到的方法是wx.canvasGetImageData(OBJECT, this)和wx.canvasPutImageData(OBJECT, this),使用這兩個方法來獲取圖片的數據信息並對圖片的像素點進行修改從而實現圖片的濾鏡效果(利用修改圖片每個像素的顏色RGBA來實現濾鏡)。
-
灰度濾鏡
效果圖如下:
wx.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: 300,
height: 300,
success(result) {
let data = result.data;
for (let i = 0; i < result.width * result.height;i++){
//********************只有這里有區別****************************
let R = data[i * 4 + 0];
let G = data[i * 4 + 1];
let B = data[i * 4 + 2];
let grey = R * 0.3 + G * 0.59 + B * 0.11;
data[i * 4 + 0] = grey;
data[i * 4 + 1] = grey;
data[i * 4 + 2] = grey;
//********************只有這里有區別****************************
}
wx.canvasPutImageData({
canvasId: 'canvasNew',
x: 0,
y: 0,
width: 300,
data: data,
success(res) {
console.log(res)
}
})
}
})
})
-
黑白濾鏡
效果圖如下:
wx.canvasGetImageData({ canvasId: 'canvas', x: 0, y: 0, width: 300, height: 300, success(result) { let data = result.data; for (let i = 0; i < result.width * result.height;i++){ //********************只有這里有區別**************************** let R = data[i * 4 + 0]; let G = data[i * 4 + 1]; let B = data[i * 4 + 2]; let grey = R * 0.3 + G * 0.59 + B * 0.11; if (grey > 125){ grey=255; } else { grey = 0; } data[i * 4 + 0] = grey; data[i * 4 + 1] = grey; data[i * 4 + 2] = grey; //********************只有這里有區別**************************** } wx.canvasPutImageData({ canvasId: 'canvasNew', x: 0, y: 0, width: 300, data: data, success(res) { console.log(res) } }) } }) })
-
反相濾鏡
效果圖如下:
wx.canvasGetImageData({ canvasId: 'canvas', x: 0, y: 0, width: 300, height: 300, success(result) { let data = result.data; for (let i = 0; i < result.width * result.height;i++){ //********************只有這里有區別**************************** let R = data[i * 4 + 0]; let G = data[i * 4 + 1]; let B = data[i * 4 + 2]; data[i * 4 + 0] = 255-R; data[i * 4 + 1] = 255-G; data[i * 4 + 2] = 255-B; //********************只有這里有區別**************************** } wx.canvasPutImageData({ canvasId: 'canvasNew', x: 0, y: 0, width: 300, data: data, success(res) { console.log(res) } }) } }) })
-
馬賽克濾鏡
馬賽克濾鏡的實現方法稍有不同,主要思路是將整個圖片分為多個小的像素塊(例如10*10的像素塊),取小像素塊中的某個像素的顏色作為整個像素塊的顏色填充,從而實現模糊馬賽克的效果。
效果圖如下:
wx.canvasGetImageData({ canvasId: 'canvas', x: 0, y: 0, width: 300, height: 300, success(result) { let data = result.data; const size = 10; const totalnum = size*size; for(let i=0;i<result.height;i+=size){ for(let j=0;j<result.width;j+=size){ var totalR=0,totalG=0,totalB=0; for(let dx=0;dx<size;dx++){ for(let dy=0;dy<size;dy++){ var x = i+dx; var y = j+dy; var p = x * result.width + y; totalR += data[p * 4 + 0]; totalG += data[p * 4 + 1]; totalB += data[p * 4 + 2]; } } var p = i * result.width + j; var resR = totalR / totalnum; var resG = totalG / totalnum; var resB = totalB / totalnum; for (let dx = 0; dx < size; dx++){ for (let dy = 0; dy < size; dy++) { var x = i + dx; var y = j + dy; var p = x * result.width + y; data[p * 4 + 0] = resR; data[p * 4 + 1] = resG; data[p * 4 + 2] = resB; } } } } wx.canvasPutImageData({ canvasId: 'canvasNew', x: 0, y: 0, width: 300, data: data, success(res) { console.log(res) } }) } }) })
4、圖片塗鴉
畫一條線的方法需要在canvas上綁定兩個函數:touchstart和touchmove
<canvas canvas-id="myCanvas" disable-scroll="true" bindtouchstart="touchStart"
bindtouchmove="touchMove" wx:if="{{hasChoosedImg}}"
style="height: {{cHeight}}px; width: {{cWidth}}px;" />
touchstart記錄下路線開始的點的坐標,並設置好線的顏色和寬度,然后在touchmove函數中,隨着移動事件,記錄下來移動過程中的每個點的坐標,這樣子就能夠得到一條線的路徑,並將其存儲下來,然后根據每次移動的兩個點畫出它們的二次貝塞爾曲線(使用ctx.quadraticCurveTo()方法),最后得到一條平滑的塗鴉出來的線條。
想要撤回時就可以簡單的通過將存儲路徑數據結構清空最后一個再重新畫圖,就可以實現撤銷的操作啦。
touchStart: function (e) {
// 開始畫圖,隱藏所有的操作欄
this.setData({
color: false,
width: false,
canvasHeightLen: 0,
prevPosition: [e.touches[0].x, e.touches[0].y],
movePosition: [e.touches[0].x, e.touches[0].y],
});
const { r, g, b } = this.data;
let color = `rgb(${r},${g},${b})`;
let width = this.data.w;
startTouch(e, color, width, this.data.masaic);
},
touchMove: function (e) {
const { r, g, b, prevPosition, movePosition, eraser, w, } = this.data;
// 觸摸,繪制中。。
const ctx = wx.createCanvasContext('myCanvas');
// 畫筆的顏色
let color = `rgb(${r},${g},${b})`;
let width = w;
if (eraser) {
ctx.clearRect(e.touches[0].x, e.touches[0].y, 30, 30);
ctx.draw();
return;
}
const [pX, pY, cX, cY] = [...prevPosition, e.touches[0].x, e.touches[0].y];
const drawPosition = [pX, pY, (cX + pX) / 2, (cY + pY) / 2];
if (this.data.masaic == true) {
ctx.setFillStyle('red')
ctx.fillRect(e.touches[0].x, e.touches[0].y, 10, 10)
ctx.fillRect(e.touches[0].x + 10, e.touches[0].y + 10, 10, 10)
ctx.setFillStyle('pink')
ctx.fillRect(e.touches[0].x + 10, e.touches[0].y, 10, 10)
ctx.fillRect(e.touches[0].x, e.touches[0].y + 10, 10, 10)
ctx.draw(true)
}else {
ctx.setLineWidth(width);
ctx.setStrokeStyle(color);
ctx.setLineCap('round');
ctx.setLineJoin('round');
ctx.moveTo(...movePosition);
ctx.quadraticCurveTo(...drawPosition);
ctx.stroke();
ctx.draw(true);
}
recordPointsFun(movePosition, drawPosition)
}
粗細的調整很簡單,直接調整繪制時候的線的粗細就可以實現,顏色的調整則也還是通過調整像素點的rgb值來進行顏色的變動,顏色的rgb變動代碼如下:
<view class="choose-box" wx:if="{{color}}">
<view class="color-box" style="background: {{'rgb(' + r + ', ' + g + ', ' + b + ')'}}; height: {{w}}px; border-radius: {{w/2}}px"></view>
<slider min="0" max="255" step="1" show-value="true" activeColor="red" value="{{r}}" data-color="r" bindchange="changeColor"/>
<slider min="0" max="255" step="1" show-value="true" activeColor="green" value="{{g}}" data-color="g" bindchange="changeColor"/>
<slider min="0" max="255" step="1" show-value="true" activeColor="blue" value="{{b}}" data-color="b" bindchange="changeColor"/>
</view>
再根據得到的rgb值,用如下代碼就可以合成顏色:
let color = `rgb(${r},${g},${b})`;
效果圖如下:

5、圖片保存
在對所選擇的圖片完成了處理之后,我們還需要對處理后的圖片進行保存,就可以直接使用wx.canvasToTempFilePath方法來將指定的畫布區域的內容進行保存,所得到的新的圖片的臨時路徑可以在其seccess的回調方法中獲得,例如:
wx.canvasToTempFilePath({
canvasId: 'edit',
success: function(res) {
that.setData({
curImage: res.tempFilePath //res.tempFilePath就是該圖片的臨時路徑
})
}
})
注意該方法最好作為draw的回調函數使用才能保證獲得圖片一定成功。
得到了圖片的路徑之后,就可以調用wx.saveImageToPhotosAlbum方法來將圖片保存到本地啦。
wx.saveImageToPhotosAlbum({
filePath: path,
success (res) {
wx.showToast({
title: '保存成功',
})
}
})
總結一下,在這個板塊的開發中,我們學到了很多圖片處理的算法,這對於以前對此一無所知的我們來說是一個非常大的提升。同時,在開發完成這個板塊的功能之后,我們感受非常有成就感。因為這個部分的一個一個的操作都是我們通過自己的代碼實現。