標籤:ios開發 quartz2d 繪圖架構
Quartz 2D學習記錄Quartz 2D簡單介紹一、什麼是Quartz 2D
Quarz 2D是一個二維繪畫引擎,同時支援ios和mac,其API是Core Graphics架構的,是純C語言的。IOS系統提供的大部分控制項的內容都是通過Quartz 2D畫出來的,因此Quartz 2D的一個很重要的價值是:自訂view(自訂UI控制項)。
二、一個重要的概念:圖形上下文
圖形上下文(Graphics context)是一個CGContextRef資料,其作用是:
- 儲存繪圖資訊、繪圖屬性
- 繪製靶心圖表案
- 輸出繪製好的圖案到輸出目標去,即渲染到什麼地方去(可以是PDF檔案、bitmap或者顯示器的視窗上)
三、自訂view,即繪製view
在view中實現- (void)drawRect:(CGRect)rect方法,然後在方法中:
1. 取得跟當前view相關聯的圖形上下文;
2. 繪製相應的圖形內容
3. 利用圖形上下文將繪製好的圖形內容渲染顯示到view上面
Quartz 2D簡單使用程式碼範例
//繪製步驟: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);
運行結果
Bitmap一、bitmap圖形上下文
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];}
Quartz2D記憶體管理
前面講繪圖路徑的時候提到過記憶體管理,下面再總結一下:
- 凡是使用含有“create”或“copy”的函數建立的對象,使用完畢後必需釋放,否則將導致記憶體泄露。不含“create”或“copy”的則不需要釋放。
- 如果retain了一個對象,不再使用時需要release。已CGColorSpace為例,如果建立了一個CGColorSpace對象,則使用函數CGColorSpaceRetain和CGColorSpaceRelease來retain和release對象。也可以使用Core Foundation的CFRetain和CFRelease。注意不能傳遞NULL值給這些函數。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
IOS開發—Quartz 2D介紹