上一篇文章大概描述了下Quartz里面大體所包含的東西,但是對具體的細節實現以及如何調用相應API卻沒有講。這篇文章就先講講圖形上下文(Graphics Context)的具體操作。
所謂Graphics Context,其實就是表示了一個繪制目標,也就是你打算繪制的地方,它包含繪制系統用于完成繪制指令的繪制參數和設備相關信息。Graphics Context定義了基本的繪制屬性,如顏色、裁減區域、線條寬度和樣式信息、字體信息、混合模式等。然而,我們怎樣才能獲得或者創建一個Graphics Context呢?方法自然有很多:Quartz提供的創建函數、Mac OS X框架或IOS的UIKit框架提供的函數。Quartz提供了多種Graphics Context的創建函數,包括bitmap和PDF,我們可以使用這些Graphics Context創建自定義的內容。下面我們就介紹如何為不同的繪制目標創建Graphics Context。在代碼中,我們用CGContextRef來表示一個Graphics Context。當獲得一個Graphics Context后,可以使用Quartz 2D函數在上下文(context)中進行繪制、完成操作(如平移)、修改圖形狀態參數(如線寬和填充顏色)等。
一、在iOS中的UIView的Graphics Context進行繪制
在iOS應用程序中,如果要在屏幕上進行繪制,需要創建一個UIView對象,并實現它的drawRect:方法。視圖的drawRect:方法在視圖顯示在屏幕上及它的內容需要更新時被調用。在調用自定義的drawRect:后,視圖對象自動配置繪圖環境以便代碼能立即執行繪圖操作。作為配置的一部分,視圖對象將為當前的繪圖環境創建一個Graphics Context。我們可以通過調用UIGraphicsGetCurrentContext函數來獲取這個Graphics Context。
例如在UIView中畫個圓
1 - (void)drawRect:(CGRect)rect { 2 3 // 1. 獲取一個與視圖相關聯的上下文 4 CGContextRef context = UIGraphicsGetCurrentContext(); 5 6 // 設置顏色 7 [[UIColor greenColor]setFill]; 8 9 // 2. 增加圓形的路徑10 CGContextAddEllipseInRect(context, CGRectMake(50, 50, 200, 200));11 // 3. 畫圓12 CGContextFillPath(context);13 14 }
PS:UIKit默認的坐標系統與Quartz不同。在UIKit中,原點位于左上角,y軸正方向為向下。UIView通過將修改Quartz的Graphics Context的CTM[原點平移到左下角,同時將y軸反轉(y值乘以-1)]以使其與UIView匹配。這些都是系統自動幫我們完成。
二、創建一個PDF Graphics Context
Quartz 2D API提供了兩個函數來創建PDF Graphics Context:當創建一個PDF Graphics Context并繪制時,Quartz將繪制操作記錄為一系列的PDF繪制命令并寫入文件中。我們需要提供一個PDF輸出的位置及一個默認的media box(用于指定頁面邊界的長方形)。
1.CGPDFContextCreateWithURL:當你需要用Core Foundation URL指定pdf輸出的位置時使用該函數。
1 CGContextRef MyPDFContextCreate (const CGRect *inMediaBox, CFStringRef path) 2 { 3 CGContextRef myOutContext = NULL; 4 CFURLRef url; 5 url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, false); 6 if (url != NULL) { 7 myOutContext = CGPDFContextCreateWithURL (url, inMediaBox, NULL); 8 CFRelease(url); 9 }10 return myOutContext;11 }
2.CGPDFContextCreate:當需要將pdf輸出發送給數據用戶時使用該方法。
1 CGContextRef MyPDFContextCreate (const CGRect *inMediaBox, CFStringRef path) 2 { 3 CGContextRef myOutContext = NULL; 4 CFURLRef url; 5 CGDataConsumerRef dataConsumer; 6 url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, false); 7 if (url != NULL) 8 { 9 dataConsumer = CGDataConsumerCreateWithURL (url);10 if (dataConsumer != NULL)11 {12 myOutContext = CGPDFContextCreate (dataConsumer, inMediaBox, NULL);13 CGDataConsumerRelease (dataConsumer);14 }15 CFRelease(url);16 }17 return myOutContext;18 }
下面是如何調用MyPDFContextCreate程序及繪制操作。
1 CGRect mediaBox; 2 mediaBox = CGRectMake (0, 0, myPageWidth, myPageHeight); 3 myPDFContext = MyPDFContextCreate (&mediaBox, CFSTR("test.pdf")); 4 CFStringRef myKeys[1]; 5 CFTypeRef myValues[1]; 6 myKeys[0] = kCGPDFContextMediaBox; 7 myValues[0] = (CFTypeRef) CFDataCreate(NULL,(const UInt8 *)&mediaBox, sizeof (CGRect)); 8 CFDictionaryRef pageDictionary = CFDictionaryCreate(NULL, (const void **) myKeys, 9 (const void **) myValues, 1,10 &kCFTypeDictionaryKeyCallBacks,11 & kCFTypeDictionaryValueCallBacks);12 CGPDFContextBeginPage(myPDFContext, &pageDictionary);13 // ********** 繪制代碼 **********14 CGContextSetRGBFillColor (myPDFContext, 1, 0, 0, 1);15 CGContextFillRect (myPDFContext, CGRectMake (0, 0, 200, 100 ));16 CGContextSetRGBFillColor (myPDFContext, 0, 0, 1, .5);17 CGContextFillRect (myPDFContext, CGRectMake (0, 0, 100, 200 ));18 CGPDFContextEndPage(myPDFContext);19 CFRelease(pageDictionary);20 CFRelease(myValues[0]);21 CGContextRelease(myPDFContext);
我們可以將任何內容(圖片,文本,繪制路徑)繪制到pdf中,并能添加鏈接及加密。
添加鏈接
我們可以在PDF上下文中添加鏈接和錨點。Quartz提供了三個函數,每個函數都以PDF圖形上下文作為參數,還有鏈接的信息:
· CGPDFContextSetURLForRect可以讓我們指定在點擊當前PDF頁中的矩形時打開一個URL。
· CGPDFContextSetDestinationForRect指定在點擊當前PDF頁中的矩形區域時設置目標以進行跳轉。我們需要提供一個目標名。
· CGPDFContextAddDestinationAtPoint指定在點擊當前PDF頁中的一個點時設置目標以進行跳轉。我們需要提供一個目標名。
保護PDF內容
為了保護PDF內容,我們可以在輔助字典中指定一些安全選項并傳遞給CGPDFContextCreate。我們可以通過包含如下關鍵字來設置所有者密碼、用戶密碼、PDF是否可以被打印或拷貝:
· kCGPDFContextOwnerPassWord: 定義PDF文檔的所有者密碼。如果指定該值,則文檔使用所有者密碼來加密;否則文檔不加密。該關鍵字的值必須是ASCII編碼的CFString對象。只有前32位是用于密碼的。該值沒有默認值。如果該值不能表示成ASCII,則無法創建文檔并返回NULL。Quartz使用40-bit加密。
· kCGPDFContextUserPassword: 定義PDF文檔的用戶密碼。如果文檔加密了,則該值是文檔的用戶密碼。如果沒有指定,則用戶密碼為空。該關鍵字的值必須是ASCII編碼的CFString對象。只有前32位是用于密碼的。如果該值不能表示成ASCII,則無法創建文檔并返回NULL。
· kCGPDFContextAllowsPRinting:指定當使用用戶密碼鎖定時文檔是否可以打印。該值必須是CFBoolean對象。默認值是kCGBooleanTrue。
· kCGPDFContextAllowsCopying: 指定當使用用戶密碼鎖定時文檔是否可以拷貝。該值必須是CFBoolean對象。默認值是kCGBooleanTrue。
代碼清單14-4(下一章)顯示了確認PDF文檔是否被鎖定,及用密碼打開文檔。
當然在iOS中,我們完全可以pass掉這些晦澀難懂的底層代碼,因為系統給我們提供了更高一級的函數來生成PDF文件,下面是代碼
1 - (void)createPDFFile 2 { 3 // 1. 上下文 4 // 1) 路徑 5 // 2) 大小,指定為空,那么使用612 * 792大小作為pdf文件的頁面大小 6 // 3) dict 7 UIGraphicsBeginPDFContextToFile(@"/Users/apple/Desktop/demo.pdf", CGRectZero, nil); 8 9 // 2. 寫入內容10 // 在pdf里面是有頁面的,一個頁面一個頁面的寫入的11 // 建立PDF頁面12 // 一個頁面最多能夠寫入兩張圖片,因此寫入6張圖片需要三個頁面13 for (NSInteger i = 0; i < 6; i++) {14 if (i % 2 == 0) {15 UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);16 }17 18 // 生成UIImage19 UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"NatGeo%02d.png", i + 1]];20 // 寫入21 [image drawAtPoint:CGPointMake(0, 400 * (i % 2))];22 }23 24 // 3. 關閉上下文25 UIGraphicsEndPDFContext();26 }
三、創建位圖Graphics Context
一個位圖Graphics Context接受一個指向內存緩存(包含位圖存儲空間)的指針,當我們繪制一個位圖Graphics Context時,該緩存被更新。在釋放Graphics Context后,我們將得到一個我們指定像素格式的全新的位圖。
我們使用CGBitmapContextCreate來創建位圖Graphics Context,該函數有如下參數:
下列代碼顯示了如何創建位圖Graphics Context。當向位圖Graphics Context繪圖時,Quartz將繪圖記錄到內存中指定的塊中。
1 CGContextRef MyCreateBitmapContext (int pixelsWide, int pixelsHigh) 2 { 3 CGContextRef context = NULL; 4 CGColorSpaceRef colorSpace; 5 void * bitmapData; 6 int bitmapByteCount; 7 int bitmapBytesPerRow; 8 bitmapBytesPerRow = (pixelsWide * 4); 9 bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);10 colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);11 bitmapData = calloc( bitmapByteCount );12 if (bitmapData == NULL)13 {14 fprintf (stderr, "Memory not allocated!");15 return NULL;16 }17 context = CGBitmapContextCreate (bitmapData, pixelsWide, pixelsHigh, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);18 if (context== NULL)19 {20 free (bitmapData);21 fprintf (stderr, "Context not created!");22 return NULL;23 }24 CGColorSpaceRelease( colorSpace );25 return context;26 }
下面是調用MyCreateBitmapContext 創建一個位圖Graphics Context,使用位圖Graphics Context來創建CGImage對象,然后將圖片繪制到窗口Graphics Context中。
1 CGRect myBoundingBox; 2 myBoundingBox = CGRectMake (0, 0, myWidth, myHeight); 3 myBitmapContext = MyCreateBitmapContext (400, 300); 4 // ********** Your drawing code here ********** 5 CGContextSetRGBFillColor (myBitmapContext, 1, 0, 0, 1); 6 CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 200, 100 )); 7 CGContextSetRGBFillColor (myBitmapContext, 0, 0, 1, .5); 8 CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 100, 200 )); 9 myImage = CGBitmapContextCreateImage (myBitmapContext);10 CGContextDrawImage(myContext, myBoundingBox, myImage);11 char *bitmapData = CGBitmapContextGetData(myBitmapContext); 12 CGContextRelease (myBitmapContext);13 if (bitmapData) free(bitmapData); 14 CGImageRelease(myImage);
上面的都是Quartz的底層代碼,然后在iOS的實際開發中,iOS應用程序使用了UIGraphicsBeginImageContextWithOptions取代Quartz低層函數。所以說如果只搞iOS開發,上面晦澀的代碼完全可以pass掉。如果使用Quartz創建一下后臺bitmap,bitmap Graphics Context使用的坐標系統是Quartz默認的坐標系統。而使用UIGraphicsBeginImageContextWithOptions創建圖形上下文,UIKit將會對坐標系統使用與UIView對象的圖形上下文一樣的轉換。這允許應用程序使用相同的繪制代碼而不需要擔心坐標系統問題。雖然我們的應用程序可以手動調整CTM達到相同的效果,但這種做沒有任何好處。
下面是iOS中利用UIGraphicsBeginImageContextWithOptions來剪裁成圓型位圖并返回的代碼
+ (instancetype)circleImageWithName:(NSString *)name borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{ // 1.加載原圖 UIImage *oldImage = [UIImage imageNamed:name]; // 2.開啟上下文 CGFloat imageW = oldImage.size.width + 2 * borderWidth; CGFloat imageH = oldImage.size.height + 2 * borderWidth; CGSize imageSize = CGSizeMake(imageW, imageH); UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0); // 3.取得當前的上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); // 4.畫邊框(大圓) [borderColor set]; CGFloat bigRadius = imageW * 0.5; // 大圓半徑 CGFloat centerX = bigRadius; // 圓心 CGFloat centerY = bigRadius; CGContextAddArc(ctx, centerX, centerY, bigRadius, 0, M_PI * 2, 0); CGContextFillPath(ctx); // 畫圓 // 5.小圓 CGFloat smallRadius = bigRadius - borderWidth; CGContextAddArc(ctx, centerX, centerY, smallRadius, 0, M_PI * 2, 0); // 裁剪(后面畫的東西才會受裁剪的影響) CGContextClip(ctx); // 6.畫圖 [oldImage drawInRect:CGRectMake(borderWidth, borderWidth, oldImage.size.width, oldImage.size.height)]; // 7.取圖 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); // 8.結束上下文 UIGraphicsEndImageContext(); return newImage;}
支持的像素格式
表2-1總結了位圖Graphics Context支持的像素格式,相關的顏色空間及像素格式支持的Mac OS X最早版本。像素格式用bpp(每像素的位數)和bpc(每個組件的位數)來表示。表格同時也包含與像素格式相關的位圖信息常量。
表2-1:位圖Graphics Context支持的像素格式
| Null | 8 bpp, 8 bpc, kCGImageAlphaOnly | Mac OS X, iOS |
| Gray | 8 bpp, 8 bpc,kCGImageAlphaNone | Mac OS X, iOS |
| Gray | 8 bpp, 8 bpc,kCGImageAlphaOnly | Mac OS X, iOS |
| Gray | 16 bpp, 16 bpc, kCGImageAlphaNone | Mac OS X |
| Gray | 32 bpp, 32 bpc, kCGImageAlphaNone|kCGBitmapFloatComponents | Mac OS X |
| RGB | 16 bpp, 5 bpc, kCGImageAlphaNoneSkipFirst | Mac OS X, iOS |
| RGB | 32 bpp, 8 bpc, kCGImageAlphaNoneSkipFirst | Mac OS X, iOS |
| RGB | 32 bpp, 8 bpc, kCGImageAlphaNoneSkipLast | Mac OS X, iOS |
| RGB | 32 bpp, 8 bpc, kCGImageAlphaPremultipliedFirst | Mac OS X, iOS |
| RGB | 32 bpp, 8 bpc, kCGImageAlphaPremultipliedLast | Mac OS X, iOS |
| RGB | 64 bpp, 16 bpc, kCGImageAlphaPremultipliedLast | Mac OS X |
| RGB | 64 bpp, 16 bpc, kCGImageAlphaNoneSkipLast | Mac OS X |
| RGB | 128 bpp, 32 bpc, kCGImageAlphaNoneSkipLast |kCGBitmapFloatComponents | Mac OS X |
| RGB | 128 bpp, 32 bpc, kCGImageAlphaPremultipliedLast |kCGBitmapFloatComponents | Mac OS X |
| CMYK | 32 bpp, 8 bpc, kCGImageAlphaNone | Mac OS X |
| CMYK | 64 bpp, 16 bpc, kCGImageAlphaNone | Mac OS X |
| CMYK | 128 bpp, 32 bpc, kCGImageAlphaNone |kCGBitmapFloatComponents | Mac OS X |
反鋸齒
位圖Graphics Context支持反鋸齒,這一操作是人為的較正在位圖中繪制文本或形狀時產生的鋸齒邊緣。當位圖的分辯率明顯低于人眼的分辯率時就會產生鋸齒。為了使位圖中的對象顯得平滑,Quartz使用不同的顏色來填充形狀周邊的像素。通過這種方式來混合顏色,使形狀看起來更平滑。如圖2-4顯示的效果。我們可以通過調用CGContextSetShouldAntialias來關閉位圖Graphics Context的反鋸齒效果。反鋸齒設置是圖形狀態的一部分。
可以調用函數CGContextSetAllowsAntialiasing來控制一個特定Graphics Context是否支持反鋸齒;false表示不支持。該設置不是圖形狀態的一部分。當上下文及圖形狀態設置為true時,Quartz執行反鋸齒,如下圖
四、獲取打印的Graphics Context
Mac OS X中的Cocoa應用程序通過自定義的NSView子類來實現打印。一個視圖通過調用print:方法來進行打印。然后視圖以打印機為目標創建一個Graphics Context,并調用drawRect:方法。應用程序使用與在屏幕進行繪制相同的繪制代碼。我們同樣可以自定義drawRect: 方法將圖形繪制到打印機。
新聞熱點
疑難解答