本系列分紅如下4個部分:css
上篇文章,咱們介紹了圖片的裁剪/旋轉與縮放,接下來本文主要介紹 圖片的合成 ,這是基礎類圖片處理中比較實用且複雜的一部分,能夠算第一篇文章內容的實踐。html
經過這些積累,我封裝了幾個項目中經常使用的功能:git
圖片的合成在實際項目中運用也是十分的普遍,你們能夠試試這個demo
(僅支持移動端): 🐶🐶🐶github
圖片的合成原理其實相似於photoshop
的理念,經過 圖層的疊加 ,最後合成並導出,相比於裁剪和縮放,其實基本原理是一致的,可是它涉及了更多的計算和比較複雜的流程,咱們先一塊兒來梳理下合成的整個邏輯。算法
相信你們對 photoshop
都是較爲了解的,咱們能夠借鑑它的思惟方式:canvas
psd
文件, 設置寬高;以須要合成下圖爲🌰:api
一、首先咱們須要建立一個與原圖同樣大小的畫布;app
二、加載背景圖並 添加背景圖層 ,也就是這個美女啦~異步
三、加載貓耳朵圖並添加美女頭上的 貓耳朵圖層 ( 2/3順序不可逆,不然耳朵會被美女蓋在下面哦。所以圖片的加載控制十分重要 );函數
四、將整個畫布 導出圖片 ;
合成部分,主要以封裝的插件爲栗子哈。這樣能儘量的完整,避免遺漏點。在開始以前,爲了確保圖片異步繪製的順序,咱們須要先來構建一套隊列系統。
圖片的加載時間是 異步且未知 的,而圖片的合成須要嚴格保證繪製 順序 ,越後繪製的圖片會置於越頂層,所以咱們須要一套嚴格機制來控制圖片的加載與繪製,不然咱們將沒法避免的寫出 回調地獄 ,這裏我使用到了簡單的隊列系統;
隊列系統的原理其實也很簡單,主要是爲了咱們能確保圖層從底到頂一層一層的繪製。我設計的使用方式以下, 隊列方式主要來確保add
函數的按順序繪製:
// 建立畫布;
let mc = new MCanvas();
// 添加圖層;
mc.add(image-1).add(image-2);
// 繪製並導出圖片;
mc.draw();
複製代碼
這樣咱們就明白了,這個隊列系統須要下面幾個點:
queue
隊列: 用於存放圖層繪製函數;
next
函數: 用於表示當前圖層已繪製完畢,執行下一圖層的繪製;
add
函數: 做爲統一添加圖層的方法,將繪製邏輯存入函數棧quene
,幷包裹next
函數;
draw
函數: 做爲繪製啓動函數,表示全部圖層素材已經準備完畢,能夠按順序開始繪製;
MCanvas.queue = [];
MCanvas.prototype.add = function(){
this.queue.push(()=>{
// 繪製邏輯,以後詳解;
...
// 執行下個圖層繪製;
this.next();
});
}
MCanvas.prototype.next = function(){
if(this.queue.length > 0){
// 當隊列中還有繪製任務時,則推出並執行;
this.queue.shift()();
}else{
// 當繪製完成後,調用成功事件,並傳出結果圖;
this.fn.success();
}
};
MCanvas.prototype.draw = function(){
// 導出邏輯;
...
// 設置成功事件,用於導出結果圖;
this.fn.success = () => {
// 使用 setTimeout 能略微提高性能表現;
// 且隊列函數中都爲真正的異步,所以此處不會影響邏輯;
setTimeout(()=>{
b64 = this.canvas.toDataURL(`image/jpeg}`, 0.9);
...
},0);
};
// 啓動隊列執行;
this.next();
}
複製代碼
此時,queue
、add
、next
與draw
便組成了一整套隊列系統,可確保圖片的順序加載和繪製,準備好素材和隊列後,咱們即可以開始真正的合成圖片咯~~
MCanvas.prototype._init = function(){
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
};
複製代碼
設置畫布大小並繪製美女背景圖。
經過調整背景圖的dx,dy,dw,dh
參數,能夠繪製出多種模式,相似於css
中的background-size
的contain
/cover
等效果。
這裏主要以上面使用到的場景爲例子,既原圖模式。
// 原圖/效果圖尺寸保持一致;
MCanvas.prototype.background = function(image, bgOps){
// 推入隊列系統;
this.queue.push(() => {
let { iw, ih } = this._getSize(img);
// 圖片與canvas的長寬比;
let iRatio = iw / ih;
// 背景繪製參數;
let dx,dy,dwidth,dheight;
// 設置畫布與背景圖尺寸一致;
this.canvas.width = iw;
this.canvas.height = ih;
dx = dy = 0;
dwidth = this.canvas.width;
dheight = this.canvas.height;
// 繪製背景圖;
this.ctx.drawImage(img,dx,dy,dwidth,dheight);
this._next();
});
return this;
};
複製代碼
相信你們都玩過貼紙,其最大的特色,就是貼紙與背景圖的匹配。也就是用戶能夠修改貼紙的 大小,位置,旋轉角度,經過手勢操做將貓耳朵完美地貼在照片人物的頭上。所以也就是說add
這個方法,須要設置縮放,旋轉與位置等參數。
這裏先模擬出一份使用參數, 實際真實狀況會根據不一樣的背景圖,用戶會調整出不一樣的位置參數。
{
// 圖片路徑;
image:'./images/ear.png',
options:{
// 貼紙寬度;
width:482,
pos:{
// 貼紙左上點座標;
x:150,
y:58,
// 貼紙放大係數;
scale:1,
// 貼紙旋轉系數;
rotate:35,
},
},
}
複製代碼
add
函數接下里咱們便來在add
函數中解析下各個參數的使用姿式:
繪製小畫布來處理旋轉:
// 建立小畫布;
let lcvs = document.createElement('canvas'),
lctx = lcvs.getContext('2d');
// 貼紙圖原始大小;
let { iw, ih } = this._getSize(img);
// 繪製參數;
let ldx, ldy, ldw, ldh;
// 貼紙原始尺寸;
ldw = iw;
ldh = ih;
// 繪製起始點;
ldx = - Math.round(ldw / 2);
ldy = - Math.round(ldh / 2);
// 上篇文章咱們說過旋轉裁剪的問題,這裏就須要用到;
// 須要擴大小畫布的容器,以免旋轉形成的裁剪;最大值爲放大5倍;
let _ratio = iw > ih ? iw / ih : ih / iw;
let lctxScale = _ratio * 1.4 > 5 ? 5 : _ratio * 1.4;
lcvs.width = ldw * lctxScale;
lcvs.height = ldh * lctxScale;
// 調整繪製基點;
lctx.translate(lcvs.width/2,lcvs.height/2);
// 旋轉畫板;
lctx.rotate(ops.pos.rotate);
// 繪製貼紙;
lctx.drawImage(img,ldx,ldy,ldw,ldh);
複製代碼
此時咱們會獲得一個小畫布,中心繪製這貓耳朵貼紙:
接下來咱們即是將貼紙繪製到背景圖上,須要注意的點就是,放大會增長貼紙畫布的空白區域,須要考慮到這部分區域,才能計算出最後真實的dx,dy
值:
// 繪製參數;
let cratio = iw / ih;
let cdx, cdy, cdw, cdh;
// ops.width 爲最終畫到大畫布上時的寬度;
// 因爲小畫布進行了放大,所以最終寬度也須要等倍放大;
// 並乘以配置中還須要縮放的係數;
cdw = ops.width * lctxScale * ops.pos.scale;
cdh = cdw / cratio * ops.pos.scale;
// 放大後增長的空白區域;
spaceX = (lctxScale - 1) * ops.width / 2;
spaceY = spaceX / cratio;
// 獲取素材的最終位置;
// 配置的位置 - 配置放大係數的影響 - 小畫布放大倍數的影響;
cdx = ops.pos.x + cdw * ( 1 - ops.pos.scale )/2 - spaceX;
cdy = ops.pos.y + cdh * ( 1 - ops.pos.scale )/2 - spaceY;
this.ctx.drawImage(lcvs,cdx,cdy,cdw,cdh);
lcvs = lctx = null;
複製代碼
這樣便能獲得合成後的結果圖了,紅色邊框表明小畫布,黑色邊框表明大畫布:
MCanvas.prototype.add = function(img, options){
this.queue.push(()=>{
// 繪製貼紙小畫布;
...
// 繪製貼紙到大畫布上;
...
this._next();
});
return this;
}
複製代碼
這樣咱們便完成了一系列方法,構建了一套完整的合成流程。經過這套流程,咱們便能添加任意的圖片圖層併合成圖片。
本文主要講解了圖片合成上的方法原理和一些須要填的坑,這整套流程也是通過了很長一段時間的打磨,填了許多坑後總結出來的,算比較成熟的方案,已經work在多個線上項目中,指望能對你們有所幫助!🤗。 下篇文章,咱們會繼續介紹下文字的合成和幾何圖片的合成,敬請期待~~🙃🙃