Graphics Contexts
圖形上下文表示繪圖目的地。它包含繪圖參數和繪圖系統執行任何后續繪圖命令所需的所有設備特定信息。圖形上下文定義了基本的繪圖屬性,如繪圖時使用的顏色、裁剪區域、行寬和樣式信息、字體信息、合成選項等。
您可以通過使用Quartz上下文創建函數或使用Mac OS X框架或iOS中的UIKit框架提供的高級函數來獲得圖形上下文。Quartz為各種類型的Quartz圖形上下文(包括位圖和PDF)提供了功能,您可以使用它們來創建自定義內容。
本章向您展示了如何為各種繪圖目的地創建圖形上下文。圖形上下文由數據類型CGContextRef在代碼中表示,CGContextRef是一種不透明的數據類型。獲得圖形上下文之后,您可以使用Quartz 2D函數來繪制上下文、執行上下文上的操作(如翻譯)和更改圖形狀態參數(如行寬和填充顏色)。
Drawing to a View Graphics Context in iOS
要在iOS應用程序中繪制屏幕,您需要設置一個UIView對象并實現它的drawRect:方法來執行繪制。當視圖在屏幕上可見且其內容需要更新時,調用視圖的drawRect:方法。在調用定制的drawRect:方法之前,view對象會自動配置它的繪圖環境,這樣您的代碼就可以立即開始繪圖了。作為這個配置的一部分,UIView對象為當前繪圖環境創建一個圖形上下文(cgcontext - tref不透明類型)。通過調用UIKit函數UIGraphicsGetCurrentContext,您可以在drawRect:方法中獲得這個圖形上下文。
UIKit中使用的默認坐標系與Quartz使用的坐標系不同。在UIKit中,原點位于左上角,正y值向下指向。UIView對象修改Quartz圖形上下文的CTM,以匹配UIKit約定,方法是將原點轉換到視圖左上角,并將y軸反轉,將其乘以-1。有關修改坐標系統的更多信息以及在您自己的繪圖代碼中的含義,請參閱Quartz 2D坐標系統。
UIView對象在iOS的View Programming Guide for iOS有描述。
Creating a Window Graphics Context in Mac OS X
在使用Mac OS X繪圖時,需要創建一個適合您正在使用的框架的窗口圖形上下文。Quartz 2D API本身不提供獲取windows圖形上下文的函數。相反,您可以使用Cocoa框架來獲取在Cocoa中創建的窗口的上下文。
您可以使用以下代碼從一個Cocoa應用程序的drawRect:例程中獲得一個Quartz圖形上下文:
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
方法currentContext返回當前線程的NSGraphicsContext實例。graphicsPort方法返回由接收器表示的低級的、特定于平臺的圖形上下文,接收器是Quartz圖形上下文。(不要被方法名弄混;他們是歷史命名。)有關更多信息,請參閱NSGraphicsContext類引用。
獲得圖形上下文之后,可以在Cocoa應用程序中調用任何Quartz 2D繪圖函數。您還可以將Quartz 2D調用與Cocoa繪圖調用混合。通過查看圖2-1,您可以看到一個針對Cocoa視圖的Quartz 2D繪圖示例。這幅畫由兩個重疊的矩形組成,一個是不透明的紅色矩形,另一個是部分透明的藍色矩形。你會學到更多關于顏色和顏色空間的透明度。能夠控制你能“看穿”多少顏色是石英2D的標志特征之一。
Figure 2-1 A view in the Cocoa framework that contains Quartz drawing
要創建圖2-1中的圖,首先創建一個Cocoa應用程序Xcode項目。在Interface Builder中,將一個自定義視圖拖到窗口,并將其子類化。然后為子類視圖編寫一個實現,類似于清單2-1所示。對于本例,子類視圖名為MyQuartzView。視圖的drawRect:方法包含所有Quartz繪圖代碼。清單后面顯示了每一行代碼的詳細說明。
注意:每次需要繪制視圖時,都會自動調用NSView類的drawRect:方法。要了解關于重寫drawRect:方法的更多信息,請參閱NSView類引用。
@implementation MyQuartzView
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
return self;
}
- (void)drawRect:(NSRect)rect
{
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
// ********** Your drawing code here **********
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);
CGContextFillRect (myContext, CGRectMake (0, 0, 200, 100 ));
CGContextSetRGBFillColor (myContext, 0, 0, 1, .5);
CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200));
}
@end
下面是代碼的作用:
1、獲取視圖的圖形上下文。
2、這就是插入繪圖代碼的地方。下面的四行代碼是使用Quartz 2D函數的示例。
3、設置一個完全不透明的紅色填充顏色。有關顏色和alpha(設置不透明度)的信息,請參閱顏色和顏色空間。
4、填充一個原點為(0,0)、寬度為200、高度為100的矩形。有關繪制矩形的信息,請參閱路徑。
5、設置部分透明的藍色填充色。
6、填充一個原點為(0,0)、寬度為100、高度為200的矩形。
Creating a PDF Graphics Context
當您創建一個PDF圖形上下文并繪制到該上下文時,Quartz會將您的繪圖記錄為一系列寫入文件的PDF繪圖命令。您為PDF輸出提供了一個位置和一個默認媒體框——一個指定頁面邊界的矩形。圖2-2顯示了繪制到PDF圖形上下文的結果,然后在預覽中打開生成的PDF。
Figure 2-2 A PDF created by using CGPDFContextCreateWithURL
Quartz 2D API提供了兩個創建PDF圖形上下文的功能:
- CGPDFContextCreateWithURL,當您希望將PDF輸出的位置指定為一個Core Foundation URL時,可以使用它。清單2-2展示了如何使用該函數創建PDF圖形上下文。
- CGPDFContextCreate,當您希望將PDF輸出發送給數據使用者時,可以使用它。(更多信息請參閱Data Management in Quartz 2D)清單2-3展示了如何使用該函數創建PDF圖形上下文。
說明:iOS中的PDF圖形上下文使用Quartz提供的默認坐標系統,而沒有應用轉換來匹配UIKit坐標系統。如果您的應用程序計劃在您的PDF圖形上下文和UIView對象提供的圖形上下文之間共享繪圖代碼,那么您的應用程序應該修改PDF圖形上下文的CTM以修改坐標系統。
Listing 2-2 Calling CGPDFContextCreateWithURL to create a PDF graphics context
CGContextRef MyPDFContextCreate (const CGRect *inMediaBox,
CFStringRef path)
{
CGContextRef myOutContext = NULL;
CFURLRef url;
url = CFURLCreateWithFileSystemPath (NULL, // 1
path,
kCFURLPOSIXPathStyle,
false);
if (url != NULL) {
myOutContext = CGPDFContextCreateWithURL (url,// 2
inMediaBox,
NULL);
CFRelease(url);// 3
}
return myOutContext;// 4
}
下面是代碼的作用:
1、調用Core Foundation函數,從提供給MyPDFContextCreate函數的CFString對象中創建一個CFURL對象。將NULL作為使用默認分配器的第一個參數。還需要指定路徑樣式,在本例中,路徑樣式是posix樣式的路徑名。
2、調用Quartz 2D函數,使用剛剛創建的PDF位置(作為CFURL對象)和一個指定PDF邊界的矩形來創建PDF圖形上下文。矩形(CGRect)被傳遞給MyPDFContextCreate函數,是PDF的默認頁面媒體包圍框。
3、釋放CFURL對象。
4、返回PDF圖形上下文。當不再需要圖形上下文時,調用者必須釋放它。
Listing 2-3 Calling CGPDFContextCreate to create a PDF graphics context
CGContextRef MyPDFContextCreate (const CGRect *inMediaBox,
CFStringRef path)
{
CGContextRef myOutContext = NULL;
CFURLRef url;
CGDataConsumerRef dataConsumer;
url = CFURLCreateWithFileSystemPath (NULL, // 1
path,
kCFURLPOSIXPathStyle,
false);
if (url != NULL)
{
dataConsumer = CGDataConsumerCreateWithURL (url);// 2
if (dataConsumer != NULL)
{
myOutContext = CGPDFContextCreate (dataConsumer, // 3
inMediaBox,
NULL);
CGDataConsumerRelease (dataConsumer);// 4
}
CFRelease(url);// 5
}
return myOutContext;// 6
}
下面是代碼的作用:
- 調用Core Foundation函數,從提供給MyPDFContextCreate函數的CFString對象中創建一個CFURL對象。將NULL作為使用默認分配器的第一個參數。還需要指定路徑樣式,在本例中,路徑樣式是posix樣式的路徑名。
- 使用CFURL對象創建一個Quartz數據使用者對象。如果您不想使用CFURL對象(例如,您想將PDF數據放在CFURL對象無法指定的位置),那么您可以從應用程序中實現的一組回調函數中創建一個數據使用者。有關更多信息,請參閱Quartz 2D中的數據管理。
- 調用Quartz 2D函數來創建一個PDF圖形上下文,將數據使用者和傳遞給MyPDFContextCreate函數的矩形(類型為CGRect)作為參數傳遞。這個矩形是PDF的默認頁面媒體包圍框。
- 釋放數據消費者。
- 釋放CFURL對象。
- 返回PDF圖形上下文。當不再需要圖形上下文時,調用者必須釋放它。
Listing 2-4 Drawing to a PDF graphics context
CGRect mediaBox;// 1
mediaBox = CGRectMake (0, 0, myPageWidth, myPageHeight);// 2
myPDFContext = MyPDFContextCreate (&mediaBox, CFSTR("test.pdf"));// 3
CFStringRef myKeys[1];// 4
CFTypeRef myValues[1];
myKeys[0] = kCGPDFContextMediaBox;
myValues[0] = (CFTypeRef) CFDataCreate(NULL,(const UInt8 *)&mediaBox, sizeof (CGRect));
CFDictionaryRef pageDictionary = CFDictionaryCreate(NULL, (const void **) myKeys,
(const void **) myValues, 1,
&kCFTypeDictionaryKeyCallBacks,
& kCFTypeDictionaryValueCallBacks);
CGPDFContextBeginPage(myPDFContext, &pageDictionary);// 5
// ********** Your drawing code here **********// 6
CGContextSetRGBFillColor (myPDFContext, 1, 0, 0, 1);
CGContextFillRect (myPDFContext, CGRectMake (0, 0, 200, 100 ));
CGContextSetRGBFillColor (myPDFContext, 0, 0, 1, .5);
CGContextFillRect (myPDFContext, CGRectMake (0, 0, 100, 200 ));
CGPDFContextEndPage(myPDFContext);// 7
CFRelease(pageDictionary);// 8
CFRelease(myValues[0]);
CGContextRelease(myPDFContext);
下面是代碼的作用:
- 為用于定義PDF媒體框的矩形聲明一個變量。
- 將媒體框的原點設置為(0,0),將寬度和高度設置為應用程序提供的變量。
- 調用函數MyPDFContextCreate(參見清單2-3)來獲得一個PDF圖形上下文,提供一個媒體框和一個路徑名。宏CFSTR將字符串轉換為CFStringRef數據類型。
- 設置帶有頁面選項的字典。在本例中,只指定了媒體框。您不必傳遞與設置PDF圖形上下文時相同的矩形。這里添加的媒體框將取代您為設置PDF圖形上下文而傳遞的矩形。
- 標志著頁的開始。這個函數用于面向頁面的圖形,這就是PDF繪圖。
- 調用Quartz 2D繪圖函數。您可以用適合您的應用程序的繪圖代碼替換這段代碼和以下四行代碼。
- 標志著PDF頁面的結束。
- 在不再需要詞典和PDF圖形上下文時發布它們。
- 您可以將任何內容寫入適合您的應用程序——圖像、文本、路徑繪圖——的PDF中,還可以添加鏈接和加密。有關更多信息,請參見PDF文檔的創建、查看和轉換。
Creating a Bitmap Graphics Context
位圖圖形上下文接受指向包含位圖存儲空間的內存緩沖區的指針。當您繪制到位圖圖形上下文時,緩沖區被更新。在您釋放圖形上下文之后,您將得到一個完全更新的像素格式的位圖。
注意:位圖圖形上下文有時用于屏幕外繪制。在決定為此目的使用位圖圖形上下文之前,請參閱Core Graphics Layer Drawing。CGLayer對象(CGLayerRef)是為屏幕外繪圖而優化的,因為只要可能,Quartz會在顯卡上緩存層。
注意:iOS應用程序應該使用UIGraphicsBeginImageContextWithOptions函數,而不是使用這里描述的底層Quartz函數。如果應用程序使用Quartz創建屏幕外的位圖,位圖圖形上下文使用的坐標系統就是默認的Quartz坐標系統。相反,如果您的應用程序通過調用函數UIGraphicsBeginImageContextWithOptions來創建一個圖像上下文,那么UIKit將同樣的轉換應用于上下文的坐標系統,就像它應用于UIView對象的圖形上下文一樣。這允許您的應用程序使用相同的繪圖代碼,而無需擔心不同的坐標系統。雖然您的應用程序可以手動調整坐標轉換矩陣以獲得正確的結果,但在實踐中,這樣做并沒有性能優勢。
您可以使用CGBitmapContextCreate函數來創建位圖圖形上下文。此函數接受以下參數:
- data.提供指向要呈現繪圖的內存中目標的指針。這個內存塊的大小至少應該是(bytesPerRow*height)字節。
- width。指定位圖的寬度(以像素為單位)。
- height。指定位圖的高度(以像素為單位)。
- bitsPerComponent。指定內存中每個像素組件的比特數。例如,對于32位像素格式和RGB顏色空間,您將指定每個組件的值為8位。看到像素格式的支持。
- bytesPerRow。指定位圖每行要使用的內存字節數。
提示:當您創建位圖圖形上下文時,如果您確保數據和bytesPerRow是16字節對齊的,您將獲得最佳性能。
- colorspace位圖上下文使用的顏色空間。在創建位圖圖形上下文時,可以提供灰色、RGB、CMYK或NULL顏色空間。有關顏色空間和顏色管理原則的詳細信息,請參閱顏色管理概述。有關在Quartz中創建和使用顏色空間的信息,請參閱顏色和顏色空間。有關支持的顏色空間的信息,請參閱位圖圖像和圖像遮罩章節中的顏色空間和位圖布局。
- bitmapInfo。位圖布局信息,表示為CGBitmapInfo常量,它指定位圖是否應該包含alpha組件、像素中alpha組件的相對位置(如果有的話)、alpha組件是否預乘、顏色組件是否為整數或浮點值。有關這些常量的詳細信息,當使用它們時,以及支持石英的像素格式用于位圖圖形上下文和圖像,請參閱位圖圖像和圖像掩碼章節中的顏色空間和位圖布局。
清單2-5展示了如何創建位圖圖形上下文。當您繪制到生成的位圖圖形上下文中時,Quartz將您的繪制記錄為指定內存塊中的位圖數據。下面是對每一行代碼的詳細說明。
Listing 2-5 Creating a bitmap graphics context
CGContextRef MyCreateBitmapContext (int pixelsWide,
int pixelsHigh)
{
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
bitmapBytesPerRow = (pixelsWide * 4);// 1
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);// 2
bitmapData = calloc( bitmapByteCount, sizeof(uint8_t) );// 3
if (bitmapData == NULL)
{
fprintf (stderr, "Memory not allocated!");
return NULL;
}
context = CGBitmapContextCreate (bitmapData,// 4
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
if (context== NULL)
{
free (bitmapData);// 5
fprintf (stderr, "Context not created!");
return NULL;
}
CGColorSpaceRelease( colorSpace );// 6
return context;// 7
}
下面是代碼的作用:
- 聲明一個變量來表示每行的字節數。本例中位圖中的每個像素都由4個字節表示;8位,紅,綠,藍,和。
- 創建一個通用的RGB顏色空間。您還可以創建一個CMYK顏色空間。有關更多信息,請參閱顏色和顏色空間,并討論一般顏色空間與設備相關的顏色空間。
- 調用calloc函數來創建和清除存儲位圖數據的內存塊。這個示例創建一個32位的RGBA位圖(也就是說,一個每個像素有32位的數組,每個像素包含8位紅色、綠色、藍色和alpha信息)。位圖中的每個像素占用4字節的內存。在Mac OS X 10.6和iOS 4中,這個步驟可以被忽略——如果您將NULL作為位圖數據傳遞,Quartz會自動為位圖分配空間。
- 創建位圖圖形上下文,提供位圖數據、位圖的寬度和高度、每個組件的比特數、每行字節數、顏色空間和一個常量,用于指定位圖是否應該包含alpha通道及其在像素中的相對位置。常量kCGImageAlphaPremultipliedLast表示alpha組件存儲在每個像素的最后一個字節中,顏色組件已經乘以這個alpha值。有關預乘阿爾法的更多信息,請參閱Alpha值。
- 如果由于某種原因沒有創建上下文,則釋放為位圖數據分配的內存。
- 版本的顏色空間。
- 返回位圖圖形上下文。當不再需要圖形上下文時,調用者必須釋放它。
清單2-6 顯示了調用MyCreateBitmapContext來創建位圖圖形上下文的代碼,使用位圖圖形上下文來創建CGImage對象,然后將生成的圖像繪制到窗口圖形上下文。圖2-3顯示了繪制到窗口的圖像。下面是對每一行代碼的詳細說明。
Listing 2-6 Drawing to a bitmap graphics context
CGRect myBoundingBox;// 1
myBoundingBox = CGRectMake (0, 0, myWidth, myHeight);// 2
myBitmapContext = MyCreateBitmapContext (400, 300);// 3
// ********** Your drawing code here ********** // 4
CGContextSetRGBFillColor (myBitmapContext, 1, 0, 0, 1);
CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 200, 100 ));
CGContextSetRGBFillColor (myBitmapContext, 0, 0, 1, .5);
CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 100, 200 ));
myImage = CGBitmapContextCreateImage (myBitmapContext);// 5
CGContextDrawImage(myContext, myBoundingBox, myImage);// 6
char *bitmapData = CGBitmapContextGetData(myBitmapContext); // 7
CGContextRelease (myBitmapContext);// 8
if (bitmapData) free(bitmapData); // 9
CGImageRelease(myImage);
下面是代碼的作用:
聲明一個變量,以存儲Quartz將在其中繪制從位圖圖形上下文創建的圖像的邊界框的原點和尺寸。
將邊界框的原點設置為(0,0),將寬度和高度設置為前面聲明的變量,但是代碼中沒有顯示其聲明。
調用應用程序提供的函數MyCreateBitmapContext(請參見清單2-5)來創建一個寬400像素、高300像素的位圖上下文。您可以使用適合您的應用程序的任何維度創建位圖圖形上下文。
調用Quartz 2D函數來繪制位圖圖形上下文。您可以用適合您的應用程序的繪圖代碼替換這段代碼和接下來的四行代碼。
從位圖圖形上下文創建一個石英2D圖像(CGImageRef)。
將圖像繪制到由邊框框指定的窗口圖形上下文中的位置。邊界框指定在用戶空間中繪制圖像的位置和尺寸。
這個例子沒有顯示窗口圖形上下文的創建。有關如何創建窗口的信息,請參閱在Mac OS X中創建窗口圖形上下文。
獲取與位圖圖形上下文關聯的位圖數據。
在不再需要位圖圖形上下文時釋放它。
釋放位圖數據,如果它存在。
當不再需要時釋放圖像。
Figure 2-3 An image created from a bitmap graphics context and drawn to a window graphics context
Supported Pixel Formats
表2-1總結了位圖圖形上下文所支持的像素格式、相關的顏色空間(cs)和Mac OS X的版本,其中該格式首次可用。像素格式指定為每個像素位(bpp)和每個組件位(bpc)。該表還包括與該像素格式相關聯的位圖信息常量。有關每個位圖信息格式常量表示的詳細信息,請參閱CGImage參考資料。
Anti-Aliasing
位圖圖形上下文支持抗鋸齒,這是在繪制文本或圖形時,人為地糾正在位圖圖像中有時看到的鋸齒(或鋸齒)邊緣的過程。當位圖的分辨率明顯低于眼睛的分辨率時,就會出現這些鋸齒狀的邊緣。為了使對象在位圖中看起來平滑,Quartz為形狀輪廓周圍的像素使用不同的顏色。通過這種方式混合顏色,形狀看起來很平滑。在圖2-4中可以看到使用抗鋸齒的效果。通過調用CGContextSetShouldAntialias函數,可以為特定的位圖圖形上下文關閉反鋸齒。反走樣設置是圖形狀態的一部分。
通過使用CGContextSetAllowsAntialiasing函數,您可以控制是否允許對特定圖形上下文進行反鋸齒。將true傳遞到此函數以允許反鋸齒;假的不允許。這個設置不是圖形狀態的一部分。當上下文和圖形狀態設置設置為true時,Quartz執行反鋸齒。
Figure 2-4 A comparison of aliased and anti-aliasing drawing
Obtaining a Graphics Context for Printing
Mac OS X中的Cocoa應用程序通過定制的NSView子類實現打印。視圖被告知通過調用其print:方法進行打印。然后視圖創建一個以打印機為目標的圖形上下文,并調用其drawRect:方法。您的應用程序使用與在屏幕上繪制相同的繪圖代碼來繪制打印機。它還可以自定義drawRect:調用打印機的圖像,該圖像與發送到屏幕上的圖像不同。