canvas進階——實現靜態圖像的變形併合成動態效果

寫在最前

在之前的這篇bezierMaker.js——N階貝塞爾曲線生成器的文章中我們提到了對於高階貝塞爾公式的繪製與生成。不過更多的童鞋看到後可能會不知道其使用場景是什麼。故作者本次分享一下基於bezierMaker.js實現的將靜態圖片按照自定義曲線軌跡扭曲圖片併合稱爲動態效果。

歡迎關注我的博客,不定期更新中——

效果預覽

之前的描述可能不是很清楚我們直接看下效果圖:

首先加載一張圖:
image

然後通過bezierMaker.js提供的試驗場功能來繪製一段曲線,進行圖片扭曲:
image

最後擬合爲動態圖:

2018-01-19 12_40_32

再來一個豎直方向的扭動:

anmate

demo地址

源碼地址

圖像變形實現思路

  1. 繪製一條由bezierMaker.js生成的貝塞爾曲線,以此來掌握曲線各點的準確座標值
  2. 確定扭曲方向爲橫向或縱向
  3. 根據該方向的基準線(圖中的灰色線)來計算本次繪製的曲線與基準對比的偏移量,按照該方向每隔1px記錄一個值
  4. 將圖像數據按照選定方向進行切分,將一個一維數組imgData.data變爲一段一段有方向的二維數組
  5. 將每段數組按照之前記錄的偏移值進行移位後再拼接爲一維數組
  6. 將新拼接好的數組重新賦值到imgData中

其中較爲核心的實現即橫向與縱向對一維圖像數據的切分。其中橫向相對簡單,細節如下:

image

如上圖所示,在原始圖像的數據中的數據形式爲一維數組的形式,而對其進行拆分則是一個從中不斷截取與提取數據的過程。橫向拆分較爲簡單,只需要確定每一行開始的位置即可,截取的數量就是一行的元素數。同時縱向拆分則需要多加一步,我們需要計算每一層數組中的每一個數,像上圖一般拆分第每列數組時首先要遍歷圖的寬得到每一列的索引,再遍歷圖的高,通過高✖️寬✖️4 + 寬 ✖️ 4算出當前值在原數據中的位置。當拆分成功數組後,將數組依次移位,移位數爲之前曲線與基準線的偏移量決定。

//pg.js
//按行拆分
bezierArr.forEach(function (obj, index) {
    if (_.imgStartY < obj.y && _.imgStartY + _.imgHeight > obj.y && type === 'row') {

        var diffX = parseInt(obj.x - _.baseX, 10) //計算偏移量
        var dissY = parseInt(obj.y - _.imgStartY, 10)
        var rowNum = dissY
        imgDataSlice = _.imgData.data.slice((rowNum) * _.imgWidth * 4, rowNum * _.imgWidth * 4 + _.imgWidth * 4) //按層切片
        ...
    }
})

//按列拆分
for (var i = 0; i < _.imgWidth; i++) {
    imgDataSlice = []
    for (var j = 0; j < _.imgHeight; j++) {
        var index = j * _.imgWidth * 4 + i * 4
        var sliceArr = _.imgData.data.slice(index, index + 4)
        imgDataSlice = imgDataSlice.concat(Array.from(sliceArr))
    }
    if(_.imgChangeObj[i]) {
        for (var k = 0; k < Math.abs(_.imgChangeObj[i].diffY * 4); k++) {
            imgDataSlice = _.arraymove(_.imgChangeObj[i].diffY, imgDataSlice)
        }
        for (var p = 0; p < imgDataSlice.length / 4; p++) {
            arr[p * _.imgWidth * 4 + i * 4] = imgDataSlice[p * 4]
            arr[p * _.imgWidth * 4 + i * 4 + 1] = imgDataSlice[p * 4 + 1]
            arr[p * _.imgWidth * 4 + i * 4 + 2] = imgDataSlice[p * 4 + 2]
            arr[p * _.imgWidth * 4 + i * 4 + 3] = imgDataSlice[p * 4 + 3]
        }
    }
}

核心的數組拆分移位再合併的邏輯相對分散,知道思路即可有興趣的同學歡迎戳源碼~

合併成動態效果

核心思想爲從我們的原始形態到最終態的兩張靜態圖我們已經得到了。現在我們需要做的是添加幾張過渡態。在這裏面有兩種方式:
- 將計算的各點偏移量進行按比例偏移,比如一共四張圖合成則需要三次改變狀態,那麼每次將數組移位的量設定爲總量的1/3,每次移位後拼出一維數組更新到一張離屏canvas中將其保存爲base64,作爲後續合併時的替換url
- 計算貝塞爾曲線控制點的偏移量且進行按比例偏移。如一開始的垂直或水平的初始圖控制點形成了一條直線。同時最終形態的控制點位置我們已經知道了,藉此我們可以將控制點由直線到兩邊的過程按比例切分,依次計算各中間態控制點所形成曲線導致的偏移圖像數據,導出base64,作爲後續合併替換的url

作者一開始使用了第一種方式,但是有一個明顯的缺陷及通過按比例直接偏移會導致拆分出來的每層的偏移每次都是相同的,那麼就會出現鋸齒現象。因爲圖像扭曲可能上一層在這一次移位的時候偏移5合適可是你仍然偏移了總量的1/3導致與下一層的圖像不匹配從而出現鋸齒。故重新選擇了第二種方式,由重新計算各中間態圖像的控制點再來移位圖像數據,圖像的呈現情況就改善了很多。

小結

由於操作圖像數據量比較大,故在嘗試demo的時候如果遇到ui卡頓那是正在計算中,並沒有引入webworker之類的所以請稍等一會就會出現結果=。=
PS:demo使用步驟
- 加載圖像
- 畫曲線,豎向切分請點擊checkbox,同時曲線寬要大於圖像的寬。橫向切分數據則曲線高要大於圖像,保證起終點在基準線外。描點後點擊繪製
- 計算結束後點擊合成

其他canvas相關文章

最後

demo地址

源碼地址

慣例po作者的博客,不定時更新中——

有問題歡迎在issues下交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章