canvas 影象旋轉與翻轉姿勢解鎖

發表於2017-05-26

多圖預警,數學不好可直接跳至文末小結。

需求背景

從一個遊戲需求說起:
需求背景

  1. 技術選型:canvas
    上圖所展示的遊戲場景,“可樂瓶”裡有多個“氣泡”,需要設定不同的動畫效果,且涉及 deviceOrientation 的互動,需要有大量計算改變元素狀態。從效能方面考慮,canvas 是不二的選擇。
  2. 技術點:canvas 繪製影象
    通過對遊戲場景的進一步分析,可見場景中的“氣泡”元素形狀都是相同的,且不規則,通過 canvas 直接繪製形狀實現成本較高,因此需要在 canvas 上繪製影象。
  3. 技術點:canvas 影象旋轉與翻轉
    雖然“氣泡”元素是相同的,可以使用相同的影象,但影象需要多個角度/多個方向展示,因此需要對影象進行相應的旋轉與翻轉(映象),這也是本文所要介紹的重點。

後文程式碼以下圖左側綠框的“氣泡”為示例,右側展示了場景中用到的兩個影象:
需求背景

認識 canvas 座標系

canvas 上影象的旋轉和翻轉,常見的做法是將 canvas 座標系統進行變換。因此,我們需要先認識 canvas 座標系統:
canvas 座標系
由上圖可得,canvas 2D 環境中座標系統和 Web 的座標系統是一致的,有以下幾個特點:

  1. 座標原點 (0,0) 在左上角
  2. X座標向右方增長
  3. Y座標向下方延伸

回到上述需求中,我們獲取 canvas 物件並設定相應的寬高:

此時,canvas 的座標系統如下圖所示:
coke 座標系

在 canvas 上繪製影象

在 canvas 上繪製影象,可以使用 drawImage() 方法,語法如下(詳細用法參見 MDN):

需要注意的是,影象必須載入完畢,才能繪製到 canvas 上,否則會出現空白:

此時,便可以 canvas 上看到一個未旋轉/翻轉的“氣泡”影象,如下圖所示:
繪製影象

canvas 座標變換

接下來,我們再來了解 canvas 座標的變換。上述需求僅涉及 2D 繪製上下文,因此僅介紹 2D 繪製上下文支援的各種變換:

  1. 平移 translate:

    translate() 方法接受兩個引數。x 是左右偏移量,y 是上下偏移量。

  2. 旋轉 rotate:

    rotate() 方法只接受一個引數。旋轉的角度 angle,它是順時針方向的,以弧度為單位的值。

  3. 縮放 scale:

    scale() 方法接受兩個引數。x 和 y 分別是橫軸和縱軸的縮放因子。其縮放因子預設是 1,如果比 1 小是縮小,如果比 1 大則放大。

  4. 變形 transform:

    transform() 方法是對當前座標系進行矩陣變換。

    setTransform() 方法重置變形矩陣。先將當前的矩陣重置為單位矩陣(即預設的座標系),再用相同的引數呼叫 transform() 方法設定矩陣。
    以上兩個方法均接受六個引數,具體如下:

引數 含義
a 水平縮放繪圖
b 水平傾斜繪圖
c 垂直傾斜繪圖
d 垂直縮放繪圖
e 水平移動繪圖
f 垂直移動繪圖

影象旋轉的實現

影象旋轉
上圖所示“氣泡”,寬為 160,高為 192,x 軸方向距離原點 512,y 軸方向距離原點 220,逆時針旋轉 35 度。
要繪製該“氣泡”,需要先將座標系平移(translate),再旋轉(rotate)。具體實現步驟如下:
影象旋轉

save() 方法與 restore() 方法:

  1. save() 方法用來儲存 Canvas 狀態的,沒有引數。每一次呼叫 save() 方法,當前的狀態就會被推入棧中儲存起來。當前狀態包括:
    • 當前應用的變形(移動/旋轉/縮放)
    • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
    • 當前的裁切路徑(clipping path)
  2. restore() 方法用來恢復 Canvas 狀態,沒有引數。每一次呼叫 restore() 方法,上一個儲存的狀態就從棧中彈出,所有設定都恢復。
  3. 狀態儲存在棧中,可以巢狀使用 save() 與 restore()。

影象翻轉的實現

影象翻轉
上圖所示“氣泡”,寬為 160,高為 192,x 軸方向距離原點 172,y 軸方向距離原點 365,順時針旋轉 35 度。
要繪製該“氣泡”,需要先將座標系統平移(translate),翻轉(scale),平移(translate),再旋轉(rotate)。具體實現步驟如下:
影象翻轉1
至此,實現了“氣泡”的映象翻轉,但翻轉後的“氣泡”還需要旋轉特定的角度,在方法一的基礎上繼續對座標系統進行變換:
影象翻轉2
以上操作中進行了兩次平移(translate)操作,可以進行合併簡化:
影象翻轉3

座標系統的矩陣變換

前文介紹了 2D 繪製上下文變形(transform)變換,實際是直接修改變換的矩陣,它可以實現前面介紹的平移(translate)/旋轉(rotate)/縮放( scale)變換,還可以實現切變/映象反射變換等。矩陣計算遵循數學矩陣公式規則:
矩陣變換
由上公式可得:

矩陣變換可實現以下變換效果:

  1. 平移 translate:

    平移

  2. 旋轉 rotate:

    旋轉

  3. 縮放 scale:

    縮放和拉伸

  4. 切變

    切變

  5. 映象反射

    映象反射

結合上述公式,可推匯出影象旋轉和翻轉的矩陣變換實現:

  1. 影象旋轉:
    影象旋轉
  2. 影象翻轉:
    影象翻轉
  3. 影象映象反射(翻轉+旋轉):
    影象旋轉&翻轉

畫素操作實現影象翻轉

除了座標系統變換,canvas 的畫素操作同樣可以實現影象的翻轉。首先需要了解下 getImageData() 方法(詳細用法參見MDN)和 putImageData()(詳細用法參見MDN)方法:

  1. getImageData()
    CanvasRenderingContext2D.getImageData() 返回一個 ImageData 物件,用來描述 canvas 區域隱含的畫素資料,這個區域通過矩形表示,起始點為 (sx, sy)、寬為 sw、高為 sh。
  2. putImageData()
    CanvasRenderingContext2D.putImageData() 是 Canvas 2D API 將資料從已有的 ImageData 物件繪製到點陣圖的方法。 如果提供了髒矩形,只能繪製矩形的畫素。

水平翻轉實現:

小結

至此,小編的數學姿勢又恢復到了高考水平。

  1. 影象旋轉:
    • 基礎變換法:
    • 矩陣變換法:
  2. 影象翻轉:
    • 基礎變換法:

    • 矩陣變換法:

    • 畫素操作法:
  3. 影象映象對稱(翻轉+旋轉):
    • 基礎變換法:
    • 矩陣變換法:

參考文章

說明:本文討論的 canvas 環境均為 2D 環境。若有更好的實現方式,歡迎留言告知。

相關文章