iOS核心動畫高級技術(十三) 高效繪圖

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity. 不必要的效率考慮往往是性能問題的萬惡之源。 ——William Allan Wulf

#軟件繪圖 術語繪圖通常在Core Animation的上下文中指代軟件繪圖(意即:不由GPU協助的繪圖)。在iOS中,軟件繪圖通常是由Core Graphics框架完成來完成。但是,在 一些必要的情況下,相比Core Animation和OpenGL,Core Graphics要慢了不少。

軟件繪圖不僅效率低,還會消耗可觀的內存。 CALayer 只需要一些與自己相關 的內存:只有它的寄宿圖會消耗一定的內存空間。即使直接賦給 contents 屬性一張圖片,也不需要增加額外的照片存儲大小。如果相同的一張圖片被多個圖層作為 contents 屬性,那么他們將會共用同一塊內存,而不是復制內存塊。

但是一旦你實現了CALayerDelegate 協議中的 -drawLayer:inContext:方 法或者 UIView 中的-drawRect:方法(其實就是前者的包裝方法),圖層就創 建了一個繪制上下文,這個上下文需要的大小的內存可從這個算式得出:圖層寬*圖層高*4字節,寬高的單位均為像素。對于一個在Retina iPad上的全屏圖層來說,這 個內存量就是 204815264字節,相當于12MB內存,圖層每次重繪的時候都需要 重新抹掉內存然后重新分配。

軟件繪圖的代價昂貴,除非絕對必要,你應該避免重繪你的視圖。提高繪制性能的秘訣就在于盡量避免去繪制。 #矢量圖形 我們用Core Graphics來繪圖的一個通常原因就是只是用圖片或是圖層效果不能輕易地繪制出矢量圖形。矢量繪圖包含一下這些:

  • 任意多邊形(不僅僅是一個矩形)
  • 斜線或曲線
  • 文本
  • 漸變 舉個例子,清單13.1 展示了一個基本的畫線應用。這個應用將用戶的觸摸手勢轉 換成一個UIBezierPath上的點,然后繪制成視圖。我們在一個 UIView 子類DrawingView中實現了所有的繪制邏輯,這個情況下我們沒有用上view controller。但是如果你喜歡你可以在view controller中實現觸摸事件處理。圖13.1 是代碼運行結果。
