JavaScript中的圖片處理與合成(二)

引言

本系列分紅如下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();
}
複製代碼

此時,queueaddnextdraw便組成了一整套隊列系統,可確保圖片的順序加載和繪製,準備好素材和隊列後,咱們即可以開始真正的合成圖片咯~~

建立畫布

MCanvas.prototype._init = function(){
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
};
複製代碼

繪製背景圖

設置畫布大小並繪製美女背景圖。

經過調整背景圖的dx,dy,dw,dh參數,能夠繪製出多種模式,相似於css中的background-sizecontain/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在多個線上項目中,指望能對你們有所幫助!🤗。 下篇文章,咱們會繼續介紹下文字的合成和幾何圖片的合成,敬請期待~~🙃🙃

相關文章
相關標籤/搜索