論文第4章:iOS繪圖平臺的實現

張雲貴發表於2013-07-31

面向移動裝置的向量繪圖平臺設計與實現

Design and Implementation of Mobile Device-oriented Vector Drawing Platform

引用本論文: 張雲貴. 面向移動裝置的向量繪圖平臺設計與實現[D]. 北京:北京理工大學軟體學院, 2013.

本論文的相似度為0%,是源創論文。歡迎評閱討論,請勿抄襲,如需更多資料請在部落格留言。

如果在研究或論文中使用到,歡迎回復或私信你的學校、姓名、研究領域,並在論文中新增引用或致謝。感謝你對開放成果的尊重和鼓勵。

 

第4章 iOS繪圖平臺的實現

本章闡述了iOS繪圖平臺的實現方法,主要是在跨平臺核心的基礎上實現iOS上的畫布介面卡和檢視介面卡,對圖形顯示優化技術進行實驗研究。

4.1 基於Quartz 2D實現畫布介面卡

4.1.1 畫布原語與Quartz 2D的對映

在iOS上(Xcode開發環境)基於Quartz 2D圖形庫實現了畫布介面卡類GiQuartzCanvas。該類實現了畫布介面GiCanvas,將其畫布原語對映到Quartz 2D的繪圖函式,如表4‑1和表4‑2所示。

這些畫布原語多數直接使用Quartz 2D的函式(CG開頭的函式)實現,影象和文字使用了UIKit框架的UIImage、UIFont類以及Foundation框架的NSString文字顯示函式。因為UIKit封裝了影象和文字的常用功能函式,所以使用該框架能簡化實現。

表4‑1 畫布原語對映到Quartz 2D

畫布原語

測試號

Quartz 2D函式對映

drawRect

b

CGContextFillRect、CGContextStrokeRect

drawEllipse

c

CGContextFillEllipseInRect、CGContextStrokeEllipseInRect

beginPath

多個

CGContextBeginPath

moveTo

多個

CGContextMoveToPoint

lineTo

e

CGContextAddLineToPoint

bezierTo

f

CGContextAddCurveToPoint

quadTo

g

CGContextAddQuadCurveToPoint

closePath

e

CGContextClosePath

drawPath

多個

CGContextDrawPath

drawHandle

i

CGContextDrawImage、CGContextConcatCTM、[UIImage CGImage]

drawBitmap

i

CGContextDrawImage、CGContextConcatCTM、[UIImage CGImage]

注:其中的測試號為圖4‑4中的測試子圖號。

表4‑2 其他畫布原語與Quartz 2D的對映

畫布原語

測試號

Quartz 2D函式對映

setPen

多個

CGContextSetRGBStrokeColor、CGContextSetLineWidth

CGContextSetLineDash、CGContextSetLineCap

setBrush

多個

CGContextSetRGBFillColor

clearRect

a

CGContextClearRect

saveClip

h

CGContextSaveGState

restoreClip

h

CGContextRestoreGState

clipRect

h

CGContextClipToRect

clipPath

h

CGContextClip

drawLine

d

CGContextBeginPath、CGContextMoveToPoint

CGContextAddLineToPoint、CGContextStrokePath

drawTextAt

j

[NSString drawAtPoint:]、[UIFont systemFontOfSize:]

在實現這些函式時,本文對下列內容進行了特殊處理。

(1)設定虛線模式(CGContextSetLineDash)時需要與線寬(如果大於1)成正比例,以保持線型與形狀的比例。針對手繪應用,本文將線型與線端型別按圖4‑1搭配使用:a、為了讓線寬較大的短線更像一個圓點,對於實線的線型使用圓端的線端型別;b、為了讓點劃線等虛線型別的線條的空白間隙整齊,對於所有的虛線型別搭配使用平端的線端型別。

41_thumb3

圖4‑1 線型與線端搭配的效果

(2)使用CGContextDrawPath函式進行繪製並描繪閉合路徑時,不能分兩次呼叫分別描邊和填充,因為該函式執行完後會清空當前路徑(CGContextClip也一樣),需要以引數kCGPathFillStroke或kCGPathEOFill同時填充和描邊。

