神筆馬良——基於 OpenGL 的塗鴉框架

取這個名字有投機取巧的嫌疑,但願能對得起先賢 >_<git

這是什麼?

MaLiang 是 iOS 平臺一個基於 OpenGL ES3 的塗鴉繪圖庫,使用純 Swift 實現,支持自定義紋理、壓力感應、自動筆觸等特性,而且提供了必定的自定義擴展的空間。github

這篇文章能夠看做是對 Github 上 README 說明的詳細擴展和補充說明。canvas

使用

個人理念是儘可能製造簡單、優雅的東西,雖然有時候要作到這一點其實很難,可是儘可能往這方面靠吧。MaLiang 的集成和使用都很簡單,我把大量對使用者來講沒有什麼用也沒有必要了解的內部邏輯都隱藏了。固然了,若是你的好奇心很重,能夠本身去看源代碼。這篇文章也會介紹一些內部實現的思路。swift

集成

MaLiang 已經推送到了 Cocopods 的官方 repo,因此,你只須要在 Podfile 增長一條 Pod 指令而後 install 就能夠在項目中使用了:ruby

pod 'MaLiang'
複製代碼

而後在須要使用的地方引入 Mudule。固然,首先須要編譯一下,否在會報找不到 Moudle 的錯誤框架

import MaLiang
複製代碼

幾個主要的類

1. Canvas

畫布是 MaLiang 最基礎的組件,全部的塗鴉都發生在 Canvas 上。Canvas 本質上是一個 UIView,因此你可使用任何你原來建立 UIView 的方法來建立一個畫布,並將它添加到你的界面上。機器學習

  • 若是你偏好代碼流,那麼直接調用 UIView 的通用構造函數 init(frame:) 就能夠了。函數

  • 若是你以爲 IB 流纔是正道,只要在 xib 或者 storyboard 中拖一個 UIView 到界面上,而後將類名改爲 Canvas 後回車就能夠了,Xcode 應該會自動將 Module 設置成 MaLiang佈局

Canvas 設置正確的佈局約束,而後你就能夠開始塗鴉了,好比寫一個像下面這樣的東東 :)學習

嗯,想畫成這樣,確實還缺乏一些東西 :)

Canvas 繼承自 MLView(ML是 MaLiang 的縮寫,不是那個機器學習的東東),MLView 作了幾乎全部與 OpenGL 打交道的事情,雖然它被定義成一個 open 的類,但實際使用中基本是用不到的。不過了解一些原理也無傷大雅麼~

OpenGL 塗鴉的核心是紋理(Texture),本質上就是沿着手指軌跡,不斷地將紋理疊加到畫布上的過程。因此能畫出什麼樣的筆跡,徹底取決於使用的紋理,以及它的大小、顏色、尺寸等參數。

MLView 初始化以後會使用自帶的圖片建立一個默認的紋理,這個紋理就是一個簡單的不透明的圓點,因此只能畫最簡單的線條。若是想要畫出上圖那樣的效果,就須要使用相對複雜一點的紋理了。MaLiang 的示例項目裏面提供了好幾個設定好的紋理,用他們能夠模擬出鉛筆、水筆以及毛筆的特效,上面的文字就是使用毛筆特效寫出來的。

快照

Canvas 提供了一個簡單的快照功能:

open func snapshot() -> UIImage?
複製代碼

調用該方法會對畫布生成一個當前內容的快照並以 Image 的形式返回,快照的實現邏輯很簡單,你也能夠本身實現更加複雜的快照邏輯。

2. Brush

直接使用紋理仍是比較繁瑣的,另外與紋理相關的還有顏色、線條的粗細以及其餘一些參數,因此這裏提供了一個 Brush 類來處理全部的這些數據。

Brush 的屬性在改變後會馬上影響接下來的繪製效果。

  • opacity 透明度

上面提到,塗鴉的本質是把紋理疊加到畫筆的過程,因此想要作出深淺不一的筆跡,紋理就須要具備透明度,能夠經過opacity 屬性來調節。

  • pointSize 筆跡粗細

pointSize 直接影響筆跡的粗細,它是以 iOS 尺寸的標準單位 點(point) 來衡量的,因此這是一個自適應屏幕像素密度的屬性。你不須要根據設備類型來計算實際像素,直接指定眼睛可見的大小就能夠了。

  • pointStep 點距

同上,因爲筆跡是經過疊加紋理實現的,所以除了透明度外,兩個紋理之間的距離也會影響到筆跡的深淺。另外若是把點距設定到大於筆跡的尺寸,甚至能夠畫出相似虛線的效果。點距的單位也是 點(point)

  • forceSensitive 壓力敏感度

之因此說 pointSize 是影響筆跡的粗細,而不是直接肯定,是由於有壓力感應的存在。筆跡的實際尺寸會隨着壓力的大小在 pointSize 指定的尺寸上下浮動,壓力越大,筆跡越粗。forceSensitive 影響筆跡對壓力浮動的劇烈程度,建議設置爲 0 - 1 之間的某個值。若是設置過大,筆跡隨壓力的便會會太過劇烈而失真;若是將 forceSensitive 的值設置爲 0,則對該畫筆關閉壓力感應效果,筆跡粗細不會隨着壓力而變化。

