本文新版本已轉移到開源中國,歡迎前往指導。git
Inkpad是一款很是優秀的iPad矢量繪圖軟件,保管你一看見就忘不了。個人感受是「一覽衆山小」、「相見甚晚」,以致於我寫的TouchVG就是「小巫見大巫」。必須好好學習這款軟件的代碼,破解其高性能繪圖奧祕。github
另外,在寫這篇日誌前本想使用Markdown語言寫乾淨的博客,在 http://rhcad.github.io/ 基於Jekyll配置了日誌項目,在本地配置了發佈平臺,無奈要作的事和要學的知識太多,半途停下來了,看來我不是當極客的料。canvas
若是你閱讀本文以爲哪裏寫得糟糕,能夠提出來交流,若是本文能幫助你一點點就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繪製靜態圖形?
一、WDCanvas:繪圖主視圖,包含標尺視圖、選擇集渲染視圖、刪除提示線視圖,包含交互命令類要用的臨時圖形對象。
二、WDCanvasController:繪圖界面操做類,負責多種UI控件的管理和操做分發。
三、WDDrawingController:負責各類圖形的增刪改查邏輯。WDCanvasController是外殼功能,WDDrawingController是內核功能。
四、WDDrawing:圖形文檔類,容納全部繪圖內容。
五、WDLayer:一個層,包含多個圖形元素WDElement。
六、WDStylable:可描邊、填充的圖形的基類。
七、WDAbstractPath:具備矢量路徑的圖形的基類。
八、WDPath:可指定線端箭頭形狀的矢量路徑的圖形類。
九、WDCompoundPath:複合路徑的圖形類。
十、WDTool:命令基類。
十一、WDShapeTool:添加幾何形狀的命令,可繪製矩形、橢圓、星形、多邊形、直線段、螺旋線。這些不一樣圖形的繪圖工具是在WDToolManager中構造的。
十二、WDPenTool:貝塞爾曲線繪圖工具。
1三、WDFreehandTool:自由畫光滑曲線工具。
對於色部分的Core.Model類,主要基於矢量路徑設計了幾何圖形模型類,好像不包含圖形的特徵數據(即後續編輯保持形狀特徵)吧。
我以爲咱們能夠基於Inkpad對圖形種類和交互命令工具進行擴充,作點行業相關的軟件來,若是你感興趣就加入討論吧。
這兩個UML圖是用EA畫的,InkpadUml.xmi能夠導入到其餘UML建模工具。
寫了半天有點累了,先寫到這吧。指望目標是把Inkpad吃透,結合TouchVG生出一個新孩子:)
本文新版本已轉移到開源中國,歡迎前往指導。