(3)使用CGContextDrawPath進行填充時,引數應設定為kCGPathEOFill或kCGPathEOFillStroke(以奇偶規則填充),不能設定為kCGPathFill或kCGPathFillStroke,否則難以在教材頁面上顯示如圖4‑2所示的中間透明的環形。原因是一個路徑的子路徑是分別填充的,為了避免環形中心被填充,需要採用kCGPathEOFill或kCGPathEOFillStroke填充路徑。

42_thumb2

圖4‑2 以奇偶規則填充環形

4.1.2 畫布介面卡的跨平臺單元測試

本文采用測試驅動開發方式逐步實現了畫布介面卡,設計結構如圖4‑3所示。單元測試類TestCanvas是跨平臺核心中的C++類。

43_thumb2

圖4‑3 畫布介面卡的測試結構

本設計結構的創新點:(1)經實驗證明在跨平臺核心中可以使用C++在iOS上繪圖。(2)TestCanvas類可以替換為圖形實體類或繪圖命令類,替換後不影響裝置平臺相關的畫布介面卡和檢視類。因此,本設計結構易於擴充套件和移植。

在單元測試類TestCanvas中使用隨機函式繪製各種圖形,在iPad上的測試圖形效果如圖4‑4所示。其中,井字格的背景用於表示繪圖檢視為透明檢視。因為通常圖文批註等繪圖應用需要在宿主頁面上顯示圖形,所以將繪圖檢視設定為透明檢視。各個子圖分別為相應的畫布原語的單元測試結果,對應關係見4.1.1節。

44_thumb6

圖4‑4 iPad上的畫布介面卡的測試效果

4.1.3 影象的向量化顯示

使用UIKit框架的UIImage類顯示影象,影象的顯示介面函式定義為:

void drawBitmap(const char* name, float xc, float yc, float w, float h, float a)

其中,使用名稱name標識影象物件,(xc,yc)為影象的中心顯示位置,w和h為顯示目標寬高,a為旋轉的角度(世界座標系中的逆時針方向)。

按照圖4‑5所示的矩陣變換過程使用Quartz 2D顯示影象,演算法過程如下:

(1)Y上下顛倒,原點移到(xc,yc),即計算矩陣:

af = CGAffineTransformMake(1, 0, 0, -1, xc, yc) (4-1)

(2)以原點為中心旋轉a角度,即計算矩陣:

af = CGAffineTransformRotate(af, a) (4-2)

(3)使用CGContextConcatCTM應用變換矩陣af;

(4)顯示影象充滿到矩形CGRectMake(-w/2, -h/2, w, h);

(5)還原矩陣,即再應用(3)中矩陣的逆反矩陣。

45_thumb2

圖4‑5 影象顯示的矩陣變換過程

4.1.4 影象資源的管理

由應用程式新增UIImage物件,在GiViewController類(見4.3.1的說明)中快取UIImage物件及標識名稱。應用程式在切換到後臺或接收到記憶體緊張通知時,呼叫繪圖平臺的釋放快取函式,這些影象物件就釋放掉。

在需要顯示時由GiViewController根據標識名稱呼叫getImageShapePath函式從標識名稱得到實際圖片檔案的地址,重新載入影象。應用程式可過載GiViewController的getImageShapePath函式指定不同的存放地址。

4.1.5 控制點的影象顯示

iOS繪圖軟體通常使用影象顯示控制點,本文按下面方式實現圖4‑6的效果。

46_thumb1

圖4‑6 控制點影象顯示效果

(1)在跨平臺核心中呼叫drawHandle函式顯示控制點,該函式的定義為:

void drawHandle(float x, float y, int type)

其中,(x,y)為控制點座標,type為控制點的影象型別。例如,“1”表示普通點的藍色圓點影象,“2”表示熱點的紅色圓點影象。

(2)在iOS畫布介面卡的drawHandle實現函式中,根據type自動從程式資源中載入和快取圓點影象(UIImage物件)。

(3)因為繪圖上下文的單位為點,所以將影象寬高轉換到點單位,記為w和h。