MaLiang 默認使用 iOS 設備的壓力感應特性,另外在一些不支持壓力感應的設備上使用模擬的壓力感應。模擬壓感依賴手勢移動的速度來判斷壓力的大小,速度越快壓力越小。

  • color 顏色

影響筆跡的顏色,實際畫出的顏色會計算進 opacity 的值,不過因爲紋理之間會疊加,因此相互效果能夠基本抵消。你通常不須要爲顏色額外指定透明度的值。

  • texture 紋理

texture 是一個非公開屬性,實際使用時只須要使用紋理圖的 Image 初始化 Brush 對象就能夠了,不須要關心 texture 的具體實現。

實際繪製時的顏色是設定的 color 與紋理的顏色混合以後的結果,因此須要保證紋理圖是白色的,才能確保繪製正確的顏色。這個問題可能會在將來改善。

texture 其實是一個 MLTexture 類型的對象,MLTexture 內部分裝了紋理相關的 OpenGL 實現,包括建立紋理、切換畫筆時的紋理綁定等。

3. Document

Document 不是實現塗鴉的必備組件,它是爲了提供一些更加深刻的功能而設計的。Document 維護着持有它的畫布的全部筆跡數據,依賴這些數據,能夠實現撤銷和重作功能。這兩個功能 MaLiang 已經默認實現。

經過 Document 持有的數據,你還能夠輕鬆實現保存塗鴉數據到文件的邏輯。反過來也能夠將保存的數據從新還原成畫布圖像,這樣能夠實現跨設備的數據同步功能。

Document 功能默認是沒有啓用的,須要手動經過代碼開啓:

canvas.setupDocument()
複製代碼

Document 在運行過程當中須要使用一部分硬盤空間來存放臨時數據,因此若是設備存儲空間不足時,上面的操做會拋出一個異常,爲了保證程序的健壯性,建議使用 do-catch 模式來捕獲可能的異常狀況:

do {
    try canvas.setupDocument()
} catch {
    // do somthing when error occurs
}
複製代碼

計劃實現的一些特性

計劃中 MaLiang 還存在一些還沒有實現的特性,這些特性會在將來逐漸添加進來,固然,你也幫助我實現,而後給我提交 PR :)

  • [x] 撤銷 & 重作,目前已經實現

  • [x] 導出圖片,已實現

  • [ ] 繪製文本到畫布中的指定位置

  • [ ] 繪製指定的圖片到畫布中的指定位置

  • [ ] 紋理旋轉,旋轉紋理能夠實現一些更加特殊的筆跡效果

由來

MaLiang 起源於多年前的一個塗鴉項目,當時仍是基於 Objective-C 和 OpenGL ES1 實現的,OpenGL ES1 對於抗鋸齒的支持不是很好,因此塗鴉的效果不怎麼敢恭維。而且當時因爲太年輕,整個框架的設計和結構都比較凌亂。雖然最後順利上架了一段時間,不過因爲各類各樣的緣由,整個項目隨當時的公司一塊兒無疾而終了。

去年開始重拾這個項目,打算基於 Swift 和 OpenGL ES3 徹底重寫,同時將當時處理得不是很好的地方加以改進,另外擴展了一些本身近期想到的東西,最終誕生了這個庫。

Why Swift?

使用 Swift 直接和 OpenGL 打交道確實不是一件容易的事情,有人奉勸我使用 OC 或者 C 做爲中間層來調用 OpenGL,再用 Swift 封裝上層邏輯,確實這樣能夠以最低的成本實現須要的效果。

不過做爲一個業餘項目,成本並非我第一考慮的要素,並且這個庫雖然是基於 OpenGL 的,可是真正跟 OpenGL 打交道的,其實也就那幾百行代碼。爲了追求這一點點成本和便利性,犧牲整個項目結構的統一和整潔,在我這是沒法接受的。

另外,引入 OC 代碼意味着同時引入了 OC 的動態運行時環境,這對 Swift 的執行效率會有必定的影響。雖然做爲一個 iOS 的項目,如今必然沒法擺脫 OC 的動態運行時環境,個人這點偏執彷佛也沒有什麼意義,不過誰知道之後會怎麼樣呢 :)

應用

說了半天,這個庫有什麼用?說實話我也不知道,或許能夠用來作簽名?不過簽名其實用 CoreGraphics 就足夠了。或許能夠用它來作一個畫畫的 App 來逗小孩玩,可能我真會這麼幹。。。

說到底,這主要是對當初懵懂時期經歷的一個記念吧。感興趣的均可以拿去玩 :)

接下來可能會打算基於這個庫開發一款塗鴉的 App。固然了,多年前的那個項目是不會復活了,新的這個 App 會是一個融合了不少我本身想法的全新項目。固然了但願不要半途而廢 - -!

相關文章
相關標籤/搜索