Quarz 2D是一個二維繪畫引擎,同時支持ios和mac,其API是Core Graphics框架的,是純C語言的。IOS系統提供的大部分控件的內容都是通過Quartz 2D畫出來的,因此Quartz 2D的一個很重要的價值是:自定義view(自定義UI控件)。
圖形上下文(Graphics context)是一個CGContextRef數據,其作用是:
保存繪圖信息、繪圖屬性 繪制目標圖案 輸出繪制好的圖案到輸出目標去,即渲染到什麼地方去(可以是PDF文件、bitmap或者顯示器的窗口上)在view中實現- (void)drawRect:(CGRect)rect方法,然後在方法中:
1. 取得跟當前view相關聯的圖形上下文;
2. 繪制相應的圖形內容
3. 利用圖形上下文將繪制好的圖形內容渲染顯示到view上面
//繪制步驟:1、獲取當前視圖的圖形上下文 2、開始繪圖 3、渲染繪制內容
/**繪制直線**/
//1、獲取當前視圖的圖形上下文(圖形上下文決定繪制的輸出目標)
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2、開始繪圖
//設置起點
CGContextMoveToPoint(ctx, 20, 50);
//設置終點
CGContextAddLineToPoint(ctx, 300, 50);
//設置線條屬性
//設置顏色
CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
//另外一種設置顏色的方式
// [[UIColor purpleColor] set];
//設置線條寬度
CGContextSetLineWidth(ctx, 10);
//設置線條起點和終點的樣式為圓角
CGContextSetLineCap(ctx, kCGLineCapRound);
//3、將畫布上繪制的內容渲染到view的layer上
CGContextStrokePath(ctx);
/**繪制三角形**/
//設置起點
CGContextMoveToPoint(ctx, 150, 80);
//設置第一個拐點
CGContextAddLineToPoint(ctx, 220, 150);
//設置第二個拐點
CGContextAddLineToPoint(ctx, 80, 150);
//設置第三個點(終點)
// CGContextAddLineToPoint(ctx, 150, 80);
//可以用下面方法代替 縫合起點和終點
CGContextClosePath(ctx);
//設置線條的拐點轉角樣式為圓角
CGContextSetLineJoin(ctx, kCGLineJoinRound);
//渲染
CGContextStrokePath(ctx);
/**繪制空心四邊形**/
CGContextAddRect(ctx, CGRectMake(40, 200, 200, 100));
//設置空心(線條)顏色
// CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
//也可以這樣設置顏色
[[UIColor lightGrayColor] setStroke];
//設置線條寬度
CGContextSetLineWidth(ctx, 10);
//渲染(空心的)
CGContextStrokePath(ctx);
/**繪制實心四邊形**/
CGContextAddRect(ctx, CGRectMake(40, 320, 200, 100));
//設置實心顏色
// CGContextSetFillColorWithColor(ctx, [UIColor orangeColor].CGColor);
[[UIColor orangeColor] setFill];
//渲染(實心的)
CGContextFillPath(ctx);
/**繪制圓(可以用繪制橢圓的方式畫圓)**/
//參數依次為圓心x,圓心y,半徑,開始位置的弧度,結束位置的弧度,繪制路徑(1為順時針,0為逆時針)
//由於Quartz2D的坐標系是x軸向右,y軸向上,不同於UIKit坐標系。因此在不將Quartz2D坐標系翻轉的情況下,畫出來的圖形是與原圖形關於x軸對稱的。
CGContextAddArc(ctx, 100, 520, 50, 0, M_PI_2, 1);
CGContextStrokePath(ctx); //空心
// CGContextFillPath(ctx); //實心
/**繪制橢圓**/
CGContextAddEllipseInRect(ctx, CGRectMake(230, 400, 120, 200));
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextStrokePath(ctx);
/**繪制弧線**/
// CGContextAddArcToPoint(ctx, 100, 200, 100, 200, 50);
CGContextAddArc(ctx, 200, 200, 100, 0, M_PI, 1);
CGContextSetStrokeColorWithColor(ctx, [UIColor cyanColor].CGColor);
CGContextStrokePath(ct
舉個例子,假如要繪制兩條線,一條紅色一條默認的黑色。先繪制紅色線,繪制完畢渲染上去後,再去繪制第二條。繪制第二條的時候如果不重新設置繪畫顏色,那麼繪制出來的線條也是紅色的。也就是說,繪制屬性如果不對其清空(即重新設置)是默認保留在圖形上下文上的。因此可以這樣理解:
一個圖形上下文有3塊區域,分別是繪制屬性,圖像信息,繪制區域:
前面說過如果要繪制多個不同屬性的圖形,那麼每次渲染好一個圖形後就要重新設置繪制屬性,通常繪制多個圖形都是這樣做的。有時候可能用到一個簡單的方法:即在繪制一個圖形前先保存當前圖形上下文中的繪制屬性,這個繪制屬性會被保存到圖形上下文棧上,如果下次需要繪制同樣屬性的圖形,直接把這個繪制屬性從棧頂取出來(恢復)就好了。需要注意的是保存一次只能取一次,可以保存多次,但是每次只從棧頂取。
/**保存繪制屬性(以繪制3條線為例,第一條第三條屬性一致)**/
//第一條
CGContextMoveToPoint(ctx, 20, 300);
CGContextAddLineToPoint(ctx, 200,300);
//設置繪制屬性
CGContextSetLineWidth(ctx, 10);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor);
CGContextStrokePath(ctx);
//第二條線
//先保存當前的繪制屬性
CGContextSaveGState(ctx);
CGContextMoveToPoint(ctx, 20, 400);
CGContextAddLineToPoint(ctx, 200, 400);
//設置新的繪制屬性
CGContextSetLineWidth(ctx, 5);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextStrokePath(ctx);
//第三條線
//取出(恢復)之前保存的繪制屬性
CGContextRestoreGState(ctx);
CGContextMoveToPoint(ctx, 20, 500);
CGContextAddLineToPoint(ctx, 200, 500);
CGContextStrokePath(ctx);
矩陣操作主要有旋轉操作、縮放操作、平移操作,是以視圖左上角為原點進行的。對矩陣的操作一定要在繪制之前完成,不然繪制完了再操作無效。
CGContextRef ctx = UIGraphicsGetCurrentContext();
//矩陣旋轉45度(參數為圖形上下文、旋轉角度)是以左上角為旋轉點的
//設置矩陣操作要在繪制前完成
// CGContextRotateCTM(ctx, M_PI_4);
//縮放(參數為圖形上下文,x方向縮放比例,y方向縮放比例)
// CGContextScaleCTM(ctx, 0.5, 0.5);
//平移(參數為圖形上下文,x方向平移距離,y方向平移距離)
CGContextTranslateCTM(ctx, 100, 100);
CGContextAddRect(ctx, CGRectMake(100, 100, 100, 100));
//標記
NSString *loc1 = @1;
NSString *loc2 = @2;
[loc1 drawAtPoint:CGPointMake(99, 99) withAttributes:nil];
[loc2 drawAtPoint:CGPointMake(201, 99) withAttributes:nil];
CGContextStrokePath(ctx);
指剪切掉指定區域意外的部分,只保留該區域內的內容。
原則:先設置好剪切區域,或者說剪切方法,再去繪制相關內容。
CGContextRef ctx = UIGraphicsGetCurrentContext();
//剪切自定義區域意外的部分(以剪切方法為三角形,剪切內容為圖片為例)
// // CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 50, 50));
// CGContextMoveToPoint(ctx, 100, 100);
// CGContextAddLineToPoint(ctx, 60, 150);
// CGContextAddLineToPoint(ctx, 140, 150);
// CGContextClosePath(ctx);
// CGContextClip(ctx);
//剪切指定矩形區域意外的部分
CGContextClipToRect(ctx, CGRectMake(80, 100, 10, 10));
UIImage *image = [UIImage imageNamed:@google];
[image drawAtPoint:CGPointMake(80, 100)];
我們之前畫一條直線,都是直接設置好它的起點和終點,然後就開始畫了。畫一個圓,設置好圓心半徑起點終點和方向即可。事實上,我們設置好這些繪圖信息後,系統會默認創建一條繪圖路徑,畫圖就是根據這條路徑來畫的。一條線對應一條路徑,一個圓對應另一條路徑。那麼我們自然可以通過手動創建路徑的方式繪制,需要繪制幾個圖案,就要創建幾條路徑。
A、Quartz2D中所有通過creat/copy/retain方法創建出來的值都要釋放,以path為例:
CGPathRelease(path);或 CFRelease(path);B、可以將要繪制的所有路徑加入到圖形上下文後,最後一次性渲染。
//繪制一條直線的兩種方法(兩種方式是等效的)
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 20, 200);
CGContextAddLineToPoint(ctx, 300, 200);
CGContextStrokePath(ctx);
//手動創建路徑繪制
//創建一條路徑
CGMutablePathRef path = CGPathCreateMutable();
//添加繪圖信息到路徑
CGPathMoveToPoint(path, NULL, 20, 300);
CGPathAddLineToPoint(path, NULL, 300, 300);
//將路徑添加到圖形上下文
CGContextAddPath(ctx, path);
//創建另一條路徑
CGMutablePathRef path2 = CGPathCreateMutable();
CGPathAddEllipseInRect(path2, NULL, CGRectMake(100, 400, 100, 100));
CGContextAddPath(ctx, path2);
CGContextStrokePath(ctx);
//渲染所有路徑對應的圖案
CGContextStrokePath(ctx);
//Quartz2D中所有通過creat/copy/retain方法創建出來的值都要釋放
CGPathRelease(path);
CGPathRelease(path2);
//或者
// CFRelease(path);
Quartz2D提供了以下幾種類型的圖形上下文
Bitmap Graphics Context PDF Graphics Context Window Graphics Context Layer Graphics Context Printer Graphics Context常用的是Bitmap Graphics Context。所謂Bitmap,其實就是UIImage,這也是最常用到的圖形上下文,通常用它來生成一張圖片。步驟如下:
創建一個bitmap圖形上下文(有兩種方式)
A.UIGraphicsBeginImageContext(<#CGSize size#>);
B.UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
這兩種方法都可以創建一個bitmap圖形上下文,但是第一種創建的圖片清晰度和質量沒有第二種好。方法二接收的參數依次為:
size:指定創建出來的bitmap尺寸大小 opaque:是否透明,YES表示不透明,NO透明 scale:縮放比例,0為不縮放獲取創建好的bitmap圖形上下文然後在上面做文章(畫圖)
取出繪制好的圖片 關閉bitmap圖形上下文 釋放需要釋放的變量
//點擊按鈕截屏
- (void)actionShot:(UIButton *)sender{
//可以隱藏按鈕,渲染完後顯示回來
self.buttonShot.hidden = YES;
//創建圖形上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.view.frame.size.width, self.view.frame.size.height), NO, 0);
//獲取圖形上下文並將當前屏幕渲染到圖形上下文上
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate.window .layer renderInContext:UIGraphicsGetCurrentContext()];
//從圖形上下文中取出繪制好的圖片
UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
//關閉圖形上下文
UIGraphicsEndImageContext();
self.buttonShot.hidden = NO;
// //截屏完畢 有時候可能想獲取屏幕中指定區域的圖片,如下操作
// //得到截屏的cgimage
CGImageRef image = screenImage.CGImage;
//設置目標區域,注意這裡需要考慮retina分辨率的放大倍數,以iphone6plus為例,在原尺寸的基礎上*3,這裡就不判斷了。
CGRect rect = CGRectMake(0, 0, screenImage.size.width*3, screenImage.size.height*3);
//取出目標區域的圖片
CGImageRef targetImage = CGImageCreateWithImageInRect(image, rect);
//最終圖片
UIImage *finalImage = [UIImage imageWithCGImage:targetImage];
//保存到相冊
UIImageWriteToSavedPhotosAlbum(finalImage, self, @selector(image: didFinishSavingWithError:contextInfo:), nil);
//保存到沙盒
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@yyyy-MM-dd HH:mm:ss];
NSString *currentTime = [dateFormatter stringFromDate:[NSDate date]];
NSString *imagePath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@ScreenShot_%@.png,currentTime]];
NSData *imageDate = UIImagePNGRepresentation(finalImage);
[imageDate writeToFile:imagePath atomically:YES];
CGImageRelease(targetImage);
}
//保存至相冊後的回調
- (void)image: (UIImage *) image didFinishSavingWithError: (NSError *) error contextInfo: (void *) contextInfo
{
NSString *msg = nil ;
if(error != NULL){
msg = @保存圖片失敗 ;
}else{
msg = @保存圖片成功 ;
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@保存圖片結果提示
message:msg
delegate:self
cancelButtonTitle:@確定
otherButtonTitles:nil];
[alert show];
}
前面講繪圖路徑的時候提到過內存管理,下面再總結一下:
凡是使用含有“create”或“copy”的函數創建的對象,使用完畢後必需釋放,否則將導致內存洩露。不含“create”或“copy”的則不需要釋放。 如果retain了一個對象,不再使用時需要release。已CGColorSpace為例,如果創建了一個CGColorSpace對象,則使用函數CGColorSpaceRetain和CGColorSpaceRelease來retain和release對象。也可以使用Core Foundation的CFRetain和CFRelease。注意不能傳遞NULL值給這些函數。