1. 程式人生 > >臟矩形(高效繪圖 13.3)

臟矩形(高效繪圖 13.3)

event 技術 chap def only @property rop context smo

臟矩形

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

我們可以給每個『線刷』創建一個獨立的圖層,但是實現起來有很大的問題。屏幕上允許同時出現圖層上線數量大約是幾百,那樣我們很快就會超出的。這種情況下我們沒什麽辦法,就用Core Graphics吧(除非你想用OpenGL做一些更復雜的事情)。

我們的『黑板』應用的最初實現見清單13.3,我們更改了之前版本的DrawingView,用一個畫刷位置的數組代替UIBezierPath。圖13.2是運行結果

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

技術分享
 1 #import "DrawingView.h"
 2 #import 
 3 #define BRUSH_SIZE 32
 4 
 5 @interface DrawingView ()
 6 
 7 @property (nonatomic, strong) NSMutableArray *strokes;
 8 
 9 @end
10 
11 @implementation DrawingView
12 13 - (void)awakeFromNib 14 { 15 //create array 16 self.strokes = [NSMutableArray array]; 17 } 18 19 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 20 { 21 //get the starting point 22 CGPoint point = [[touches anyObject] locationInView:self]; 23 24 //add brush stroke
25 [self addBrushStrokeAtPoint:point]; 26 } 27 28 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 29 { 30 //get the touch point 31 CGPoint point = [[touches anyObject] locationInView:self]; 32 33 //add brush stroke 34 [self addBrushStrokeAtPoint:point]; 35 } 36 37 - (void)addBrushStrokeAtPoint:(CGPoint)point 38 { 39 //add brush stroke to array 40 [self.strokes addObject:[NSValue valueWithCGPoint:point]]; 41 42 //needs redraw 43 [self setNeedsDisplay]; 44 } 45 46 - (void)drawRect:(CGRect)rect 47 { 48 //redraw strokes 49 for (NSValue *value in self.strokes) { 50 //get point 51 CGPoint point = [value CGPointValue]; 52 53 //get brush rect 54 CGRect brushRect = CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE); 55 56 //draw brush stroke ? 57 [[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect]; 58 } 59 } 60 @end
View Code

技術分享

圖13.2 用程序繪制一個簡單的『素描』

這個實現在模擬器上表現還不錯,但是在真實設備上就沒那麽好了。問題在於每次手指移動的時候我們就會重繪之前的線刷,即使場景的大部分並沒有改變。我們繪制地越多,就會越慢。隨著時間的增加每次重繪需要更多的時間,幀數也會下降(見圖13.3),如何提高性能呢?

技術分享

圖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:來減少不必要的繪制

技術分享
 1 - (void)addBrushStrokeAtPoint:(CGPoint)point
 2 {
 3     //add brush stroke to array
 4     [self.strokes addObject:[NSValue valueWithCGPoint:point]];
 5 
 6     //set dirty rect
 7     [self setNeedsDisplayInRect:[self brushRectForPoint:point]];
 8 }
 9 
10 - (CGRect)brushRectForPoint:(CGPoint)point
11 {
12     return CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE);
13 }
14 
15 - (void)drawRect:(CGRect)rect
16 {
17     //redraw strokes
18     for (NSValue *value in self.strokes) {
19         //get point
20         CGPoint point = [value CGPointValue];
21 
22         //get brush rect
23         CGRect brushRect = [self brushRectForPoint:point];
24         ?
25         //only draw brush stroke if it intersects dirty rect
26         if (CGRectIntersectsRect(rect, brushRect)) {
27             //draw brush stroke
28             [[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect];
29         }
30     }
31 }
View Code

臟矩形(高效繪圖 13.3)