图形上下文(Graphics Context)—— Quartz
本文翻译自苹果官方文档:原文地址
图形上下文(Graphics Context)
图形上下文指的是绘图的地点,它包含了绘图系统需要实现后续绘图操作需要的参数和指定设备的所有信息。图形上下文定义了基本的绘图特性,如:颜色、裁剪区域、线的宽度和样式、字体信息、复合选项等等。
你能够使用Quartz提供的上下文创建函数或者由 OS X 以及 iOS 提供的更高级的函数来得到一个图形上下文。Quartz为很多不同种类的图形上下文(如,bitmap、PDF)提供了很多函数;你可以用这些来创建自定义的内容。
本章中,我们将教会你如何创建各式各样的图形上下文(针对不同的绘图地点)。在代码中图形上下文用一个类型为CGContextRef的数据代替,这个数据是闭源的。在你获取到图形上下文之后,你可以使用Quartz提供的函数来进行绘制、变换等操作。
iOS中绘制到页面的图形上下文(Drawing to a View Graphics Context in iOS)
在iOS的一个APP中,如果你想将内容绘制到屏幕,你需要创建一个UIView对象并实现它的drawRect:方法。当View可见并且内容需要更新时,View的drawRect:将会被调用。在调用drawRect:方法之前,这View对象自动注册了它的绘图环境,以方便你能够快速的绘制。这个UIView在注册的过程中,创建了一个针对当前绘图环境的图形上下文(CGContextRef类型)。你能够在drawRect:方法中用使用UIGraphicsGetCurrentContext来获取这个图形上下文。
UIKit与Quartz使用着不同的坐标系;在UIKit中,坐标系的原点在左上角,y轴正半轴向下延伸;而在Quartz中原点在左下角,y轴正半轴向上延伸。UIView对象修改了当前图形上下文的CTM,使得坐标系能够与UIKit匹配。
UIView详细的描述在View Programming Guide for iOS。
在 Mac OS X 中创建一个窗口图形上下文(Window Graphics Context)
当你在 Mac OS X 中绘制时,你需要创建一个适合你正在使用框架的图形上下文。Quartz 2D 本身没有提供函数来获取一个窗口图形上下文。但是,你可以使用Cocoa 框架来获取一个在Cocoa中创建的窗口上下文。
你在drawRect:中使用如下代码来获取上下文:
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
currentContext方法返回了一个属于当前线程的NSGraphicsContext实例。graphicsPort 方法返回一个低级、指定平台的图形上下文,它是一个Quartz图形上下文(CGContextRef),不要被这个方法的名字弄混淆了,它是有历史原因的。想要了解更多可以看:NSGraphicsContext Class Reference。
在你获取到这个图形上下文之后,你可以调用任何Quartz提供的绘图方法。你还可以混合使用Quartz 2D 和 Cocoa 绘图方法。图 2-1 展示了一个在Cocoa View 上的绘制,它是由两个部分重叠的矩形构成的,一个完全不透明的红色矩形和一个半透明的蓝色矩形。你可以在颜色和色域中了解更多。你能透过颜色看到多少内容是 Quartz 2D 的一个特征。
图 2-1 Cocoa框架中一个包含Quartz绘图的View想要完成 图 2-1 绘制的,首先在Xcode中创建一个Cocoa应用程序工程;然后在Interface Builder中拖拽一个自定义的View到window并子类化它;接着,在相关的子类实现中写下如 列表 2-1 中的代码。在这个例子中,这个view的类叫做MyQuartzView。该view的drawRect:方法包含了所有的Quartz绘图代码。在列表下面有详细的解释。
注意:NSView的drawRect:方法在每次需要被绘制的时候都会自动调用。想了解更多关于重写drawRect:方法的信息,请见:NSView Class Reference。
列表 2-1 在窗口图形上下文绘制
@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
下面解释这些代码做了什么:
- 获取view的图形上下文。
- 你需要把绘图的代码写到这个地方。接下来的4行使用了 Quartz 2D 的函数。
- 将填充色设置为一个完全不透明的红色。关于颜色和alpha(设置透明度),详见:颜色和色域。
- 填充了一个矩形,这个矩形原点在(0,0),宽度为200,高度为100。关于绘制矩形的更多内容见:路径(Paths)。
- 设置一个半透明的蓝色为填充色。
- 填充了一个原点在(0,0),宽度为100,高度为200的矩形。
创建一个PDF图形上下文(PDF Graphics Context)
当你创建一个PDF图形上下文并绘制时,Quartz将你的绘制记录成一系列的绘图指令,并将其写入到一个文件。你需要为这个PDF文件指定一个输出位置和默认的媒体盒(定义这个页(Page)边界的矩形)。图 2-2 展示了用PDF预览将使用 PDF Graphics Context 绘制的结果打开的情形。
图 2-2 用 CGPDFContextCreateWithURL 创建的PDF文件Quartz 2D 的API 提供了两种函数来创建一个PDF图形上下文:
- CGPDFContextCreateWithURL,当你想为输出的PDF文件指定一个路径时(CoreFoundation URL)。列表 2-2 展示了如何用这个函数创建PDF图形上下文。
- CGPDFContextCreate,当你想把PDF输出送到一个消费者(Consumer)处理的时候(可以在Quartz 2D的数据管理中查看更多)。列表 2-3 展示了如何用这个函数创建PDF图形上下文。
对于每个标注了数字的代码行,都会有详细的解释。
iOS 注意:在iOS中的PDF图形上下文使用了Quartz提供的默认坐标,并没有应用一个变换去适配UIKit的坐标。如果你的应用程序计划在PDF图形上下文和UIView提供的图形上下文之间共享绘制代码,你的应用应当修改PDF图形上下文的CTM从而修改坐标系。详见:Quartz 2D Coordinate Systems。
列表 2-2 调用CGPDFContextCreateWithURL来创建PDF图形上下文
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
}
代码解释:
- 调用了 Core Foundation 的函数来用CFString创建一个CFURL对象,这个对象稍后会被用于MyPDFContextCreate函数。对于CFURLCreateWithFileSystemPath,你传入第一个参数为NULL时使用默认的分配器(allocator);同时你需要指定路径的样式,在这个例子中是一个POSIX-style的路径名。
- 调用Quartz 2D 的函数来创建PDF图形上下文,会使用到刚刚创建的路径和一个指示PDF页(Page)大小的矩形,这个矩形在调用MyPDFContextCreate时由外部传入,他表示了默认的PDF边界大小。
- 释放CFURL对象。
- 返回创建的PDF图形上下文对象。调用者在不使用的时候必须释放这个图形上下文。
列表 2-3 调用CGPDFContextCreate来创建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
}
代码解释:
- 调用了 Core Foundation 的函数来用CFString创建一个CFURL对象,这个对象稍后会被用于MyPDFContextCreate函数。对于CFURLCreateWithFileSystemPath,你传入第一个参数为NULL时使用默认的分配器(allocator);同时你需要指定路径的样式,在这个例子中是一个POSIX-style的路径名。
- 使用CFURL对象,创建了一个Quartz data consumer 对象。如果你不想使用CFURL对象(例如你想要放置的输出数据不能指定CFURL对象时),你可以利用一系列的回掉函数(callback functions)创建一个data consumer来代替CFURL。详见: Quart 2D 中的数据管理。
- 调用Quartz 2D的函数来创建PDF图形上下文。
- 释放data cunsumer。
- 释放CFURL对象。
- 返回创建的PDF图形上下文对象。调用者在不使用的时候必须释放这个图形上下文。
列表 2-4 展示了MyPDFContextCreate 的使用和绘制。
列表 2-4 在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的media box 的矩形。
- 设置矩形的原点、宽度和高度。
- 调用函数MyPDFContextCreate(列表 2-3)获取一个PDF图形上下文,并传入正确的 media box 和路径。CFSTR可以将一个字符串转换成CFStringRef类型。
- 设置page的选项。在这个例子中,只有media box 是指定了值的。你不需要传入相同的矩形来设置PDF图形上下文。因为media box 取代了你传入设置PDF图形上下文的矩形。
- 开始一个page的信号。这个方法用于面向页面的绘图,即PDF绘图。
- 调用Quartz 2D的绘图函数。你可以替换接下来的这四行,来画你想画的东西。
- PDF page结束的信号。
- 在选项字典和PDF图形上下文不使用时释放它们。
你能够在PDF中写下任意的内容(对于你的应用来说合适的内容)。如:图片、文字、基于路径的绘图、链接、编码等等。更多信息请见:PDF文档的创建、预览和转换。
创建一个位图图形上下文(Bitmap Graphics Context)
位图图形上下文接收一个指向内存的指针,这片指向的区域中包含了位图(bitmap)。当你在位图图形上下文中绘制时,这些字节流会得到更新,当你释放这个图形上下文时,你就在你指定的像素格式下完全更新了这个bitmap。
注意:位图图形上下文有时候也被用来做离屏绘制。在你决定这么做之前,看看:核心图形图层绘画(Core Graphics Layer Drawing)。CGLayer 对象(CCGLayerRef)针对离屏绘制有特定的优化,因为在可能的时候它将缓存 video card 上的涂层。
iOS 注意:iOS中的应用程序应当使用UIGraphicsBeginImageContextWithOptions方法而不是这里描述的Quartz低级方法来获取图形上下文。如果你的应用程序使用Quartz提供的API创建了一个离屏的bitmap,那么这么位图图形上下文会使用Quartz默认的坐标系。作为对比,如果你使用 UIGraphicsBeginImageContextWithOptions 来创建图形上下文,那么UIKit会对其(位图图形上下文)使用与从UIView获取图形上下文相同的变换。这就允许你使用相同的坐标系来绘制。虽然你的应用程序能够人为地来完成这些补偿,但是在实际中,这样做没有任何好处。
你可以使用CGBitmapContextCreate函数来创建一个位图图形上下文,这个函数接收下列的参数:
- 数据(data)。提供一个指向内存的指针,指向的这个地方是你想要存放绘图渲染的地方;这块内存的大小至少要有(bytesPerRow*height)字节。
- 宽度(width)。指定位图的像素宽度(单位是像素)。
- 高度(height)。指定位图图像的高度(单位是像素)。
- 组成部分的位数(bitsPerComponent)。指定每一个像素中组件的位数(bits,二进制位)。例如:对于32位格式的RGB色域,你需要为每一个部分指定8位。见:本章中的表“支持的像素格式”。
- 每行的字节数(bytesPerRow)。指定内存中位图每一行的字节数。
注意:当你创建一个位图图形上下文后,如果你确保数据(data)和每行的字节数(bitsPerCompoent)是16为对齐的,那么你将获得最佳的性能。
- 色域(colorspace)。位图图形上下文所使用的色域,你可以提供灰阶(Gray)、RGB、CMYK或者NULL这些色域值给位图图形上下文。更多关于色域和颜色管理的细节,见:Color Management Overview。更多关于颜色创建的信息见:颜色和色域。更多支持的色域见:位图图像和图像遮罩。
- 位图信息(bitmapInfo)。位图的布局信息,通常用一个CGBitmapInfo的常量来表示。这个信息指明了位图是否包含alpha、alpha值在一个像素中的位置、alpha值是否是预相乘的(premultiplied)以及颜色的组成到底是浮点还是整数值等等。想要知道这些常量是什么、什么时候使用、Quartz支持的位图图形上下文和图片的像素格式等信息,查看:位图图像和图像遮罩章节的色域和位图布局。
列表 2-5 展示了如何创建一个位图图形上下文。当你在位图图形上下文中绘制时,Quartz记录你的绘图操作,并将其转换数据存放到指定的内存区域中。
列表 2-5 创建一个位图图形上下文
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
}
代码解释:
- 声明代表每行有多少字节的变量。在这个例子中bitmap中的一个像素由4个字节表示,8位的红、绿、蓝和alpha。
- 创建了一个普通的RGB色域,当然你也可以创建其他的色域如CMYK。在颜色和色域中你能了解到普通的(generic)色域是依赖于设备的。
- 调用calloc函数来创建一个用于存储bitmap数据的内存块。在这个例子中创建了一个32位的bitmap(32位表示一个像素,每个像素都包含8位的红、绿、蓝和aplha值)。不难看出每一个像素占用了4个字节(1个字节8位)。在 Mac OS 10.6 和 iOS 4 以及之后的版本中,你可以将bitmap data 传为NULL,此时Quartz将自动为你分配bitmap的存储空间。
- 调用方法创建一个位图图形上下文,传入相应的参数。常量kCGImageAlphaPremultipliedLast指明了alpha值被存储在每个像素的最后一个字节,同时它也指明了其中的颜色早已经与alpha值相乘过了。关于alpha值的预乘(premultiplied alpha)见:The Alpha Value。
- 如果上下文没有创建成功,释放申请的用来存放 bitmap data 数据的内存。
- 释放色域。
- 返回创建好的位图图形上下文。使用者需要释放这个图形上下文。
列表 2-6 使用先前创建的MyCreateBitmapContext函数来创建一个位图图形上下文,然后使用这个图形上下文创建了一个CGImage对象,接着将这个图片绘制到了一个窗口图形上下文上。
图 2-3 展示了绘制到窗口上的这个图片。
列表 2-6 在位图图形上下文上绘制
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
代码解释:
- 声明了一个定义了原点和尺寸的变量box,这个变量将被用于绘制由位图图形上下文产生的图片。
- 设置这个box的原点和宽高。
- 调用自定义的函数MyCreateBitmapContext(列表 2-5)来创建了一个宽为400px高为300px的像素。你使用任意尺寸来创建位图图形上下文。
- 调用Quartz 2D的函数来在这个位图图形上下文中绘制。你能够自由替换下面4行绘制代码。
- 从位图图形上下文中创建了一个Quartz 2D 图片(CGImageRef)。
- 将这个图片绘制到box指定的窗口图形上下文中,这个有边界的box指示的坐标和尺寸是在用户空间下的(user space)。例子中没有展示如何创建窗口图形上下文,可以看本章中的在 Mac OS X 中创建一个窗口图形上下文。
- 获取和这个位图图形上下文关联的 bitmap data 指针。
- 释放位图图形上下文。
- 如果bitmap data 存在则释放它。
- 在不使用的时候释放图片。
支持的像素格式(Supported Pixel Formats)
表 2-1 概括了位图图形上下文支持的像素格式、相关的色域(color space)、Mac OS X中的可用性。像素格式由每个像素的位数(bits per pixel -- bpp)和每个部分的位数(bits per component -- bpc)表示。表中同样包含了与像素格式关联的位图信息常量(bitmap information constant)。想要了解每一个常量代表什么可以查看:CGImage Reference。
表 2-1 位图图形上下文支持的像素格式
色域(Color Space) | 像素格式和位图信息常量(Pixel format and bitmap information constant) | 可用性 |
---|---|---|
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 |
抗锯齿(Anti-Aliasing)
位图图形上下文支持抗锯齿。抗锯齿是对绘制文字或者图形时边缘出现锯齿状情况的纠正。当位图的分辨率大大低于人眼的分辨率时,就会出现锯齿。为了使绘制的内容变得光滑,Quartz对包裹在对应图形形状外的像素使用了不同的颜色。通过这样的混色方式,这些形状看起来就光滑了一些。你能够从 图 2-4 中看出来抗锯齿的效果。同样,你可以在绘制特殊的位图图形上下文时关闭抗锯齿(通过CGContextSetShouldAntialias方法)。这个抗锯齿设置是图形状态的(graphics state)一部分。
你同样也能够使用CGContextSetAllowsAntialiasing方法针对一个特殊的图形上下文设置其是否抗锯齿。当函数的参数传入true时则允许抗锯齿。这个设置不属于图形状态(graphics state)。只有当图形状态和当前上下文中的抗锯齿都设置为true时,Quartz才会进行抗锯齿操作。
图 2-4 抗锯齿的使用与否对比获取打印使用的图形上下文
在 Mac OS X 中的Cocoa应用程序通过自定义的NSView子类来实现打印。想要打印一个view,直接调用它的print:方法;随后,这个view创建了一个针对打印的图形上下文并调用它的drawRect:方法。对于绘制到屏幕和打印机,你的程序使用了相同的绘图代码。同样也可自定义drawRect:方法,图片这样对于打印机的调用是不同于屏幕的(原文:It can also customize the drawRect: call to an image to the printer that is different from the one sent to the screen.)。
对于更多在Cocoa中打印的资料,请查看:Printing Programming Guide for Mac。