Design and Implementation of Mobile Device-oriented Vector Drawing Platform
引用本論文: 張雲貴. 面向移動裝置的向量繪圖平臺設計與實現[D]. 北京:北京理工大學軟體學院, 2013.
本論文的相似度為0%,是源創論文。歡迎評閱討論,請勿抄襲,如需更多資料請在部落格留言。
如果在研究或論文中使用到,歡迎回復或私信你的學校、姓名、研究領域,並在論文中新增引用或致謝。感謝你對開放成果的尊重和鼓勵。
第3章 移動繪圖平臺的架構設計
本章根據移動裝置的特點,設計了跨平臺繪圖核心及裝置介面。為了在iOS和Android上實現向量繪圖平臺,本章設計了介面卡的實現方式。後面兩章將基於本章內容具體闡述在iOS和Android上介面卡的實現方式。
3.1 繪圖應用的跨平臺設計
相對於Web應用,本地應用通常開發成本高、難以實現跨平臺。iOS和Android的主要程式語言分別是ObjC(即Objective-C)和Java,相互移植很困難。本文提出了基於虛擬函式多型行為的跨平臺擴充套件機制。該機制將主要功能在跨平臺核心中使用C++實現,而在不同的移動平臺上實現介面卡模組,使其易於擴充套件。
3.1.1 基於多型的回撥擴充套件機制
對於iOS,介面封裝層採用ObjC和C++實現(實現檔案的字尾名為.mm)。ObjC類可以呼叫C++類和全域性函式,但不能從C++類繼承。國內外的相似軟體通常使用ObjC實現所有繪圖功能,不能跨平臺。
本文針對iOS設計瞭如圖3‑1(a)所示的擴充套件機制:與裝置平臺相關的類從繪圖核心的C++介面派生,以虛擬函式多型方式實現定製。其優點是跨平臺核心可以呼叫與裝置平臺相關的C++類,C++類再呼叫ObjC類。創新點是可以在跨平臺核心中使用C++實現易於擴充的功能,使用ObjC對核心進行擴充套件。
對於Android,介面封裝層採用Java實現,在核心層與介面封裝層之間提供JNI封裝層,實現C++到Java的銜接轉化。通常的實現方案是使用Java實現所有繪圖功能,很少從C++類繼承和擴充套件。本文設計瞭如圖3‑1(b)所示的呼叫和回撥機制:介面封裝層通過JNI呼叫核心,核心通過SWIG所生成的Director類回撥到JNI的Java類,介面封裝層再從Java類派生,以虛擬函式多型方式實現定製。創新點是可在跨平臺核心中使用C++實現易於擴充的繪圖功能,通過SWIG在Android上實現定製。
基於多型的回撥擴充套件機制的創新在於實現了“一次編寫,多處應用”,解決了目前在移動平臺之間程式碼移植困難的問題。在跨平臺核心中不斷擴充功能不影響裝置平臺介面卡模組,從而增強了擴充套件性,減少了移植工作。
3.1.2 跨平臺編碼的策略
跨平臺核心採用C++實現。C++適合iOS、Android、Windows Phone、Windows Desktop等作業系統。本文試驗總結了下列跨平臺編碼策略:
(1)使用標準C/C++函式庫(libc、libstdc++)進行數學計算、字串操作、動態記憶體管理,不使用C++異常型別(exception)以避免跨程式語言的異常未捕獲問題。
(2)避免使用各個平臺的保留關鍵字與已有名稱作為變數和函式名。例如,在iOS上不使用id作為變數名。為了在iOS上避免巨集定義、列舉定義及變數的同名衝突,不使用名稱空間來區分名稱,而是加上命名字首,對列舉值的名稱以k開頭。
(3)不使用複雜的多執行緒管理函式和鎖型別。將多執行緒管理放在裝置相關的模組中,少用全域性變數和共享資料。使用輕量級的原子計數加減函式[1]進行訪問保護。
(4)在iOS和Android上,使用泛型程式設計和標準模板庫(STL)的容器類,不使用專用的容器等資料結構型別。
(5)因為iOS和Android不支援寬字元(wchar_t)型別,字串預設採用UTF-8編碼,所以在跨平臺核心中採用UTF-8編碼、char*型別。
3.2 適應多種裝置的繪圖模型
3.2.1 移動平臺的異同分析
目前,主流的移動作業系統是iOS和Android。本文通過對這兩個平臺的特性分析得出下列與繪圖相關的結論:
(1)使用多點觸控手勢的互動方式。單指和雙指手勢具有普遍適用性,裝置平臺已提供這些手勢的識別器,不需要重新開發手勢識別器。
(2)介面主要採用二維圖形引擎顯示。圖形引擎內部可以使用OpenGL ES加速繪製,使用層(矩形紋理)進行顯示內容的快取和動畫合成。官方推薦採用離屏渲染技術加速繪圖,在層上快取渲染內容。
(3)互動式動態繪圖的重新整理方式主要有兩種:標記檢視的無效區域,依靠訊息機制延後重新整理內容;在後臺執行緒中預渲染,完成後直接提交到檢視的層中。
(4)與繪圖相關的裝置平臺差異,主要是使用了不同的程式語言、本地圖形庫、介面框架和訊息處理方式。檢視和層採用樹狀層次結構顯示內容和傳遞訊息事件。
3.2.2 跨平臺繪圖模型
根據iOS和Android的異同分析,本文提出了一種如圖3‑2所示的核心跨平臺的互動式繪圖模型。
本繪圖模型包含六大元素,具體描述如下。
(1)應用程式。整合繪圖平臺,定製介面佈局和效果,應用繪圖資料。
(2)檢視介面卡。使用介面框架實現佔位檢視,響應繪製和觸控訊息,將顯示請求和識別出的手勢委託核心檢視處理。允許核心以回撥方式重新整理內容。
(3)畫布介面卡。將畫布介面的顯示原語對映到特定圖形庫的繪製函式。
(4)核心檢視。管理圖形,將顯示和手勢請求轉發給互動命令和圖形實體。
(5)互動命令。將手勢請求轉換為繪圖步驟,實現互動邏輯。對顯示請求,以動態圖形回顯互動效果。擴充互動命令可實現更多繪圖功能。
(6)圖形實體。在核心中管理和顯示圖形資料,擴充型別實現更多繪圖功能。
本繪圖模型的關鍵設計點:抽象出畫布介面和檢視介面,採用介面卡模式解決平臺差異問題,基於委託模式實現圖形顯示和觸控互動功能的跨平臺。
3.3 總體設計
3.3.1 分層架構
為了降低模組耦合性、在不同的裝置平臺上提高可複用性,本論文的向量繪圖平臺(TouchVG)按圖3‑3所示的分層架構設計。總體上分為兩大層:跨平臺繪圖核心層和裝置平臺相關的介面封裝層。在跨平臺繪圖核心層中實現裝置平臺無關的功能。在介面封裝層中提供裝置平臺特有的通用功能,供頂層的各種應用程式使用。
對於iOS,介面封裝層採用ObjC和C++實現。對於Android,介面封裝層採用Java實現。在繪圖核心層與介面封裝層之間提供JNI封裝層,允許Android的Java程式碼與核心的C++程式碼相互呼叫。實現相互呼叫的關鍵技術是使用SWIG進行程式語言的轉換和整合。在Windows桌面應用中,可以使用C#和WPF開發介面封裝層及程式(也可以使用MFC和GDI+實現)。SWIG也應用在C++與C#之間的銜接轉換上。
TouchVG在各個作業系統上有相應的繪圖平臺,這些平臺有相同的核心。例如,圖3‑4所示的iOS和Android繪圖平臺。這些繪圖平臺都是本地應用形式,能實現更大程度的融合和效能優化。在繪圖核心中實現主要的繪圖功能,通過在不同平臺上編譯同一程式碼的方式適應多種平臺、避免重複開發、提高可複用性。
為iOS提供的繪圖平臺以靜態庫的形式供各種繪圖應用程式使用,如圖3‑4(a)所示。應用程式無需使用繪圖核心的介面,只需使用介面封裝層所提供的簡易介面,從而降低了應用開發的工作量。介面封裝層和應用程式都基於iOS平臺的SDK開發,可以實現統一的介面風格,降低實現難度。
為Android應用程式提供的繪圖平臺包含一個JAR包(核心JNI和介面封裝層的Java類)和一個繪圖核心的本地動態庫,如圖3‑4(b)所示。應用程式通過使用介面封裝層所提供的簡易介面降低了整合難度。介面封裝層和應用程式都基於Android 平臺的SDK開發,可以最大程度地利用平臺SDK的特性,實現緊密融合。
3.3.2 MVC架構
TouchVG平臺採用如圖3‑5所示的MVC架構模式設計動態行為。
(1)在繪圖檢視中響應重繪訊息,使用當前的繪圖上下文初始化畫布介面卡,將此畫布介面卡以抽象畫布介面物件傳入核心檢視,請求繪圖。
(2)核心檢視將靜態繪圖和動態繪圖的請求分別轉發給圖形實體和當前繪圖命令。圖形實體和繪圖命令呼叫畫布介面物件繪圖。在繪圖時畫布介面卡將被回撥。
(3)在繪圖檢視中響應觸控訊息,識別出手勢後委託核心檢視處理,由核心檢視轉發給當前繪圖命令進行互動式繪圖。
(4)繪圖命令根據觸控位置改變臨時圖形,呼叫抽象檢視介面的重繪函式觸發重繪訊息,在下次重繪時顯示該臨時圖形,從而實現動態繪圖。在一次觸控結束後改變圖形實體,呼叫檢視介面的重新生成函式,這樣就會顯示新的圖形實體內容。
3.3.3 系統組成
為各種iOS繪圖程式提供的TouchVG繪圖平臺以靜態庫的形式出現。為Android程式提供的TouchVG繪圖平臺包含JAR包和繪圖核心的本地動態庫。TouchVG繪圖平臺包含的模組如圖3‑6所示。其中,refine表示有某個類實現了指定的介面。
TouchVG有下列六個核心模組,除了GraphView模組外都是跨平臺的模組。
(1)GraphView:裝置平臺相關的介面卡模組,包含檢視介面卡和畫布介面卡。
(2)CoreView:核心檢視模組,為上層介面卡提供訪問介面,管理圖形資料,將顯示和手勢請求轉發給圖形實體或互動命令。
(3)Command:互動命令模組,實現選擇命令及多個繪圖命令。這些命令用於繪製和修改各種圖形,為上層介面提供動作訊息介面以便接受觸控手勢或滑鼠訊息資料。互動命令模組在內部將動作訊息分發給相應的命令物件,實現互動式繪圖。
(4)Shape:圖形實體模組,主要功能是常見圖形的儲存、渲染和互動計算。
(5)Graphics:圖形介面模組,實現顯示座標系變換、放縮平移計算、多種曲線形狀的顯示輸出。在圖形介面庫中不應用任何圖形庫,只是提供了適合多種平臺的畫布介面。由介面封裝層的畫布介面卡類使用某一種圖形庫來實現該畫布介面。
(6)Geometry:數學幾何模組,實現點、向量、變換矩陣、矩形框、基於Bezier的曲線、路徑、剪裁、最短距離等幾何計算功能及方程組求解等數學演算法。
其他模組或類:
(1)JsonStorage:用JSON實現的一種序列化類,實現了MgStorage介面。
(2)Test:跨平臺的單元測試模組,供裝置平臺的TestCanvas等程式使用。
3.3.4 程式碼目錄結構
TouchVG平臺的程式碼目錄結構如圖3‑7所示,各個資料夾節點的含義見表3‑1。
主目錄 |
資料夾名 |
含義 |
core |
callback |
畫布介面和檢視介面 |
view |
供檢視介面卡呼叫的核心檢視分發介面 |
|
test |
畫布介面的單元測試類 |
|
command |
互動命令模組 |
|
shape |
圖形實體模組 |
|
json |
JSON序列化器模組 |
|
graph |
圖形介面模組 |
|
geom |
數學幾何模組 |
|
ios |
view |
檢視介面卡和畫布介面卡 |
tests |
iOS繪圖測試程式 |
|
lib |
iOS繪圖平臺的靜態庫工程 |
|
android |
mk |
編譯指令碼 |
demo/jni |
本地動態庫的配置檔案和自動生成的原始檔 |
|
touchvg/jni |
SWIG生成的JNI類檔案 |
|
touchvg/view |
檢視介面卡和畫布介面卡 |
|
vgdemo |
Android繪圖測試類、測試程式 |
3.4 跨平臺繪圖核心的實現方式
本節描述與裝置平臺顯示相關的實現策略和跨平臺繪圖核心相關的實現方式,為在iOS和Android上實現了畫布介面卡和檢視介面卡設計介面。
3.4.1 向量圖形顯示的實現方式
核心跨平臺的向量圖形顯示方式如表3‑2所示。在裝置平臺上實現可自定義繪圖的檢視類(例如MyDeviceView),響應檢視的重繪訊息,將繪圖上下文資訊傳入畫布介面卡(例如CanvasAdapter)進行初始化,然後將此畫布介面卡傳入核心檢視(GiCoreView)請求繪圖,核心檢視再分發給相應的物件進行顯示。
在核心中呼叫抽象畫布介面(GiCanvas)的繪圖函式時,畫布介面卡的對應實現函式將自動使用特定的圖形庫完成繪製。因為裝置相關的檢視類與顯示哪些內容無關,所以在跨平臺核心中擴充圖形結構後不影響檢視類,實現了跨平臺繪圖。
3.4.2 互動式繪圖的實現方式
互動式繪圖在上述靜態圖形顯示的基礎上,根據多點觸控動作的位置資訊,動態改變和顯示圖形。如表3‑3所示,其執行步驟如下:
(1)在繪圖檢視類中識別出多點觸控手勢動作,委託核心分發手勢動作。在繪圖檢視類(例如MyDeviceView)中利用系統內建的手勢識別器從多點觸控資訊中識別出某種觸控手勢,然後轉換手勢動作引數並委託核心處理(第8~10行)。
(2)核心檢視(GiCoreView)將手勢動作分發給當前的繪圖命令(第21~27行),由繪圖命令根據觸控位置來改變臨時圖形的形狀。
(3)繪圖命令改變臨時圖形後,呼叫檢視介面(如表3‑4所示的GiView)的redraw函式,檢視介面卡自動設定重繪區域和觸發重繪訊息(第28、13~14行)。
(4)檢視類在重繪響應函式中委託核心顯示動態圖形(第1~4、18~19行),核心的繪圖命令將呼叫抽象畫布介面(GiCanvas)繪製動態圖形(例如拖曳效果)。
函式名稱 |
介面函式定義 |
對應的GiCoreView函式 |
重新構建顯示 |
void regenAll() |
void drawAll(GiCanvas& canvas) |
追加顯示新圖形 |
void regenAppend() |
bool drawAppend(GiCanvas& canvas) |
更新顯示 |
void redraw() |
void dynDraw(GiCanvas& canvas) |
根據iOS和Android的手勢識別特點(例如Android需要自行實現放縮和旋轉手勢的識別演算法),設計瞭如表3‑5所示的繪圖命令的手勢原語。其中,click、doubleClick、longPress是單指手勢,touchBegan、touchMoved、touchEnded是單指拖動手勢(使用最多,故分解為三個),twoFingersMove對應於雙指捏合、旋轉和拖動手勢(三種手勢合併為一個手勢原語是為了避免在裝置平臺識別具體手勢型別)。
手勢原語 |
介面函式定義 |
點選 |
bool click(const MgMotion* sender) |
雙擊 |
bool doubleClick(const MgMotion* sender) |
長按 |
bool longPress(const MgMotion* sender) |
開始拖動 |
bool touchBegan(const MgMotion* sender) |
正在拖動 |
bool touchMoved(const MgMotion* sender) |
拖動結束 |
bool touchEnded(const MgMotion* sender) |
滑鼠掠過 |
bool mouseHover(const MgMotion* sender) |
雙指觸控 |
bool twoFingersMove(const MgMotion* sender, int state, const Point2d& pt1, const Point2d& pt2) |
MgMotion包含當前觸點、起始觸點、觸控動作狀態和檢視GiView物件等資料,通過MgMotion的view成員變數將裝置平臺的檢視介面卡物件傳遞到核心。
3.4.3 畫布介面
TouchVG平臺參考HTML5 Canvas標準[2]設計了跨平臺的畫布介面,定義了表3‑6中所列出顯示原語,適合多數圖形庫。
(1)基於路徑的向量圖形顯示。
畫布介面支援子路徑,包含beginPath、moveTo、lineTo、bezierTo、quadTo、closePath及drawPath等7種顯示原語。設計要點:a、避免使用陣列、點等複合資料型別,沒有折線和多邊形等需要可變數量座標的繪圖函式,主要使用浮點數和整數等簡單型別,以免在JNI中生成複雜的引數型別。b、座標使用單精度浮點數而不是整數,與iPhone 4、iPad 3等高畫素密度(PPI)的螢幕相適應。
因為矩形、線段、圓、橢圓是較簡單且經常使用的圖形,大多數圖形庫都提供了這些圖形的繪製函式,所以在畫布介面中提供了drawRect、drawLine、drawEllipse繪圖函式。其他向量圖形可以用這些路徑顯示原語表示。例如使用三次貝塞爾曲線表示任意角度的圓弧、四段貝塞爾曲線表示橢圓、將B樣條曲線和三次引數樣條曲線分解為連續的三次貝塞爾曲線。
顯示原語 |
介面函式定義 |
設定畫筆 |
void setPen(int argb, float width, int style, float phase) |
設定畫刷 |
void setBrush(int argb, int style) |
清除區域 |
void clearRect(float x, float y, float w, float h) |
顯示矩形 |
void drawRect(float x, float y, float w, float h, bool stroke, bool fill) |
顯示橢圓 |
void drawEllipse(float x, float y, float w, float h, bool stroke, bool fill) |
顯示線段 |
void drawLine(float x1, float y1, float x2, float y2) |
開始新的路徑 |
void beginPath() |
新增子路徑 |
void moveTo(float x, float y) |
新增線段 |
void lineTo(float x, float y) |
新增貝塞爾曲線 |
void bezierTo(float x1, float y1, float x2, float y2, float x, float y) |
新增拋物線 |
void quadTo(float cpx, float cpy, float x, float y) |
閉合當前路徑 |
void closePath() |
繪製路徑 |
void drawPath(bool stroke, bool fill) |
儲存剪裁區域 |
void saveClip() |
恢復剪裁區域 |
void restoreClip() |
矩形作為路徑 |
bool clipRect(float x, float y, float w, float h) |
區域作為路徑 |
bool clipPath() |
顯示控制點 |
void drawHandle(float x, float y, int type) |
顯示影象 |
void drawBitmap(const char* name, float xc, float yc, float w, float h, float angle) |
單行文字 |
float drawTextAt(const char* text, float x, float y, float h, int align) |
(2)設定畫筆和畫刷。
包含setPen和setBrush設定函式。設計要點:a、顏色值使用整數(int argb),在Android上可直接應用該顏色值,在iOS等平臺上按位元組順序提取各個顏色分量並轉換為平臺特有的顏色型別。b、在跨平臺核心的GiGraphics類中儲存線寬等繪圖引數,自動判斷和呼叫畫布介面的setPen和setBrush函式,避免在各個裝置平臺的畫布介面卡中重複實現該功能。
(3)圖形剪裁。
使用圖形庫的剪裁功能,包含saveClip、restoreClip、clipRect、clipPath函式。
(4)顯示影象。
為了簡化實現、隔離平臺的差異性,影象在裝置平臺中管理,在畫布介面中只使用名稱來標識影象物件。drawHandle(x, y, type)顯示原始大小的控制點圖示等點狀影象,drawBitmap(name, xc, yc, w, h, angle)在一個矩形框內顯示影象。對於drawBitmap,使用實際寬高表達影象放縮資訊、角度表達影象旋轉資訊、影象的中心位置表達位移資訊,這三者共同表達影象的矩陣變換資訊,以任意角度和大小向量化顯示影象。
3.4.4 互動式繪圖命令
在跨平臺核心中使用命令模式和模板方法模式設計互動命令體系結構,如圖3‑8所示,每個互動式繪圖命令都對應於一個命令類,支援表3‑5所示的手勢原語介面。每個命令類對應有一個唯一標識的命令名稱,所有命令物件都在MgCmdManager命令管理器類中快取。
核心中提供常用的基本繪圖命令,外界模組可以實現更多的互動命令類,將命令名稱和類工廠函式登記到命令管理器,在實際使用該命令時才建立命令物件。
在裝置平臺相關的模組中可以不直接訪問互動命令,通過傳遞命令名稱啟動相應的互動命令(例如view.commandName=”circle”),由GiCoreView類將顯示請求和手勢動作轉發給當前命令。
3.4.5 向量圖形的仿射變換
TouchVG平臺採用二維變換矩陣實現向量圖形的仿射變換(例如無級放縮顯示、平移顯示、旋轉變形),使用三種座標系:模型座標系、世界座標系和顯示座標系。其中,模型座標系和顯示座標系都參照世界座標系使用一個變換矩陣表示,分別記為M和D。模型座標系到顯示座標系的變換矩陣為M×D-1。顯示座標系的變換矩陣計算涉及下列變數:
(1)檢視中心點的世界座標(xc,yc),單位為毫米。
(2)檢視顯示比例(scale)。為1.0時表示按世界座標系等比顯示。
(3)檢視的寬度(width)和高度(height),單位為點,與裝置相關。
(4)檢視顯示時每英寸的點數(dpi),與裝置相關。
按如下式(3-1)計算顯示座標系相對於世界座標系的變換矩陣:
3.5 本章小結
本章提出了一種適合多種移動平臺的基於虛擬函式多型行為的跨平臺擴充套件機制,將主要功能在跨平臺核心中使用C++實現,在不同的移動平臺上開發介面卡模組,易於擴充套件和實現。在Android上使用SWIG實現C++與Java的呼叫和擴充套件。
通過對移動平臺的差異分析,總結了iOS和Android在螢幕顯示和觸控互動方式等方面的特點。提出了一種跨平臺繪圖模型,設計了跨平臺互動式繪圖核心,關鍵點是抽象出畫布介面和檢視介面,採用介面卡模式解決平臺差異問題,基於委託模式實現圖形渲染和觸控操作功能,具備跨平臺特性。
TouchVG平臺採用分層和MVC架構,主體功能在跨平臺核心中實現。為移動平臺上的介面卡實現工作描述了關鍵實現方式、畫布介面和檢視介面的設計意圖。
[1] 採用條件編譯方式實現原子計數加減函式,在Android上使用 __sync_add_and_fetch和 __sync_sub_and_fetch函式,在iOS上使用OSAtomicIncrement32和OSAtomicDecrement32函式。
[2] Canvas 2D Context:http://dev.w3.org/html5/2dcontext/ 。