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

2021-09-11 14:23:35 字數 4351 閱讀 5103

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

maliang是 ios 平台乙個基於 opengl es3 的塗鴉繪相簿,使用純 swift 實現,支援自定義紋理、壓力感應、自動筆觸等特性,並且提供了一定的自定義擴充套件的空間。

這篇文章可以看作是對 github 上readme說明的詳細擴充套件和補充說明。

我的理念是盡量製造簡單、優雅的東西,雖然有時候要做到這一點其實很難,但是盡量往這方面靠吧。maliang的整合和使用都很簡單,我把大量對使用者來說沒有什麼用也沒有必要了解的內部邏輯都隱藏了。當然了,如果你的好奇心很重,可以自己去看源**。這篇文章也會介紹一些內部實現的思路。

maliang已經推送到了 cocopods 的官方 repo,所以,你只需要在 podfile 增加一條 pod 指令然後install就可以在專案中使用了:

pod 'maliang'

複製**

然後在需要使用的地方引入 mudule。當然,首先需要編譯一下,否在會報找不到 moudle 的錯誤

import maliang

複製**

1. canvas

畫布是maliang最基礎的元件,所有的塗鴉都發生在canvas上。canvas本質上是乙個uiview,所以你可以使用任何你原來建立uiview的方法來建立乙個畫布,並將它新增到你的介面上。

canvas設定正確的布局約束,然後你就可以開始塗鴉了,比如寫乙個像下面這樣的東東 :)

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

canvas繼承自mlview(ml是maliang的縮寫,不是那個機器學習的東東),mlview做了幾乎所有與opengl打交道的事情,雖然它被定義成乙個open的類,但實際使用中基本是用不到的。不過了解一些原理也無傷大雅麼~

opengl塗鴉的核心是紋理(texture),本質上就是沿著手指軌跡,不斷地將紋理疊加到畫布上的過程。所以能畫出什麼樣的筆跡,完全取決於使用的紋理,以及它的大小、顏色、尺寸等引數。

mlview初始化之後會使用自帶的建立乙個預設的紋理,這個紋理就是乙個簡單的不透明的圓點,所以只能畫最簡單的線條。如果想要畫出上圖那樣的效果,就需要使用相對複雜一點的紋理了。maliang的示例專案裡面提供了好幾個設定好的紋理,用他們可以模擬出鉛筆、水筆以及毛筆的特效,上面的文字就是使用毛筆特效寫出來的。

快照canvas提供了乙個簡單的快照功能:

open func

snapshot

() -> uiimage?

複製**

呼叫該方法會對畫布生成乙個當前內容的快照並以 image 的形式返回,快照的實現邏輯很簡單,你也可以自己實現更加複雜的快照邏輯。

2. brush

直接使用紋理還是比較繁瑣的,另外與紋理相關的還有顏色、線條的粗細以及其他一些引數,所以這裡提供了乙個brush類來處理所有的這些資料。

brush的屬性在改變後會立刻影響接下來的繪製效果。

上面提到,塗鴉的本質是把紋理疊加到畫筆的過程,所以想要做出深淺不一的筆跡,紋理就需要具有透明度,可以通過opacity屬性來調節。

pointsize直接影響筆跡的粗細,它是以 ios 尺寸的標準單位點(point)來衡量的,所以這是乙個自適應螢幕畫素密度的屬性。你不需要根據裝置型別來計算實際畫素,直接指定眼睛可見的大小就可以了。

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

之所以說pointsize是影響筆跡的粗細,而不是直接確定,是因為有壓力感應的存在。筆跡的實際尺寸會隨著壓力的大小在pointsize指定的尺寸上下浮動,壓力越大,筆跡越粗。forcesensitive影響筆跡對壓力浮動的劇烈程度,建議設定為0-1之間的某個值。如果設定過大,筆跡隨壓力的便會會太過劇烈而失真;如果將forcesensitive的值設定為0,則對該畫筆關閉壓力感應效果,筆跡粗細不會隨著壓力而變化。