(4)按照圖4‑7所示的矩陣變換過程使用Quartz 2D顯示影象,應用變換矩陣:

af = CGAffineTransformMake(1, 0, 0, -1, x – w/2, y + h/2) (4-3)

47_thumb2

圖4‑7 控制點顯示的矩陣變換過程

(5)顯示影象充滿到矩形CGRectMake(0, 0, w, h)。

(6)還原矩陣,即再應用(4)中矩陣的逆反矩陣。

4.2 顯示優化技術研究

4.2.1 基於點陣圖的雙緩衝繪圖

本節通過實驗證明了基於點陣圖的雙緩衝繪圖技術不適合iOS裝置。雙緩衝繪圖技術是傳統的繪圖優化技術,主要涉及兩種點陣圖的用法:(1)快取點陣圖(Cached Bitmap),是圖形顯示內容的快照,下次檢視重繪時直接顯示該點陣圖,以免重新顯示圖形費時。(2)緩衝點陣圖(Buffered Bitmap),用於避免直接在目標上下文中繪圖引起的逐步顯示閃爍問題,先繪製圖形到緩衝點陣圖,然後一次性複製到目標上下文。

本文使用Quartz 2D進行雙緩衝繪圖技術實驗,按照是否有緩衝點陣圖、快取點陣圖重建和重繪、圖形規模差異進行組合實驗,在iPad 3上的實驗結果見表4‑3,其中的六列的含義如下。

表4‑3 在iPad 3上的雙緩衝繪圖時間(毫秒)

 

無緩衝

重建快取

無緩衝

4倍圖形

無緩衝

重繪快取

有緩衝

重建快取

有緩衝

4倍圖形

有緩衝

重繪快取

建立點陣圖上下文

-

-

-

0.1

0.1

0.1

清除背景

-

-

-

21

20

20

顯示快取點陣圖

-

-

46

-

-

46

重建快取點陣圖

33

32

-

1

1

-

應用緩衝點陣圖

-

-

-

64

76

64

重新顯示圖形

71

260

-

71

261

-

drawRect總計

108

297

47

168

411

138

(1)無緩衝、重建快取:直接在當前圖形上下文(檢視上下文)上繪製圖形,快取點陣圖需要重新生成,當檢視第一次顯示或圖形改變後重新整理顯示時屬於該條件。

(2)無緩衝、4倍圖形:在上面(1)顯示條件的基礎上,顯示4倍的圖形量。

(3)無緩衝、重繪快取:直接在當前檢視上下文上繪製,一次性顯示快取點陣圖,不重新顯示圖形。

(4)有緩衝、重建快取:使用緩衝點陣圖繪圖,快取點陣圖需要重新生成,當檢視第一次顯示或圖形改變後重新整理顯示時屬於該顯示條件。

(5)有緩衝、4倍圖形:在上面(4)的顯示條件基礎上,顯示4倍的圖形量。

(6)有緩衝、重繪快取:先在緩衝點陣圖上下文中顯示有圖形內容的快取點陣圖,不重新顯示圖形,然後將緩衝點陣圖顯示到檢視上下文。

表4‑3中的各個評測引數解釋如下:

(1)建立點陣圖上下文:使用CGBitmapContextCreate函式建立緩衝點陣圖上下文,需用將座標系由預設的LLO座標系改為ULO座標系、計算點陣圖寬高需用考慮到螢幕放大比例(即將檢視的點單位轉換為畫素單位)。

(2)清除背景:使用CGContextClearRect函式將顯示區域填充為透明背景。

(3)顯示快取點陣圖:使用CGContextDrawImage函式顯示已建立的快取點陣圖。因為Quartz 2D內部座標系是LLO型別,影象的Y軸正方向朝上,顯示前需用將繪圖上下文的當前轉換矩陣上下臨時顛倒為LLO座標系。

(4)重建快取點陣圖:使用CGBitmapContextCreateImage函式對繪圖上下文建立一個快照影象,影象包含了各種圖形的顯示內容。

