一个图形上下文代表一个绘图目标,它包含绘图参数和绘图系统执行任何后续绘图命令所需的所有特定于设备的信息。图形上下文定义了基本的绘图属性,例如绘图时要使用的颜色,剪切区域,线宽和样式信息,字体信息,合成选项以及其他几个属性。
可以使用Quartz上下文创建函数,或者使用Mac OS X框架或iOS中的UIKit框架提供的更高级别的函数来获取图形上下文。Quartz提供了用于各种Quartz图形上下文的函数,包括可用于创建自定义内容的位图和PDF。
本章介绍如何为各种绘图目标创建图形上下文。图形上下文在代码中由数据类型CGContextRef
表示,它是一种不透明的数据类型。获取图形上下文后,可以使用Quartz 2D函数绘制上下文,对上下文执行操作(例如平移),以及更改图形状态参数(例如线宽和填充颜色)。
在iOS中绘制到视图图形上下文
要在iOS应用程序中绘制到屏幕,请设置一个UIView
对象并实现其drawRect:
方法来执行绘制。当视图在屏幕上可见并且其内容需要更新时,将调用该视图的drawRect:
方法。在调用自定义drawRect:
方法之前,视图对象会自动配置其绘图环境,以便我们的代码可以立即开始绘制。作为此配置的一部分,UIView
对象为当前绘图环境创建图形上下文(CGContextRef
不透明类型)。可以通过在drawRect:
方法中调用UIKit函数UIGraphicsGetCurrentContext
获取此图形上下文。
整个UIKit中使用的默认坐标系不同于Quartz使用的坐标系。在UIKit中,原点位于左上角,y轴指向下方。UIView
对象通过将原点平移到视图的左上角,并将y轴乘以-1来反转y轴,从而修改Quartz图形上下文的CTM以匹配UIKit约定。有关修改坐标系及其在您自己的图形代码中的含义的更多信息,请参见Quartz 2D坐标系。
在Mac OS X中创建一个窗口图形上下文
在Mac OS X中绘图时,需要创建一个适合所用框架的窗口图形上下文。Quartz 2D API本身不提供获取窗口图形上下文的函数,而是使用Cocoa框架获取在Cocoa中为窗口创建的上下文。
可以使用以下代码从Cocoa应用程序的drawRect:
例程中获取一个Quartz图形上下文:
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
currentContext
方法返回当前线程的NSGraphicsContext
实例。 graphicsPort
方法返回由接收者代表的底层特定平台的图形上下文,它是Quartz图形上下文。有关更多信息,请参看NSGraphicsContext Class Reference。
获取图形上下文后,可以在Cocoa应用程序中调用任何Quartz 2D绘图函数。也可以将Quartz 2D调用与Cocoa绘图调用混合使用,在下图中可以看到Quartz 2D绘制到Cocoa视图的示例。该图案由两个重叠的矩形组成,一个不透明的红色矩形和一个部分透明的蓝色矩形。可以在Color and Color Spaces中了解关于透明度的更多信息。
要创建上图中的图案,首先要创建一个Cocoa应用程序Xcode项目。 在Interface Builder中,将一个自定义视图拖动到窗口,并将其子类化,然后为子类视图编写一个实现,类似于以下代码所示。在此示例中,子类化视图名为MyQuartzView
。视图的drawRect:
方法包含所有Quartz绘图代码。
注意:每次需要绘制视图时,都会自动调用
NSView
类的drawRect:
方法。要了解有关重写drawRect:
方法的更多信息,请参看NSView Class Reference。
@implementation MyQuartzView
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
return self;
}
- (void)drawRect:(NSRect)rect
{
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];// 1
// ********** Your drawing code here ********** // 2
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);// 3
CGContextFillRect (myContext, CGRectMake (0, 0, 200, 100 ));// 4
CGContextSetRGBFillColor (myContext, 0, 0, 1, .5);// 5
CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200));// 6
}
@end
以上代码依次做了下面这些事情:
- 获取视图的图形上下文;
- 在此处插入绘图代码。接下来的四行代码是使用Quartz 2D函数的示例;
- 设置完全不透明的红色填充颜色;
- 填充原点为(0,0),宽度为200,高度为100的矩形;
- 设置部分透明的蓝色填充颜色;
- 填充一个原点为(0,0),宽度为100,高度为200的矩形。
创建一个PDF图形上下文
当创建PDF图形上下文并绘制到该上下文时,Quartz将绘画记录为一系列写入文件的PDF绘制命令。您提供PDF输出的位置和一个默认的媒体框(一个指定页面边界的矩形),下图显示了绘制到PDF图形上下文然后在预览中打开生成的PDF的结果。
Quartz 2D API提供了两个创建PDF图形上下文的函数:
-
CGPDFContextCreateWithURL
,当想要将PDF输出位置指定为一个Core Foundation URL时使用。 -
CGPDFContextCreate
,在需要将PDF输出发送到数据使用者时使用。
iOS注意:iOS中的PDF图形上下文使用Quartz提供的默认坐标系,而不是应用变换来匹配UIKit坐标系。如果应用程序计划在PDF图形上下文和
UIView
对象提供的图形上下文之间共享绘图代码,则应用程序应修改PDF图形上下文的CTM来修改坐标系。请参看Quartz 2D Coordinate Systems。
CGContextRef MyPDFContextCreate (const CGRect *inMediaBox, CFStringRef path)
{
CGContextRef myOutContext = NULL;
CFURLRef url;
url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, false);// 1
if (url != NULL) {
myOutContext = CGPDFContextCreateWithURL(url, inMediaBox, NULL);// 2
CFRelease(url);// 3
}
return myOutContext;// 4
}
以上代码依次做了下面这些事情:
- 根据提供给
MyPDFContextCreate
函数的CFString
对象调用Core Foundation函数创建CFURL
对象。传递NULL
作为第一个参数来使用默认分配器。还需要指定路径样式,在此示例中,该路径样式是POSIX样式的路径名; - 使用刚刚创建的PDF位置(一个
CFURL
对象)和一个指定PDF边界的矩形来调用Quartz 2D函数创建PDF图形上下文。矩形(CGRect
)已传递到MyPDFContextCreate
函数,并且是PDF的默认页面媒体边框; - 释放
CFURL
对象; - 返回PDF图形上下文。当图形上下文不再需要被使用时,调用者必须释放它。
CGContextRef MyPDFContextCreate(const CGRect *inMediaBox, CFStringRef path)
{
CGContextRef myOutContext = NULL;
CFURLRef url;
CGDataConsumerRef dataConsumer;
url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false);// 1
if (url != NULL)
{
dataConsumer = CGDataConsumerCreateWithURL(url);// 2
if (dataConsumer != NULL)
{
myOutContext = CGPDFContextCreate(dataConsumer, inMediaBox, NULL);// 3
CGDataConsumerRelease(dataConsumer);// 4
}
CFRelease(url);// 5
}
return myOutContext;// 6
}
以上代码依次做了下面这些事情:
- 根据提供给
MyPDFContextCreate
函数的CFString
对象调用Core Foundation函数创建CFURL
对象。传递NULL
作为第一个参数来使用默认分配器。还需要指定路径样式,在此示例中,该路径样式是POSIX样式的路径名; - 使用
CFURL
对象创建一个Quartz数据使用者对象。如果不想使用CFURL
对象(例如,要将PDF数据放置在CFURL
对象无法指定的位置),则可以改用一组您在应用程序中实现的回调函数来创建数据使用者。有关更多信息,请参看Data Management in Quartz 2D; - 将数据消费者和传递给
MyPDFContextCreate
函数的矩形(CGRect
类型)作为参数传递,调用Quartz 2D函数来创建PDF图形上下文,传递的矩形是PDF的默认页面媒体边框; - 释放数据消费者;
- 释放
CFURL
对象; - 返回PDF图形上下文。当图形上下文不再需要被使用时,调用者必须释放它。
以下代码显示了如何调用MyPDFContextCreate
例程并绘制PDF图形上下文。
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
来获取PDF图形上下文,并提供一个媒体框和一个路径名。宏CFSTR
将字符串转换为CFStringRef
数据类型; - 使用页面选项设置一个字典。在此示例中,仅指定了媒体框。不必传递用于设置PDF图形上下文的相同矩形。在此处添加的媒体框将取代您传递的用于设置PDF图形上下文的矩形;
- 指示页面的开始;
- 调用Quartz 2D绘图函数;
- 指示PDF页面的结束;
- 当不再需要字典和PDF图形上下文时,将它们释放。
可以将任何适合应用程序的内容(图像,文本,路径图案)写到PDF中,还可以添加链接和加密。有关更多信息,请参看PDF Document Creation, Viewing, and Transforming。
创建一个位图图形上下文
位图图形上下文接收一个指向包含位图存储空间的内存缓冲区的指针。当绘制到位图图形上下文中时,会更新缓冲区。释放图形上下文后,将获得一张具有指定像素格式的完全更新的位图。
注意:位图图形上下文有时用于在屏幕外绘制。在决定为此目的使用位图图形上下文之前,请参看Core Graphics Layer Drawing。CGLayer对象(
CGLayerRef
)已针对屏幕外绘图进行了优化,因为只要有可能,Quartz就会在显卡中缓存图层。
iOS注意:iOS应用程序应使用
UIGraphicsBeginImageContextWithOptions
函数,而不要使用此处介绍的底层Quartz函数。如果应用程序使用Quartz创建屏幕外的位图,则位图图形上下文使用的坐标系是默认的Quartz坐标系。相反,如果应用程序通过调用函数UIGraphicsBeginImageContextWithOptions
创建图像上下文(image context),则UIKit会将应用与UIView
对象的图形上下文(graphics context)相同的变换到上下文的坐标系。这样,应用程序就可以将相同的绘图代码用于其中一个,而不必担心坐标系不同。尽管应用程序可以手动调整coordinate transformation matrix以获取正确的结果,但实际上,这样做没有任何性能上的好处。
可以使用函数CGBitmapContextCreate
来创建位图图形上下文。此函数包含以下参数:
- data,提供一个指向内存缓冲区的指针,Quartz在该内存缓冲区中渲染图案。该存储区域的大小至少应为(bytesPerRow * height)个字节。
- width,指定位图的宽度(以像素为单位)。
- height,指定位图的高度(以像素为单位)。
- bitsPerComponent,指定要用于内存中像素的每个分量的bit数。例如,对于32-bit像素格式和RGB颜色空间,应为每个部分指定8bit的值。
- bytesPerRow,指定位图的每一行要使用的内存字节数。
提示:创建位图图形上下文时,如果确保data和bytesPerRow是16-byte对齐的,则将获得最佳性能。
- colorspace,用于位图上下文的颜色空间。创建位图图形上下文时,可以提供Gray,RGB,CMYK或
NULL
颜色空间。有关颜色空间和颜色管理原理的详细信息,请参看Color Management Overview。有关在Quartz中创建和使用颜色空间的信息,请参看Color and Color Spaces。有关支持的颜色空间的信息,请参看Bitmap Images and Image Masks中的Color Spaces and Bitmap Layout。 - bitmapInfo,位图布局信息,表示为CGBitmapInfo常数,用于指定位图是否应包含alpha分量、alpha分量(如果有)在像素中的相对位置、alpha分量是否预乘以及颜色分量是整数还是浮点值。有关这些常量是什么,何时使用常量以及位图图形上下文和图像的Quartz支持的像素格式的详细信息,请参看Bitmap Images and Image Masks中的Color Spaces and Bitmap Layout。
以下代码显示了如何创建位图图形上下文。当您绘制到最终的位图图形上下文中时,Quartz将您的图案作为位图数据记录在指定的内存块中。
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, pixelsWide, pixelsHigh, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);// 4
if (context== NULL)
{
free (bitmapData);// 5
fprintf (stderr, "Context not created!");
return NULL;
}
CGColorSpaceRelease( colorSpace );// 6
return context;// 7
}
以上代码依次做了下面这些事情:
- 声明一个变量以表示每行的字节数。在此示例中,位图中的每个像素都由4个字节表示;红色,绿色,蓝色和Alpha分别为8位(bit)。
- 创建一个通用的RGB颜色空间。也可以创建一个CMYK颜色空间,有关更多信息以及有关通用颜色空间与设备相关的颜色空间的讨论,请参看Color and Color Spaces。
- 调用
calloc
函数开辟一个用于存储位图数据的内存块。本示例创建一个32位RGBA位图(即每个像素32位的数组,每个像素包含8位红,绿,蓝和alpha信息)。位图中的每个像素占用4个字节的内存。在Mac OS X 10.6和iOS 4中,如果将NULL
作为位图数据传递,可以省略此步骤,Quartz会自动为位图分配空间。 - 创建位图图形上下文,提供位图数据,位图的宽度和高度,每个分量的位数,每行的字节数,颜色空间以及一个常数,该常数指定位图是否应包含Alpha通道及其像素中的相对位置。 常数
kCGImageAlphaPremultipliedLast
表示alpha分量存储在每个像素的最后一个字节中,并且颜色分量已经乘以该alpha值。有关预乘alpha的更多信息,请参见The Alpha Value。 - 如果由于某种原因未创建上下文,则释放为位图数据分配的内存。
- 释放颜色空间。
- 返回位图图形上下文。当不再需要该位图图形上下文时,由调用者释放它。
以下代码显示了调用MyCreateBitmapContext
函数创建并使用位图图形上下文来创建CGImage
对象,然后将所得图像绘制到窗口图形上下文中。
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);// 10
以上代码依次做了下面这些事情:
- 声明一个变量来存储边界框的原点和尺寸,Quartz将在该边界框内绘制从位图图形上下文创建的图像。
- 将边界框的原点设置为(0,0),并将宽度和高度设置为先前声明的变量,但其声明未在此代码中显示。
- 调用应用程序提供的函数
MyCreateBitmapContext
以创建宽度为400像素,高度为300像素的位图图形上下文。 - 调用Quartz 2D函数以绘制到位图图形上下文中。可以用其它合适的绘图代码替换此代码以及接下来的四行代码。
- 从位图图形上下文创建Quartz 2D图像(
CGImageRef
)。 - 将图像绘制到窗口图形上下文中由边界框指定的位置。边界框指定在用户空间中绘制图像的位置和尺寸。本示例未展示创建窗口图形上下文的代码。
- 获取与位图图形上下文关联的位图数据。
- 当不再需要时,释放位图图形上下文。
- 释放位图数据(如果存在)。
- 当不再需要时,释放图像。
支持的像素格式
下表总结了位图图形上下文所支持的像素格式,关联的颜色空间(cs)以及首次使用该格式的Mac OS X版本。像素格式指定为每个像素的位(bpp)和每个分量的位(bpc)。该表还包括与像素格式关联的位图信息常量。有关每个位图信息格式常量表示什么的详细信息,请参看CGImage Reference。
颜色空间 | 像素格式和位图信息常量 | 适用性 |
---|---|---|
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 和kCGBitmapFloatComponent
|
Mac OS X |
抗锯齿
位图图形上下文支持抗锯齿,这是人为地纠正在绘制文本或形状时有时会在位图图像中看到的钩状(或锯齿状)边缘的过程。当位图的分辨率大大低于人的眼睛的分辨率时,就会出现这些锯齿状的边缘。为了使对象在位图中显得平滑,Quartz对围绕形状的轮廓的像素使用不同的颜色。通过以这种方式混合颜色,形状看起来很平滑。可以在下图中看到使用抗锯齿后的效果。可以通过调用CGContextSetShouldAntialias
函数来关闭特定的位图图形上下文的抗锯齿功能。抗锯齿设置是图形状态的一部分。
可以使用函数CGContextSetAllowsAntialiasing
控制是否允许特定的图形上下文抗锯齿。将true
传递给此函数以允许抗锯齿;false
,则不允许。此设置不是图形状态的一部分。当上下文和图形状态设置同时被设置为true
时,Quartz才会执行抗锯齿。
获取一个要打印的图形上下文
Mac OS X中的Cocoa应用程序通过自定义NSView
子类实现打印。通过调用视图的print:
方法来告知视图去打印。然后,该视图创建一个以打印机为目标的图形上下文,并调用其drawRect:
方法。应用程序使用与绘制到屏幕上相同的绘制代码绘制到打印机上。
有关Cocoa打印的详细讨论,请参看Printing Programming Guide for Mac。