maliang預設使用 ios 裝置的壓力感應特性,另外在一些不支援壓力感應的裝置上使用模擬的壓力感應。模擬壓感依賴手勢移動的速度來判斷壓力的大小,速度越快壓力越小。

影響筆跡的顏色,實際畫出的顏色會計算進opacity的值,不過由於紋理之間會疊加,所以相互效果可以基本抵消。你一般不需要為顏色額外指定透明度的值。

texture是乙個非公開屬性,實際使用時只需要使用紋理圖的 image 初始化brush物件就可以了,不需要關心texture的具體實現。

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

texture實際上是乙個mltexture型別的物件,mltexture內部分裝了紋理相關的opengl實現,包括建立紋理、切換畫筆時的紋理繫結等。

3. document

document不是實現塗鴉的必備元件,它是為了提供一些更加深入的功能而設計的。document維護著持有它的畫布的所有筆跡資料,依賴這些資料,可以實現撤銷和重做功能。這兩個功能 maliang 已經預設實現。

通過document持有的資料,你還可以輕鬆實現儲存塗鴉資料到檔案的邏輯。反過來也可以將儲存的資料重新還原成畫布影象,這樣可以實現跨裝置的資料同步功能。

document功能預設是沒有啟用的,需要手動通過**開啟:

canvas.setupdocument()

複製**

document在執行過程中需要使用一部分硬碟空間來存放臨時資料,所以如果裝置儲存空間不足時,上面的操作會丟擲乙個異常,為了保證程式的健壯性,建議使用 do-catch 模式來捕獲可能的異常情況:

do  catch 

複製**

計畫中maliang還存在一些尚未實現的特性,這些特性會在未來逐漸新增進來,當然,你也幫助我實現,然後給我提交 pr :)

maliang起源於多年前的乙個塗鴉專案,當時還是基於 objective-c 和 opengl es1 實現的,opengl es1 對於抗鋸齒的支援不是很好,所以塗鴉的效果不怎麼敢恭維。並且當時由於太年輕,整個框架的設計和結構都比較凌亂。雖然最後順利上架了一段時間,不過由於各種各樣的原因,整個專案隨當時的公司一起無疾而終了。

去年開始重拾這個專案,打算基於 swift 和 opengl es3 完全重寫,同時將當時處理得不是很好的地方加以改進,另外擴充套件了一些自己近期想到的東西,最終誕生了這個庫。

使用 swift 直接和 opengl 打交道確實不是一件容易的事情,有人奉勸我使用 oc 或者 c 作為中間層來呼叫 opengl,再用 swift 封裝上層邏輯,確實這樣可以以最低的成本實現需要的效果。

不過作為乙個業餘專案,成本並不是我第一考慮的要素,而且這個庫雖然是基於 opengl 的,但是真正跟 opengl 打交道的,其實也就那幾百行**。為了追求這一點點成本和便利性,犧牲整個專案結構的統一和整潔,在我這是無法接受的。

另外,引入 oc **意味著同時引入了 oc 的動態執行時環境,這對 swift 的執行效率會有一定的影響。雖然作為乙個 ios 的專案,現在必然無法擺脫 oc 的動態執行時環境,我的這點偏執似乎也沒有什麼意義,不過誰知道以後會怎麼樣呢 :)

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

《基於MFC的OpenGL程式設計》系列文章

1,基於 mfc的 opengl 程式設計 part 1 a primer 2,基於 mfc的 opengl 程式設計 part 2 setting up opengl on windows 3,基於 mfc的 opengl 程式設計 part 3 drawing 2d shapes 4,基於 mf...

《基於MFC的OpenGL程式設計》系列文章

1,基於mfc的opengl程式設計 part 1 a primer 2,基於mfc的opengl程式設計 part 2 setting up opengl on windows 3,基於mfc的opengl程式設計 part 3 drawing 2d shapes 4,基於mfc的opengl程式...

《基於MFC的OpenGL程式設計》系列文章

1,基於mfc的opengl程式設計 part 1 a primer 2,基於mfc的opengl程式設計 part 2 setting up opengl on windows 3,基於mfc的opengl程式設計 part 3 drawing 2d shapes 4,基於mfc的opengl程式...