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:调用打印机的图像,该图像与发送到屏幕上的图像不同。