(5)應用緩衝點陣圖:首先使用CGBitmapContextCreateImage函式從緩衝點陣圖上下文生成快照影象,然後顯示到檢視上下文中,顯示前將當前轉換矩陣上下顛倒。

(6)重新顯示圖形:使用畫布介面顯示所有圖形。

(7)drawRect總計:以上顯示工作都是在檢視的drawRect函式中進行的,此處統計了所有顯示工作的時間。

本文對錶4‑3的顯示時間進行對比分析,得出下列結論:

(1)在iOS裝置上無需使用基於緩衝點陣圖的顯示技術,直接在當前圖形上下文上繪圖更快,原因是iOS內部使用了基於矩形紋理的緩衝顯示技術。

(2)清除背景較耗時。使用UIGraphicsBeginImageContextWithOptions函式用時約9毫秒,相對較快,並能自動背景透明和設定當前變換矩陣,因此在需要點陣圖上下文繪圖時要用該函式,不使用更底層的CGBitmapContextCreateImage函式。

注:清除背景(CGContextClearRect)較耗時,佔用記憶體較多,建立點陣圖上下文則很快,不影響記憶體。推測其原因是建立點陣圖上下文時並沒有分配繪圖緩衝區,在實際繪製時才分配緩衝區。(2014-2-19)

(3)快取點陣圖較大,顯示較慢。應當在重新顯示圖形所需時間超過快取點陣圖的顯示時間時才使用快取點陣圖。可動態記錄該閥值和決定是否快取圖形內容。

本文經實驗發現圖形數量與顯示時間成如圖4‑8所示的線性比例關係。繪圖上下文內顯示圖形前的初始化時間與圖形數量無關,在iPod Touch 4上約為40毫秒。

本文對在檢視上下文和點陣圖上下文上的圖形顯示速度進行評測,在iPod Touch 4上的實驗結果如圖4‑8所示。可見顯示時間與圖形數量成線性比例關係,在檢視上下文上的顯示略慢。結合iOS顯示原理的分析結論,本文做出下列推測:

(1)兩者的向量圖形顯示速度接近,檢視上下文沒有使用硬體加速能力。

(2)在檢視上下文的內部顯示流程中,先將向量圖形以PostScript指令快取到顯示列表,然後使用OpenGL ES渲染這些指令,增加了圖形快取時間,所以比點陣圖上下文略慢。快取PostScript指令的優點是在動畫顯示和放大顯示時保持高質量。

48_thumb2

圖4‑8 點陣圖上下文和檢視上下文的顯示速度對比

4.2.2 快速手繪的增量繪圖技術

在快速手繪原筆跡圖形時,每次增加了一段筆跡圖形(內部為多段貝塞爾曲線)都需要在檢視顯示新的內容。如果每次都全部重新顯示所有圖形就會越來越慢,影響快速手繪應用的回顯體驗。本文設計了一種增量繪圖技術的實現方式,使得圖形顯示時間由遞增趨勢變為常量時間(通常能保持在60毫秒以下)。實現方式如下:

(1)在觸控過程中設定當前臨時圖形的幾何引數,動態顯示該圖形:呼叫檢視介面的redraw函式,在檢視介面卡的redraw實現函式中呼叫檢視的setNeedDisplay函式,在下次系統呼叫檢視的drawRect函式時呼叫核心的dynDraw函式顯示圖形。

(2)一次觸控完成後,在核心中將該臨時圖形提交到圖形列表,記下新圖形,然後呼叫檢視介面的regenAppend函式。如果呼叫regen函式則會顯示所有圖形。如果觸控太快來不及顯示,就記下更多的新圖形,後續批量顯示。

(3)在檢視介面卡的regenAppend實現函式中,先得到檢視的當前快照影象(使用 [CALayer renderInContext:] 函式),然後通知檢視重繪。

(4)在檢視重繪訊息響應函式drawRect中,先顯示並銷燬該快照影象,然後呼叫核心的drawAppend函式,由後者顯示(2)中記錄的新圖形。

(5)繼續觸控繪圖,轉到步驟(1),實現快速增量繪圖。

