Inkpad繪圖原理淺析

張雲貴發表於2014-01-06

本文新版本已轉移到開源中國,歡迎前往指導。

 

Inkpad是一款非常優秀的iPad向量繪圖軟體,保管你一看見就忘不了。我的感覺是“一覽眾山小”、“相見甚晚”,以至於我寫的TouchVG就是“小巫見大巫”。必須好好學習這款軟體的程式碼,破解其高效能繪圖奧祕。

另外,在寫這篇日誌前本想使用Markdown語言寫乾淨的部落格,在 http://rhcad.github.io/ 基於Jekyll配置了日誌專案,在本地配置了釋出平臺,無奈要做的事和要學的知識太多,半途停下來了,看來我不是當極客的料。

如果你閱讀本文覺得哪裡寫得糟糕,可以提出來交流,如果本文能幫助你一點點就OK了,我也是在學習,本意不是想寫漂亮的文章。

一、觸控互動繪圖

互動式繪圖當然得先看觸控響應機制,先看一張序列圖:

觸控繪圖響應序列圖

WDCanvas是代表繪圖畫布的主檢視,直接響應原始觸控事件(touchesBegan、touchesMoved等),沒有使用雙擊、旋轉、長按等很流行的手勢識別器,奇怪吧?
我猜想Inkpad不使用手勢識別器有兩個原因:(1)手勢識別器採用延遲識別技術進行手勢二義性判斷,有幾百毫秒的延遲,會影響繪圖快速響應的感覺;(2)Inkpad是獨立的程式,所有介面都是自己的,不需要與各種帶有手勢識別器的介面元件共存(例如在滾動檢視、電子書頁面中繪圖)。
在WDCanvas中通過“[[WDToolManager sharedInstance].activeTool touchesBegan:touches withEvent:event inCanvas:self]”向當前命令傳遞觸控事件,當然用的是單例項模式和命令模式了。
在WDCanvas的touchesBegan函式中不立即向互動命令WDTool傳送觸控開始事件,而是延遲到touchesMoved才去傳送。我猜想作者本想區分普通輕擊(Tap)和拖動(Pan),但在實現時並不徹底:在下面的touchesEnded函式片段中,沒有移動也是要傳送touchesBegan的。我認為應該在touchesMoved中檢查移動距離來判斷是否算是已移動,而不是簡單的直接切換到已移動狀態。

if (!controlGesture_ && [self canSendTouchToActiveTool]) {
        if (!moved_) {
            [[WDToolManager sharedInstance].activeTool touchesBegan:touches withEvent:event inCanvas:self];
        }
        [[WDToolManager sharedInstance].activeTool touchesEnded:touches withEvent:event inCanvas:self];
    }

 

在開始觸控、當前不是鋼筆命令(WDPenTool)時,設定WDCanvas的activePath為空(self.drawingController.activePath = nil)。當前路徑(activePath)用於記憶鋼筆命令的當前圖形路徑,將activePath定義在WDDrawingController中而不是定義在WDPenTool中,是方便於在多個類中檢查使用。

互動命令類的觸控響應函式使用瞭如下所示的模板方法模式,具體的命令類重寫beginWithEvent、moveWithEvent、endWithEvent函式實現具體的繪圖邏輯。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
    //.....
    WDEvent *genericEvent = [self genericEventForTouch:primaryTouch_ inCanvas:canvas];
    [self beginWithEvent:genericEvent inCanvas:canvas];
        self.previousEvent = genericEvent;
    //......
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
    //......
    [self moveWithEvent:genericEvent inCanvas:canvas];
    self.previousEvent = genericEvent;
    //......
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
    [self endWithEvent:genericEvent inCanvas:canvas];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
    [self touchesEnded:touches withEvent:event inCanvas:canvas];
}

下面就要說說Inkpad的一大亮點:在動態拖曳繪圖中使用OpenGL ES高速繪圖了!