#import "DrawingView.h"
@interface DrawingView ()
@property (nonatomic, strong) UIBezierPath *path; 
@end
@implementation DrawingView
- (void)awakeFromNib {//create a mutable pathself.path = [[UIBezierPath alloc] init]; //終點處理self.path.lineJoinStyle = kCGLineJoinRound; //線條拐角self.path.lineCapStyle = kCGLineCapRound;self.path.lineWidth = 5; 
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get the starting pointCGPoint point = [[touches anyObject] locationInView:self];//move the path drawing cursor to the starting point[self.path moveToPoint:point]; 
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {//get the current pointCGPoint point = [[touches anyObject] locationInView:self]; //add a new line segment to our path[self.path addLineToPoint:point];//redraw the view[self setNeedsDisplay]; 
}
- (void)drawRect:(CGRect)rect {//draw path[[UIColor clearColor] setFill]; [[UIColor redColor] setStroke];//使用當前繪圖屬性在接收器的路徑上繪制一條直線。//繪制的線以路徑為中心,其邊平行于路徑段。此方法將當前繪圖屬性應用于所呈現的路徑。//此方法在繪圖前自動保存當前圖形狀態并在完成時恢復該狀態,因此您不必自行保存圖形狀態。[self.path stroke];
}
@end
復制代碼

這樣實現的問題在于,我們畫得越多,程序就會越慢。因為每次移動手指的時候都會重繪整個貝塞爾路徑( UIBezierPath ),隨著路徑越來越復雜,每次重繪的工作就會增加,直接導致了幀數的下降。看來我們需要一個更好的方法了。

Core Animation為這些圖形類型的繪制提供了專門的類,并給他們提供硬件支持 (第六章『專有圖層』有詳細提到)。CAShapeLayer可以繪制多邊形,直線和曲線。 CATextLayer 可以繪制文本。CAGradientLayer用來繪制漸變。這些總體上都比Core Graphics更快,同時他們也避免了創造一個寄宿圖。

如果稍微將之前的代碼變動一下,用 CAShapeLayer 替代Core Graphics,性能就會得到提高(見清單13.2).雖然隨著路徑復雜性的增加,繪制性能依然會下降, 但是只有當非常非常浮躁的繪制時才會感到明顯的幀率差異。

清單13.2 用 CAShapeLayer 重新實現繪圖應用

#import "DrawingView.h"
#import <QuartzCore/QuartzCore.h>
@interface DrawingView ()
@property (nonatomic, strong) UIBezierPath *path; 
@end
@implementation DrawingView
+ (Class)layerClass {//this makes our view create a CAShapeLayer //instead of a CALayer for its backing layer return [CAShapeLayer class];
}
- (void)awakeFromNib {//create a mutable pathself.path = [[UIBezierPath alloc] init];//configure the layerCAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer; shapeLayer.strokeColor = [UIColor redColor].CGColor; shapeLayer.fillColor = [UIColor clearColor].CGColor; shapeLayer.lineJoin = kCALineJoinRound; shapeLayer.lineCap = kCALineCapRound; shapeLayer.lineWidth = 5;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get the starting pointCGPoint point = [[touches anyObject] locationInView:self];//move the path drawing cursor to the starting point[self.path moveToPoint:point]; 
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {//get the current pointCGPoint point = [[touches anyObject] locationInView:self]; //add a new line segment to our path[self.path addLineToPoint:point];//update the layer with a copy of the path((CAShapeLayer *)self.layer).path = self.path.CGPath;
}
@end
復制代碼

#臟矩形 有時候用 CAShapeLayer 或者其他矢量圖形圖層替代Core Graphics并不是那么切實可行。比如我們的繪圖應用:我們用線條完美地完成了矢量繪制。但是設想一 下如果我們能進一步提高應用的性能,讓它就像一個黑板一樣工作,然后用『粉 筆』來繪制線條。模擬粉筆最簡單的方法就是用一個『線刷』圖片然后將它粘貼到用戶手指碰觸的地方,但是這個方法用 CAShapeLayer 沒辦法實現。

我們可以給每個『線刷』創建一個獨立的圖層,但是實現起來有很大的問題。屏幕上允許同時出現圖層上線數量大約是幾百,那樣我們很快就會超出的。這種情況 下我們沒什么辦法,就用Core Graphics吧(除非你想用OpenGL做一些更復雜的事 情)。 我們的『黑板』應用的最初實現見清單13.3,我們更改了之前版本的 DrawingView ,用一個畫刷位置的數組代替 UIBezierPath 。圖13.2是運行結果

清單13.3 簡單的類似黑板的應用

#import "DrawingView.h"
#import <QuartzCore/QuartzCore.h>
#define BRUSH_SIZE 32
@interface DrawingView ()
@property (nonatomic, strong) NSMutableArray *strokes; 
@end
@implementation DrawingView
- (void)awakeFromNib {//create arrayself.strokes = [NSMutableArray array];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get the starting pointCGPoint point = [[touches anyObject] locationInView:self];//add brush stroke[self addBrushStrokeAtPoint:point]; 
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {//get the touch pointCGPoint point = [[touches anyObject] locationInView:self];//add brush stroke[self addBrushStrokeAtPoint:point]; 
}
- (void)addBrushStrokeAtPoint:(CGPoint)point {//add brush stroke to array[self.strokes addObject:[NSValue valueWithCGPoint:point]];//needs redraw[self setNeedsDisplay]; 
}
- (void)drawRect:(CGRect)rect {//redraw strokesfor (NSValue *value in self.strokes) {//get pointCGPoint point = [value CGPointValue];//get brush rectCGRect brushRect = CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE); [[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect];}}
@end
復制代碼

這個實現在模擬器上表現還不錯,但是在真實設備上就沒那么好了。問題在于每次手指移動的時候我們就會重繪之前的線刷,即使場景的大部分并沒有改變。我們 繪制地越多,就會越慢。隨著時間的增加每次重繪需要更多的時間,幀數也會下降 (見圖13.3),如何提高性能呢?
為了減少不必要的繪制,Mac OS和iOS設備將會把屏幕區分為需要重繪的區域和不需要重繪的區域。那些需要重繪的部分被稱作『臟區域』。在實際應用中,鑒于非矩形區域邊界裁剪和混合的復雜性,通常會區分出包含指定視圖的矩形位置,而這個位置就是『臟矩形』。

當一個視圖被改動過了,TA可能需要重繪。但是很多情況下,只是這個視圖的一部分被改變了,所以重繪整個寄宿圖就太浪費了。但是Core Animation通常并不了解你的自定義繪圖代碼,它也不能自己計算出臟區域的位置。然而,你的確可以提供這些信息。

當你檢測到指定視圖或圖層的指定部分需要被重繪,你直接調用 - setNeedsDisplayInRect:來標記它,然后將影響到的矩形作為參數傳入。這樣就會在一次視圖刷新時調用視圖的 -drawRect: (或圖層代理的 - drawLayer:inContext: 方法)。

傳入 -drawLayer:inContext:CGContext 參數會自動被裁切以適應對應的矩形。為了確定矩形的尺寸大小,你可以用CGContextGetClipBoundingBox()方法來從上下文獲得大小。調用 -drawRect:會更簡單,因為CGRect會作為參數直接傳入。

你應該將你的繪制工作限制在這個矩形中。任何在此區域之外的繪制都將被自動無視,但是這樣CPU花在計算和拋棄上的時間就浪費了,實在是太不值得了。

相比依賴于Core Graphics為你重繪,裁剪出自己的繪制區域可能會讓你避免不必要的操作。那就是說,如果你的裁剪邏輯相當復雜,那還是讓Core Graphics來 代勞吧,記住:當你能高效完成的時候才這樣做。

清單13.4 展示了一個 -addBrushStrokeAtPoint: 方法的升級版,它只重繪當前線刷的附近區域。另外也會刷新之前線刷的附近區域,我們也可以用 CGRectIntersectsRect() 來避免重繪任何舊的線刷以不至于覆蓋已更新過的區域。這樣做會顯著地提高繪制效率(見圖13.4)

清單13.4 用 -setNeedsDisplayInRect: 來減少不必要的繪制

- (void)addBrushStrokeAtPoint:(CGPoint)point {//add brush stroke to array[self.strokes addObject:[NSValue valueWithCGPoint:point]];//set dirty rect[self setNeedsDisplayInRect:[self brushRectForPoint:point]]; 
}
- (CGRect)brushRectForPoint:(CGPoint)point {return CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE);
}
- (void)drawRect:(CGRect)rect {//redraw strokesfor (NSValue *value in self.strokes) {//get pointCGPoint point = [value CGPointValue];//get brush rectCGRect brushRect = [self brushRectForPoint:point];//only draw brush stroke if it intersects dirty rectif (CGRectIntersectsRect(rect, brushRect)) {//draw brush stroke[[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect];}} 
}
復制代碼

#異步繪制 UIKit的單線程天性意味著寄宿圖通暢要在主線程上更新,這意味著繪制會打斷用戶交互,甚至讓整個app看起來處于無響應狀態。我們對此無能為力,但是如果能避免用戶等待繪制完成就好多了。

針對這個問題,有一些方法可以用到:一些情況下,我們可以推測性地提前在另外一個線程上繪制內容,然后將由此繪出的圖片直接設置為圖層的內容。這實現起 來可能不是很方便,但是在特定情況下是可行的。Core Animation提供了一些選 擇: CATiledLayerdrawsAsynchronously 屬性。 #CATiledLayer 我們在第六章簡單探索了一下CATiledLayer。除了將圖層再次分割成獨立更新的小塊(類似于臟矩形自動更新的概念), CATiledLayer還有一個有趣的特性:在多個線程中為每個小塊同時調用-drawLayer:inContext:方法。這就避免了阻塞用戶交互而且能夠利用多核心新片來更快地繪制。只有一個小塊的 CATiledLayer 是實現異步更新圖片視圖的簡單方法。 #drawsAsynchronously iOS 6中,蘋果為 引入了這個令人好奇的屬性,drawsAsynchronously屬性對傳入 -drawLayer:inContext: 的 CGContext進行改動,允許CGContext延緩繪制命令的執行以至于不阻塞用戶交互。

它與CATiledLayer使用的異步繪制并不相同。它自己的-drawLayer:inContext:方法只會在主線程調用,但是CGContext并不等待每個繪制命令的結束。相反地,它會將命令加入隊列,當方法返回時,在后臺線程逐個執行真正的繪制。

根據蘋果的說法。這個特性在需要頻繁重繪的視圖上效果最好(比如我們的繪圖應用,或者諸如 UITableViewCell 之類的),對那些只繪制一次或很少重繪的圖 層內容來說沒什么太大的幫助。

#總結 本章我們主要圍繞用Core Graphics軟件繪制討論了一些性能挑戰,然后探索了一些改進方法:比如提高繪制性能或者減少需要繪制的數量。 第14章,『圖像IO』,我們將討論圖片的載入性能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278901.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278901.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278901.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

dropbox鏈接過期_詢問操作方法:“開始”菜單中的Dropbox,了解符號鏈接和翻錄TV系列DVD...

dropbox鏈接過期This week we take a look at how to incorporate Dropbox into your Windows Start Menu, understanding and using symbolic links, and how to rip your TV series DVDs right to unique and high-quality episode files. 本周&#xff0c;我們來看看如何將D…

android listpreference 自定義,Android – 我的ListPreference中的自定義行布局

在我的Android應用程序中,我實現了從ListPreference擴展的類SubtitleColorListPreference.我需要這個,因為我需要為列表中的每個項目設置自己的布局.一切正常,它看起來像這樣&#xff1a;重要的代碼是onPrepareDialogBu??ilder(AlertDialog.Builder builder)中的方法,我在其中…

springMVC3學習(十一)--文件上傳CommonsMultipartFile

版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主同意不得轉載。 https://blog.csdn.net/itmyhome/article/details/27976873 使用springMVC提供的CommonsMultipartFile類進行讀取文件須要用到上傳文件的兩個jar包 commons-logging.jar、commons-io-xxx.jar1、在sp…

基于React和SpringBoot的快速開發模板QuickAdmin

經過一段時間的總結和完善&#xff0c;我的管理系統快速開發模板已經基本成型&#xff0c;現在GitHub上開源啦&#xff1a; QuickAdmin QuickAdmin是基于Spring Boot和React.js實現的管理系統開發框架。用于開發網站的后臺管理系統。 本框架提供了如下功能&#xff1a; 完整的基…

android sim iso,android – 意外的telephonyManager.getSimCountryIso()行為

您可以使用MCC MNC獲取SIM卡國家/地區,它是SIM配置的,與您所在的網絡無關.Configuration config getResources().getConfiguration();int countryCode config.mcc;您可以在此處找到MCC列表MccTable.java例如,西班牙是214,法國是208MCC should work on all GSM devices with S…

火狐 增強查找工具欄_在“提示”框中:簡單的IE至Firefox同步,輕松的Windows工具欄和識別USB電纜...

火狐 增強查找工具欄() Every week we tip into our mail bag and share great tips from your fellow readers. This week we’re looking at an easy way to sync your bookmarks between IE and Firefox, using simple Windows toolbars, and a clever way to ID USB cables…

day22 模塊-collections,time,random,pickle,shelve等

一、引入模塊的方式: 1. 認識模塊 模塊可以認為是一個py文件. 模塊實際上是我們的py文件運行后的名稱空間 導入模塊: 1. 判斷sys.modules中是否已經導入過該模塊 2. 開辟一個內存 3. 在這個內存中執行該py文件 4. 給這個內存起個名字&#xff0c; 一般用的是py文件的名字。返回…

基于Redis實現分布式鎖,避免重復執行定時任務

Spring提供了定時任務的功能&#xff0c;但是在多個實例的集群中&#xff0c;會出現定時任務重復執行多次的情況。 使用Qutaz框架自帶的分布式定時任務可以很好的解決這個問題&#xff0c;但是講道理功能有些過于強大&#xff0c;對于需求不高&#xff0c;乃至可以一定程度上允…

Input Director使用一個鍵盤和鼠標即可控制多臺Windows計算機

The problem is having two or more PC’s and having to go back and forth between workstation. Input Director solves the problem by allowing you to control multiple Windows systems with only one keyboard and mouse on the Master PC. 問題是擁有兩臺或更多臺PC…

viper4android 生效,另一種讓V4a音效在Poweramp上生效的方法

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓本人按照網上的方法進Poweramp設置—音頻—高級選項—直接音量控制—不打勾后 V4a音效沒有生效 我又把V4a音效兼容模式里的正常模式切換成為兼容模式 結果還是不行......后來我郁悶了三天三夜有一天我去了v4a官網論壇看到了admin帖…

[學習筆記]狀壓dp

狀壓 \(dp\) 1、[SDOI2009]Bill的挑戰 \(f[i][j]\) 表示匹配到字符串的第 \(i\) 位狀態為 \(j\) 的方案數 那么方程就很明顯了&#xff0c;每次枚舉第 \(i\) 位的字母 \(alpha\) 然后 \(O(n)\) 判斷就好了 時間復雜度 \(O(26Tlen2^nn)\) \(Code\ Below:\) #include <bits/st…

excel導入csv文件_如何將包含以0開頭的列的CSV文件導入Excel

excel導入csv文件Microsoft Excel will automatically convert data columns into the format that it thinks is best when opening comma-separated data files. For those of us that don’t want our data changed, we can change that behavior. Microsoft Excel將在打開…

MySQL之進化篇

MySQL之實用篇 MySQL之牛刀小試 子查詢是指出現在其他SQL語句內的SELECT子句. 例如: SELECT * FROM t1 WHERE column1 (SELECT column2 FROM t2) 其中 SELECT * FRIN t1 稱為outerQuery SELECT column2 FROM t2 稱為subQuery 注意:子查詢指嵌套在查詢內部,且必須始終出現在圓括…

android 9.0新ui,SystemUI分析(Android9.0)

8種機械鍵盤軸體對比本人程序員&#xff0c;要買一個寫代碼的鍵盤&#xff0c;請問紅軸和茶軸怎么選&#xff1f;一、SystemUI組成SystemUI是Android的系統界面&#xff0c;包括狀態欄statusbar、鎖屏keyboard、任務列表recents等等&#xff0c;都繼承于SystemUI這個類&#xf…

WMI技術介紹和應用——WMI概述

https://blog.csdn.net/breaksoftware/article/details/8424317轉載于:https://www.cnblogs.com/diyunpeng/p/9982885.html

解決App啟動時白屏的問題

第一次 03-25 11:02:34.431 6908-6908/com.newenergyjinfu.jytz D/App: before_onCreate: 239 03-25 11:02:34.513 6908-6908/com.newenergyjinfu.jytz D/App: after_initOkGo( initPicasso): 316 03-25 11:02:34.570 6908-6908/com.newenergyjinfu.jytz D/App: after_ J…

chromebook刷機_如何為不支持Chrome操作系統的網站欺騙Chromebook用戶代理

chromebook刷機Not all browsers handle websites the same, and if they don’t support your operating system or browser, you could be denied access. Luckily, you can spoof the user agent on Chrome OS to make it look like you use a completely different system.…

什么時候可以升級HarmonyOS,華為鴻蒙OS即將迎來升級 手機版本或仍需時間

原標題&#xff1a;華為鴻蒙OS即將迎來升級 手機版本或仍需時間在2019年的華為開發者大會上&#xff0c;華為消費者業務CEO余承東正式對外發布了HarmonyOS。時隔一年后&#xff0c;華為開發者大會2020即將拉開帷幕。此次大會&#xff0c;HarmonyOS無疑仍會是重頭戲之一&#xf…

Shell_mysql命令以及將數據導入Mysql數據庫

連接MYSQL數據庫 mysql -h${db_ip} -u${db_user} -p${db_pawd} -P${db_port} -D${db_name} -s -e "${sql}" db_ip&#xff1a;主機地址 db_user &#xff1a;數據庫用戶名 db_pwd&#xff1a;密碼 db_port&#xff1a;端口號 db_name&#xff1a;數據庫名稱 sql&…

cocos android-1,cocos2dx在windows下開發,編譯到android上(1)

轉自&#xff1a;http://www.2cto.com/kf/201205/130697.html下面我給大家介紹下&#xff0c;用vs2010開發cocos2dx&#xff0c;然后如何使其編譯到android上。步驟如下&#xff1a;1、必要條件&#xff0c;你的eclipse能把代碼編譯到安卓手機或虛擬機上&#xff0c;如果這一步…