本文所實現的增量繪圖技術的關鍵點在於每次新增圖形後只需要顯示快照影象和新增圖形,無需顯示之前已有圖形。以iPad 3為例,使用renderInContext生成快照影象約36毫秒,顯示快照影象約46毫秒,生成和顯示是在不同的訊息響應函式中進行的,整體顯示很流暢,不受圖形數量影響。

4.2.3 快速動態繪圖的多層繪圖技術

動態繪圖的過程:在觸控過程中改變臨時圖形,呼叫檢視介面的redraw函式,由檢視介面卡設定檢視無效區域並觸發重繪訊息,在重繪訊息響應函式中顯示這些臨時圖形,最終實現了及時回顯的互動式動態繪圖。

在一個檢視中同時顯示靜態圖形和動態臨時圖形的問題是動態繪圖相對於當前觸控位置有短暫延遲,會產生拖尾現象。雖然可以使用快照影象避免重新顯示所有圖形,但也需要幾十毫秒進行影象顯示。

本文設計瞭如圖4‑9所示的多層檢視結構,將靜態圖形和動態圖形分別在單獨的檢視中顯示。靜態圖形檢視是應用宿主檢視中的子檢視,設定為背景透明以便顯示出閱讀器等應用宿主檢視中的內容。動態圖形檢視與靜態圖形檢視大小相同,動態圖形檢視通常在所有檢視的頂端。因為iOS中每個檢視都有獨立的層,能夠獨立繪製,所以分離檢視後可以讓動態圖形檢視不顯示靜態圖形內容,改善回顯體驗。

49_thumb4

圖4‑9 繪圖檢視的層次結構

從靜態圖形檢視獲取快照影象(例如用於預覽)是較常用的功能。本文比較了下列獲取快照的方法,結論是呼叫層的renderInContext函式是最快的方法。

方法1:在點陣圖上下文中繪製所有圖形,與在檢視顯示圖形的時間相近,慢。

方法2:在點陣圖上下文中呼叫層的drawInContext函式,比方法1慢10毫秒。

方法3:在點陣圖上下文中呼叫檢視的層的renderInContext函式,最快,與圖形數量無關。例如,在iPad 3上用時36毫秒,在iPod Touch 4上用時11毫秒。與表4‑3對比還能得出結論:renderInContext比顯示快取點陣圖更快。

對靜態圖形檢視呼叫層的renderInContext函式會得到該層及所有子層的快照影象,包含了動態臨時圖形的顯示內容,不滿足實際需要。為了僅得到靜態圖形檢視的顯示內容,本文的解決方法是將這兩類圖形所在的檢視設定為應用宿主檢視的同級子檢視。但在靜態圖形檢視中響應觸控訊息進行繪圖時發現一個問題:第一次觸控能正常工作,後續觸控不能工作。本文試驗了兩種方法解決該問題:一是在動態圖形檢視中也響應觸控訊息並轉發給靜態圖形檢視;二是將動態圖形檢視設定為禁止互動(userInteractionEnabled = NO)。第二種方法更簡單。

4.2.4 動態繪圖的引數優化

由於動態互動式繪圖需要頻繁更新顯示內容(靜態圖形較少更新),提高動態繪圖的效能就比較關鍵。本文除了上述顯示優化技術外,還針對一些關鍵的繪圖引數進行了優化分析,在iPad 2上顯示100個橢圓的測試結果見圖4‑10。

410_thumb2

圖4‑10 繪圖引數對顯示時間的影響

圖4‑10中的測試引數有:

(1)平滑度。Quartz 2D內部的擬合折線與實際曲線的最大偏差距離,螢幕畫素值,小於1時高精度渲染、費時。

(2)線寬。Quartz 2D採用填充方式實現線寬效果,一個圖形內部的重疊部分不會重複填充。

(3)反走樣(糙,不反走樣。反,反走樣)。iOS在單獨的離屏點陣圖上對圖形渲染實現反走樣效果。

(4)填充(空,不填充。填,填充)。在閉合形狀的區域中填充單一顏色。

(5)線型(實,實線。虛,虛線)。指定線條陣列描繪形狀路徑。