在繪圖命令(例如可繪製多種圖形的WDShapeTool)中,設定WDCanvas的shapeUnderConstruction屬性,將當前的臨時圖形(WDPath)傳遞給WDCanvas。WDCanvas的setShapeUnderConstruction函式將呼叫WDSelectionView成員的立即繪製函式,WDSelectionView使用OpenGL ES 1.1繪製各種動態圖形。用了OpenGL ES,拖動上千圖形也回顯毫無延遲感覺,這是Inkpad的亮點。

Inkpad在使用OpenGL ES 1.1繪製動態圖形時,採用的優化顯示方法有:(1)單點線寬,(2)忽略虛線樣式,(3)不填充,(4)快取圖形輪廓路徑。

 

觸控操作完成後就要向文件提交靜態圖形了:(1)繪圖命令在endWithEvent中向當前層提交新的圖形物件([canvas.drawing addObject:path]);(2)WDDrawingController 觸發WDCanvas檢視的重繪訊息;(3)在WDCanvas的drawRect:函式中,遍歷各個圖形,呼叫各個物件的renderInContext函式顯示所有圖形,其實質是將圖形的path顯示到當前CGContext上。

與動態圖形的繪製相比,提交靜態圖形並沒有採用OpenGL ES,而是使用最簡單的CGContext顯示方式,而且是重畫全部圖形,沒有使用CALayer等高階渲染方式。這就產生一個疑問,大量圖形會不會太慢?我還沒去做效能分析實驗,初步估計Inkpad採用的顯示優化方法是:(1)快取CGPath輪廓路徑和填充路徑等物件;(2)避免幾何計算和物件重構。

Inkpad已經這麼好了,就需要重新評估TouchVG最近做的一些顯示優化實驗,看看這些計算是否有必要:(1)在GCD中非同步繪製,使用前臺圖形列表和後臺圖形列表避免多執行緒衝突;(2)在CALayer上提前繪製,在主檢視中只貼圖(使用renderInContext,利用GPU貼圖);(3)每一層圖形一個CALayer,避免重繪所有圖形;(4)是否有必要使用OpenGL ES 2.0繪製靜態圖形?

 

二、相關核心類

Inkpad繪圖核心類

1、WDCanvas:繪圖主檢視,包含標尺檢視、選擇集渲染檢視、刪除提示線檢視,包含互動命令類要用的臨時圖形物件。

2、WDCanvasController:繪圖介面操作類,負責多種UI控制元件的管理和操作分發。

3、WDDrawingController:負責各種圖形的增刪改查邏輯。WDCanvasController是外殼功能,WDDrawingController是核心功能。

4、WDDrawing:圖形文件類,容納所有繪圖內容。

5、WDLayer:一個層,包含多個圖形元素WDElement。

6、WDStylable:可描邊、填充的圖形的基類。

7、WDAbstractPath:具有向量路徑的圖形的基類。

8、WDPath:可指定線端箭頭形狀的向量路徑的圖形類。

9、WDCompoundPath:複合路徑的圖形類。

 

10、WDTool:命令基類。

11、WDShapeTool:新增幾何形狀的命令,可繪製矩形、橢圓、星形、多邊形、直線段、螺旋線。這些不同圖形的繪圖工具是在WDToolManager中構造的。

12、WDPenTool:貝塞爾曲線繪圖工具。

13、WDFreehandTool:自由畫光滑曲線工具。

 

對於色部分的Core.Model類,主要基於向量路徑設計了幾何圖形模型類,好像不包含圖形的特徵資料(即後續編輯保持形狀特徵)吧。

我覺得我們可以基於Inkpad對圖形種類和互動命令工具進行擴充,做點行業相關的軟體來,如果你感興趣就加入討論吧。

這兩個UML圖是用EA畫的,InkpadUml.xmi可以匯入到其他UML建模工具。

 

寫了半天有點累了,先寫到這吧。期望目標是把Inkpad吃透,結合TouchVG生出一個新孩子:)

本文新版本已轉移到開源中國,歡迎前往指導。

相關文章