從圖4‑10可以得到下列結論:

(1)平滑度小於3畫素時較費時,超過3後顯示時間變化較小、影響美觀性。

(2)線型影響較大,虛線的顯示時間是實線的兩倍以上。

(3)反走樣所需時間是不反走樣的接近兩倍。

(4)填充對顯示效能影響較大,顯示時間是不填充的兩倍以上。

(5)線寬影響較小。

因此,平滑度可以設定為3,快速顯示時不使用虛線等線型、不反走樣、不填充能顯著加快顯示速度。可以採用逐步渲染技術(例如,在子執行緒中分精度等級渲染、先批量描邊後批量填充)提高響應速度。

4.3 iOS繪圖平臺的結構

4.3.1 靜態結構

根據4.2節顯示優化技術的研究結果,iOS繪圖平臺按圖4‑11設計靜態類結構(省略了跨平臺核心的結構),相應類的說明如下。

411_thumb3

圖4‑11 iOS繪圖適配模組的結構

(1)GiViewController。面向應用程式的繪圖封裝介面類,提供常用API,從UIViewController派生。

(2)GiGraphView。顯示靜態圖形的檢視類,負責觸控手勢識別,委託核心的GiCoreView實現圖形顯示和手勢操作。

(3)DynDrawView。顯示動態圖形的檢視類,不負責觸控手勢識別,委託核心的GiCoreView顯示動態圖形。

(4)GiViewAdapter。檢視介面卡,允許核心回撥iOS檢視,通知重新整理顯示。

(5)GiQuartzCanvas。使用Quartz 2D實現的畫布介面卡。

(6)GiCoreView。跨平臺核心的檢視分發器,託管圖形物件,分發顯示請求和手勢資訊給圖形列表和當前命令。

4.3.2 應用效果

在iOS繪圖平臺中應用多層繪圖技術分離GiGraphView和DynDrawView檢視,提高了動態互動式繪圖的回顯速度。在GiGraphView檢視中應用增量繪圖技術,連續繪製自由曲線等圖形時不出現明顯的拖尾現象。在旋轉螢幕和動態放縮過程中應用繪圖引數優化技術,提高顯示反饋速度。採用這些技術後,繪圖體驗較流暢。

在跨裝置平臺的核心中使用繪圖命令可以顯示各種圖形,在核心檢視中使用仿射變換實現放縮顯示,圖4‑12展示了實際繪圖效果[1]

412_thumb3

圖4‑12 iOS綜合繪圖效果

目前,iOS繪圖平臺已在數字教育等領域應用,降低了應用開發的工作量。圖4‑13(a)和(b)展示了在閱讀器頁面上進行批註式教學的效果。圖4‑13(c)展示了第三方公司基於TouchVG開發的“腦力風暴”iPad軟體的圖文筆記效果[2]

413_thumb2

圖4‑13 繪圖平臺在數字教育等領域的應用效果

4.4 本章小結

本章描述了基於Quartz 2D實現畫布介面卡的方式,實現了圖形和影象的向量化顯示,針對手繪應用設計了虛線模式和線端型別的匹配規則。畫布介面卡的單元測試使用了跨平臺核心自動繪製圖形,證明了在跨平臺核心中可以使用C++在iOS上互動式繪圖。

本章對雙緩衝技術進行了實驗,結論是基於緩衝點陣圖的顯示技術不適合iOS等移動裝置,需要根據時間閥值動態決定是否使用快取點陣圖。總結了圖形數量與顯示時間的線性比例規律。

本章設計了適合連續手繪的增量繪圖技術的實現方式和快速動態繪圖的多層繪圖技術的實現方式,通過利用快取點陣圖和層加快了互動式繪圖的回顯速度,總結了繪圖屬性的優化方法。

最後,描述了iOS繪圖平臺的結構和應用效果。


[1] 手繪曲線採用三次引數樣條曲線模型,本文對曲線模型和資料點取樣法不做研究和論述。

[2] 已在AppStore釋出,地址為 https://itunes.apple.com/us/app/nao-li-feng-bao/id565836136?mt=8 。

相關文章