Quartz 2D Programming Guide

在iOS中,Quartz 2D适用于所有可用的图形和动画技术,如Core animation、OpenGL ES和UIKit类。

图形上下文(graphics context)是一种不透明的数据类型(CGContextRef),它封装了Quartz用于将图像绘制到输出设备(如PDF文件、位图或显示窗口)的信息。图形上下文中的信息包括图形绘图参数和页面上特定于设备的绘图表示。Quartz中的所有对象都被绘制到图形上下文中,或由图形上下文中包含。

这些graphics context可用于您的应用程序:
1.位图(bitmap)图形上下文允许您将RGB颜色、CMYK颜色或灰度绘制为位图。位图是像素的矩形阵列(或栅格),每个像素代表图像中的一个点。位图图像也称为采样图像。
2.PDF图形上下文允许您创建PDF文件。在PDF文件中,您的绘图保存为命令序列。PDF文件和位图之间有一些显著的区别:
2-1.与位图不同,PDF文件可能包含多个页面。
当您从另一个设备上的PDF文件中绘制页面时,生成的图像将针对该设备的显示特性进行优化。
2-2.PDF文件的分辨率与自然无关——绘制文件的大小可以无限增加或减少,而不会牺牲图像的细节。位图图像的用户感知质量与要查看位图的分辨率有关。
3.层上下文(layer context)(CGLayerRef)是与另一个图形上下文关联的屏幕外绘图目的地。当将层绘制到创建它的图形上下文时,它是为了获得最佳性能而设计的。与位图图形上下文相比,层上下文在屏幕外绘图时是更好的选择。

除了图形上下文之外,Quartz 2D API还定义了各种不透明数据类型。因为API是核心图形框架的一部分,所以数据类型和操作它们的例程使用CG前缀。

Quartz 2D的不透明数据类型
Quartz 2D从应用程序操作以实现特定绘图输出的不透明数据类型创建对象。图1-3显示了将绘图操作应用到Quartz 2D提供的三个对象时可以获得的结果。例如:

您可以通过创建PDF页面对象、对图形上下文应用旋转操作以及请求Quartz 2D将页面绘制到图形上下文来旋转和显示PDF页面。
您可以通过创建图案(pattern)对象、定义构成图案(pattern)的形状以及设置Quartz 2D来绘制图案(pattern),以便在将图案(pattern)绘制到图形上下文中时将其用作绘图。
您可以通过创建一个着色对象(提供一个确定着色中每个点的颜色的函数)来用轴向或径向着色填充一个区域,然后要求Quartz 2D将该着色用作填充颜色。


图1-3

CGPathRef,用于矢量图形,用于创建填充或描边的路径。
CGImageRef,用于表示位图图像和基于您提供的示例数据的位图图像掩码。
CGLayerRef,用于表示可用于重复绘制(如背景或图案)和离屏绘制(offscreen drawing)的绘图层。
CGPatternRef,用于重复绘制。
CGShadingRef和CGGradientRef,用于绘制渐变。
CGFunctionRef,用于定义接受任意数量浮点参数的回调函数。当你为阴影创建渐变时,你使用这个数据类型。
CGColorRef和CGColorSpaceRef,用于通知Quartz如何解释颜色。
CGImageSourceRef和CGImageDestinationRef,它们用于将数据移入和移出Quartz。
CGFontRef,用于绘制文本。
CGPDFDictionaryRef、CGPDFObjectRef、CGPDFPageRef、CGPDFStream、CGPDFStringRef和CGPDFArrayRef,提供对PDF元数据的访问。
CGPDFScannerRef和CGPDFContentStreamRef,它们解析PDF元数据。

Quartz根据当前图形状态中的参数修改绘图操作的结果。图形状态包含参数,否则这些参数将作为绘制例程的参数。绘制图形上下文的例程参考图形状态以确定如何呈现结果。例如,当您调用一个函数来设置填充颜色时,您正在修改存储在当前图形状态中的值。当前图形状态的其他常用元素包括行宽、当前位置和文本字体大小。

图形上下文包含一组图形状态。当Quartz创建图形上下文时,堆栈是空的。保存图形状态时,Quartz将当前图形状态的副本推入堆栈。当您恢复图形状态时,Quartz将图形状态弹出堆栈顶部。弹出状态变为当前图形状态。

要保存当前图形状态,请使用函数CGContextSaveGState将当前图形状态的副本推送到堆栈上。要恢复以前保存的图形状态,请使用函数CGContextRestoreGState将当前图形状态替换为堆栈顶部的图形状态。

注意,并不是当前绘图环境的所有方面都是图形状态的元素。例如,当前路径不被认为是图形状态的一部分,因此在调用函数CGContextSaveGState时不会保存它。调用此函数时保存的图形状态参数列在图1-4中。


图1-4

Quartz 2D坐标系、
一个坐标系统,如图1-4所示,定义了用来表示要在页面上绘制的对象的位置和大小的位置范围。在用户空间坐标系统中指定图形的位置和大小,或者更简单地说,在用户空间中指定图形的位置和大小。坐标被定义为浮点值。


图1-4

由于不同的设备具有不同的底层成像能力,图形的位置和大小必须以独立于设备的方式定义。例如,屏幕显示设备可能每英寸显示不超过96像素,而打印机可能每英寸显示300像素。如果您在设备级别定义了坐标系统(在本例中,是96像素还是300像素),那么在该空间中绘制的对象就不能在其他设备上复制,而不会造成可见的失真。它们会显得太大或太小。
Quartz使用一个单独的坐标系统—用户空间—使用当前转换矩阵(CTM)将其映射到输出设备空间的坐标系统—设备空间—来实现设备独立性。矩阵是用来有效地描述一组相关方程的数学构造。当前的转换矩阵是一种特殊类型的矩阵,称为仿射变换,它通过应用平移、旋转和缩放操作(移动、旋转和调整坐标系统大小的计算)将点从一个坐标空间映射到另一个坐标空间。
当前的转换矩阵还有一个次要的用途:它允许您转换对象的绘制方式。例如,要绘制一个旋转了45度的框,您需要在绘制框之前旋转页面的坐标系统(CTM)。Quartz使用旋转坐标系统绘制输出设备。
用户空间中的一个点用坐标对(x,y)表示,其中x表示沿水平轴(左和右)的位置,y表示垂直轴(上和下)。用户坐标空间的原点是点(0,0)原点位于页面的左下角,如图1-4所示。在Quartz的默认坐标系统中,当x轴从页面的左边移动到右边时,它会增加。当y轴从底部移动到页面顶部时,它的值会增加。
有些技术使用不同于Quartz使用的默认坐标系统来设置图形上下文。相对于Quartz,这样的坐标系是经过修改的坐标系,在进行一些Quartz拉拔操作时必须进行补偿。最常见的修改坐标系统将原点放在上下文中的左上角,并将y轴改为指向页面底部。你可能会在以下几个地方看到这个特定的坐标系:
1.在iOS中,由UIView返回的绘图上下文。
2.在iOS中,通过调用UIGraphicsBeginImageContextWithOptions函数创建的绘图上下文。
UIKit返回带有修改坐标系统的Quartz绘图上下文的原因是UIKit使用不同的默认坐标约定;它将转换应用于它创建的Quartz上下文,以便它们与约定匹配。如果您的应用程序要使用相同的绘图程序画UIView对象和一个PDF图形上下文(由Quartz和使用默认坐标系),您需要应用一个变换,PDF格式的图形上下文接收相同的修改坐标系统。为此,应用一个转换,将原点转换为PDF上下文的左上角,并将y坐标缩放-1。
使用缩放变换来消除y坐标会改变Quartz绘图中的一些约定。例如,如果您调用CGContextDrawImage来将图像绘制到上下文中,那么在将图像绘制到目标中时,转换将对图像进行修改。类似地,路径绘制例程接受指定在默认坐标系中是顺时针还是逆时针绘制弧的参数。如果坐标系统被修改,结果也会被修改,就像图像被反射到镜子里一样。在图1-5中,将相同的参数传递到Quartz中,会在默认坐标系中产生顺时针的电弧,而在y坐标被转换否定后,会产生逆时针的电弧。


图1-5

您的应用程序可以调整它对应用于它的转换的上下文的任何Quartz调用。例如,如果希望将图像或PDF正确绘制到图形上下文中,应用程序可能需要临时调整图形上下文中的CTM。在iOS中,如果使用UIImage对象包装创建的CGImage对象,则不需要修改CTM。UIImage对象自动补偿UIKit应用的修改后的坐标系。
用这种写法:

    CGContextRotateCTM(context, M_PI);
    CGContextScaleCTM(context, -1, 1);
    CGContextTranslateCTM(context, 0, -image.size.height);

内存管理:对象所有权
Quartz使用核心基础内存管理模型,在该模型中对对象进行引用计数。创建Core Foundation对象时,引用计数为1。可以通过调用函数来保留对象来增加引用计数,通过调用函数来释放对象来减少引用计数。当引用计数降为0时,对象被释放。这个模型允许对象安全地共享对其他对象的引用。
有一些简单的规则需要牢记:
1.如果您创建或复制一个对象,那么您拥有它,因此您必须释放它。也就是说,一般来说,如果您从一个函数中获得一个名称中带有“Create”或“Copy”字样的对象,则在使用该对象时必须释放该对象。否则,将导致内存泄漏。
2.如果您从一个函数中获得一个对象,该函数的名称中不包含“创建”或“复制”字样,则您不拥有对该对象的引用,并且您不能释放该对象。该对象将在未来的某个时候由其所有者释放。
如果您不拥有一个对象,并且需要保留它,那么您必须保留它,并在处理完之后释放它。您可以使用特定于对象的Quartz 2D函数来保留和释放该对象。例如,如果接收到对CGColorspace对象的引用,则使用CGColorSpaceRetain和CGColorSpaceRelease函数来根据需要保留和释放该对象。您还可以使用核心基础函数CFRetain和CFRelease,但必须小心不要将NULL传递给这些函数。

绘制到iOS中的视图图形上下文
要在iOS应用程序中绘制屏幕,您需要设置一个UIView对象并实现它的drawRect:方法来执行绘制。当视图在屏幕上可见且其内容需要更新时,调用视图的drawRect:方法。在调用定制的drawRect:方法之前,view对象会自动配置它的绘图环境,这样您的代码就可以立即开始绘图了。作为这个配置的一部分,UIView对象为当前绘图环境创建一个图形上下文(CGContextRef 不透明类型)。通过调用UIKit函数UIGraphicsGetCurrentContext,您可以在drawRect:方法中获得这个图形上下文。
UIKit中使用的默认坐标系与Quartz使用的坐标系不同。在UIKit中,原点位于左上角,正y值向下指向。UIView对象修改Quartz图形上下文的CTM,以匹配UIKit约定,方法是将原点转换到视图左上角,并将y轴反转,将其乘以-1。

创建一个PDF图形上下文
当您创建一个PDF图形上下文并绘制到该上下文时,Quartz会将您的绘图记录为一系列写入文件的PDF绘图命令。您为PDF输出提供了一个位置和一个默认媒体框——一个指定页面边界的矩形。图2-2显示了绘制到PDF图形上下文的结果,然后在预览中打开生成的PDF。


图2-2

Quartz 2D API提供了两个创建PDF图形上下文的功能:
CGPDFContextCreateWithURL,当您希望将PDF输出的位置指定为一个Core Foundation URL时,可以使用它。

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图形上下文。当不再需要图形上下文时,调用者必须释放它。

CGPDFContextCreate,当您希望将PDF输出发送给数据使用者时,可以使用它。

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
}

下面是代码的作用:
1.调用Core Foundation函数,从提供给MyPDFContextCreate函数的CFString对象中创建一个CFURL对象。将NULL作为使用默认分配器的第一个参数。还需要指定路径样式,在本例中,路径样式是posix样式的路径名。
2.使用CFURL对象创建一个Quartz数据使用者对象。如果您不想使用CFURL对象(例如,您想将PDF数据放在CFURL对象无法指定的位置),那么您可以从应用程序中实现的一组回调函数中创建一个数据使用者。
3.调用Quartz 2D函数来创建一个PDF图形上下文,将数据使用者和传递给MyPDFContextCreate函数的矩形(类型为CGRect)作为参数传递。这个矩形是PDF的默认页面媒体包围框。
4.释放数据使用者。
5.释放CFURL对象。
6.返回PDF图形上下文。当不再需要图形上下文时,调用者必须释放它。

演示如何调用MyPDFContextCreate例程并绘制它。

    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);

下面是代码的作用:
1.为用于定义PDF媒体框的矩形声明一个变量。
2.将媒体框的原点设置为(0,0),将宽度和高度设置为应用程序提供的变量。
3.调用函数MyPDFContextCreate来获得一个PDF图形上下文,提供一个媒体框和一个路径名。宏CFSTR将字符串转换为CFStringRef数据类型。
4.设置带有页面选项的字典。在本例中,只指定了媒体框。您不必传递与设置PDF图形上下文时相同的矩形。这里添加的媒体框将取代您为设置PDF图形上下文而传递的矩形。
5.标志着页的开始。这个函数用于面向页面的图形,这就是PDF绘图。
6.调用Quartz 2D绘图函数。您可以用适合您的应用程序的绘图代码替换这段代码和以下四行代码。
7.标志着PDF页面的结束。
8.在不再需要词典和PDF图形上下文时释放它们。

创建位图图形上下文
位图图形上下文接受指向包含位图存储空间的内存缓冲区的指针。当您绘制到位图图形上下文时,缓冲区被更新。在您发布图形上下文之后,您将得到一个完全更新的像素格式的位图。

注意:位图图形上下文有时用于屏幕外绘制。在您决定为此目的使用位图图形上下文之前。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颜色空间。
bitmapInfo。位图布局信息,表示为CGBitmapInfo常量,它指定位图是否应该包含alpha组件、像素中alpha组件的相对位置(如果有的话)、alpha组件是否预乘、颜色组件是否为整数或浮点值。

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
}

下面是代码的作用:

1.声明一个变量来表示每行的字节数。本例中位图中的每个像素都由4个字节表示;8位,红,绿,蓝,和透明度。
2.创建一个通用的RGB颜色空间。您还可以创建一个CMYK颜色空间。
3.调用calloc函数来创建和清除存储位图数据的内存块。这个示例创建一个32位的RGBA位图(也就是说,一个每个像素有32位的数组,每个像素包含8位红色、绿色、蓝色和alpha信息)。位图中的每个像素占用4字节的内存。在Mac OS X 10.6和iOS 4中,这个步骤可以被忽略——如果您将NULL作为位图数据传递,Quartz会自动为位图分配空间。
4.创建位图图形上下文,提供位图数据、位图的宽度和高度、每个组件的比特数、每行字节数、颜色空间和一个常量,用于指定位图是否应该包含alpha通道及其在像素中的相对位置。常量kCGImageAlphaPremultipliedLast表示alpha组件存储在每个像素的最后一个字节中,颜色组件已经乘以这个alpha值。
5.如果由于某种原因没有创建上下文,则释放为位图数据分配的内存。
6.释放颜色空间。
7.返回位图图形上下文。当不再需要图形上下文时,调用者必须释放它。
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);// 10

下面是代码的作用:

1.声明一个变量,以存储Quartz将在其中绘制从位图图形上下文创建的图像的边界框的原点和尺寸。
2.将边界框的原点设置为(0,0),将宽度和高度设置为前面声明的变量,但是代码中没有显示其声明。
3.调用应用程序提供的函数MyCreateBitmapContext(请参见清单2-5)来创建一个宽400像素、高300像素的位图上下文。您可以使用适合您的应用程序的任何维度创建位图图形上下文。
4.调用Quartz 2D函数来绘制位图图形上下文。您可以用适合您的应用程序的绘图代码替换这段代码和接下来的四行代码。
5.从位图图形上下文创建一个Quartz 2D图像(CGImageRef)。
6.将图像绘制到由边框框指定的窗口图形上下文中的位置。边界框指定在用户空间中绘制图像的位置和尺寸。
7.获取与位图图形上下文关联的位图数据。
8.在不再需要位图图形上下文时释放它。
9.释放位图数据,如果它存在。
10.当不再需要时释放图像。

抗锯齿
位图图形上下文支持抗锯齿,这是在绘制文本或图形时,人为地纠正在位图图像中有时看到的锯齿(或锯齿)边缘的过程。当位图的分辨率明显低于眼睛的分辨率时,就会出现这些锯齿状的边缘。为了使对象在位图中看起来平滑,Quartz为形状轮廓周围的像素使用不同的颜色。通过这种方式混合颜色,形状看起来很平滑。在图2-4中可以看到使用抗锯齿的效果。通过调用CGContextSetShouldAntialias函数,可以为特定的位图图形上下文关闭反锯齿。反走样设置是图形状态的一部分。

通过使用CGContextSetAllowsAntialiasing函数,您可以控制是否允许对特定图形上下文进行反锯齿。将true传递到此函数以允许反锯齿;false的不允许。这个设置不是图形状态的一部分。当上下文和图形状态设置设置为true时,Quartz执行反锯齿。


图2-4


点是在用户空间中指定位置的x和y坐标。您可以调用函数CGContextMoveToPoint来指定新子路径的起始位置。Quartz跟踪当前点,这是最后一个用于路径构建的位置。例如,如果您调用函数CGContextMoveToPoint来设置一个位置(10,10),那么将当前点移动到(10,10)。如果你画一条长50单位的水平线,这条线上的最后一点,也就是(60,10),变成了现在的点。线、弧和曲线总是从当前点开始绘制。

大多数情况下,通过向Quartz函数传递两个浮点值来指定x和y坐标,从而指定一个点。有些函数需要传递CGPoint数据结构,该结构包含两个浮点值。

线
直线是由它的端点定义的。它的起始点总是假定为当前点,因此当您创建一条直线时,您只指定它的端点。您可以使用函数CGContextAddLineToPoint向子路径追加一行。

您可以通过调用函数CGContextAddLines向路径添加一系列连接的行。你给这个函数传递一个点数组。第一点必须是第一行的起点;其余的点是端点。Quartz从第一个点开始一个新的子路径,并将直线段连接到每个端点。

    CGMutablePathRef path = CGPathCreateMutable();
    CGPoint points1[] = {CGPointMake(10, 100), CGPointMake(60, 50), CGPointMake(60, 150), CGPointMake(110, 100)};
    CGPathAddLines(path, nil, points1, sizeof(points1)/sizeof(points1[0]));
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path;
    layer.fillColor = [UIColor redColor].CGColor;
    layer.strokeColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:layer];
    CGPathRelease(path);


弧是圆段。Quartz提供了两个创建弧线的函数。函数CGContextAddArc从一个圆创建一个曲线段。指定圆的中心、半径和径向角(以弧度表示)。您可以通过指定2pi的径向角来创建一个完整的圆。

函数CGContextAddArcToPoint是理想的用于圆角矩形。Quartz使用您提供的端点创建两条切线。您还可以提供Quartz切割弧的圆的半径。圆弧的中心点是两个半径的交点,每个半径都垂直于两条切线中的一条。弧的每个端点都是其中一条切线上的一个切点,如图3-5所示。圆圈的红色部分是实际画出来的。


图3-5
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 100, 300);
    CGPathAddArcToPoint(path, nil, 200, 200, 100, 100, 50);
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path;
    layer.fillColor = [UIColor redColor].CGColor;
    layer.strokeColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:layer];
    CGPathRelease(path);

效果如图


CGPathAddArcToPoint效果图

曲线
二次曲线和三次贝塞尔曲线是可以指定任意数量的有趣曲线形状的代数曲线。这些曲线上的点是用多项式公式计算起始点和结束点,以及一个或多个控制点。以这种方式定义的形状是矢量图形的基础。一个公式比一个位数组存储起来要紧凑得多,而且它的优点是可以在任何分辨率下重新创建曲线。

在许多数学文本和描述计算机图形学的在线资源中讨论了产生二次和三次贝塞尔曲线的多项式公式,以及如何从这些公式生成曲线的细节。这里不讨论这些细节。

使用函数CGContextAddCurveToPoint从当前点追加一个三次Bezier曲线,使用指定的控制点和端点。图3-7显示了由图中所示的当前点、控制点和端点生成的三次Bezier曲线。两个控制点的位置决定了曲线的几何形状。如果控制点都在起始点和结束点之上,曲线就向上拱起。如果控制点都在起始点和结束点以下,曲线就向下弯曲。


图3-7

通过调用函数CGContextAddQuadCurveToPoint并指定控制点和端点,可以从当前点追加一个二次Bezier曲线。图3-8显示了使用相同端点但不同控制点所得到的两条曲线。控制点决定曲线拱起的方向。因为二次曲线只使用一个控制点,所以用二次贝塞尔曲线不可能像用三次曲线那样创建那么多有趣的形状。例如,不可能创建一个交叉使用单一控制点。


图3-8

关闭一个子路径
要关闭当前子路径,应用程序应该调用CGContextClosePath。这个函数添加了一条从当前点到子路径起始点的线段,并关闭子路径。在子路径的起始点结束的线、弧和曲线实际上并没有关闭子路径。必须显式调用CGContextClosePath关闭子路径。说人话就是当前点到和初始点连一根线。

一些Quartz函数将路径的子路径视为应用程序关闭的路径。这些命令将每个子路径视为您的应用程序调用CGContextClosePath来关闭它,隐式地向子路径的起点添加一个线段。

在关闭子路径之后,如果应用程序发出额外的调用,向路径添加线条、弧线或曲线,Quartz将从刚刚关闭的子路径的起点开始新的子路径。

椭圆
椭圆实质上是一个压扁的圆。你通过定义两个焦点,然后画出所有距离上的点,使得从椭圆上任意一点到一个焦点的距离,加上从这个焦点到另一个焦点的距离,总是相同的值。

您可以通过调用函数CGContextAddEllipseInRect向当前路径添加一个椭圆。提供一个定义椭圆边界的矩形。Quartz用一系列贝塞尔曲线近似椭圆。椭圆的中心是矩形的中心。如果矩形的宽度和高度相等(即正方形),则椭圆为圆形,半径等于矩形宽度(或高度)的一半。如果矩形的宽度和高度不相等,则定义椭圆的主轴和副轴。

添加到路径中的椭圆以移动到操作开始,以关闭子路径操作结束,所有移动都是顺时针方向的。

矩形
您可以通过调用CGContextAddRect函数向当前路径添加一个矩形。提供一个CGRect结构,该结构包含矩形的原点及其宽度和高度。

添加到路径中的矩形以移动到操作开始,以关闭子路径操作结束,所有移动都是逆时针方向的。

创建一个路径

当您想在图形上下文中构造路径时,您可以通过调用函数CGContextBeginPath对Quartz发出信号。接下来,通过调用函数CGContextMoveToPoint为路径中的第一个形状或子路径设置起点。建立第一个点后,可以在路径上添加直线、弧线和曲线,请记住:

在开始新路径之前,调用函数CGContextBeginPath。
从当前点开始绘制直线、弧和曲线。空路径没有当前点;您必须调用CGContextMoveToPoint来设置第一个子路径的起始点,或者调用一个便利函数来隐式地执行此操作。
当您想要关闭路径中的当前子路径时,调用函数CGContextClosePath将一个段连接到子路径的起始点。后续路径调用开始一个新的子路径,即使您没有显式地设置一个新的起点。
当您绘制弧线时,Quartz在弧线的当前点和起始点之间绘制一条线。
添加椭圆和矩形的石英例程向路径添加一个新的封闭子路径。
您必须调用绘图函数来填充或描边路径,因为创建路径不会绘制路径。有关详细信息,请参见绘制路径。
在绘制路径之后,它将从图形上下文刷新。你可能不想这么容易迷路,尤其是当它描述了一个复杂的场景,你想一遍又一遍地使用。因此,Quartz提供了两种数据类型来创建可重用路径—cgpathref和CGMutablePathRef。您可以调用函数CGPathCreateMutable来创建一个可变的CGPath对象,您可以向该对象添加线、弧、曲线和矩形。Quartz提供了一组与构建块中讨论的函数并行的CGPath函数。路径函数操作的是CGPath对象,而不是图形上下文。这些函数是:

CGPathCreateMutable,replacesCGContextBeginPath
CGPathMoveToPoint,替换CGContextMoveToPoint
CGPathAddLineToPoint,替换CGContextAddLineToPoint
CGPathAddCurveToPoint,替换CGContextAddCurveToPoint
CGPathAddEllipseInRect,替换CGContextAddEllipseInRect
CGPathAddArc,替换CGContextAddArc
CGPathAddRect,替换CGContextAddRect
CGPathCloseSubpath替换了CGContextClosePat
(前文大多数都是对cgpath的操作。因为cgpath可以直接和layer一起用)
当您想要将路径附加到图形上下文时,您可以调用CGContextAddPath函数。该路径将停留在图形上下文中,直到Quartz绘制它。您可以通过调用CGContextAddPath再次添加路径。

画一个路径
您可以通过描边或填充或两者都绘制当前路径。描边画了一条横跨路径的线。填充绘制路径中包含的区域。Quartz有一些函数可以让您描边路径、填充路径,或者同时描边和填充路径。线条的特征(宽度、颜色等等)、填充颜色以及Quartz用于计算填充区域的方法都是图形状态的一部分

影响描边的参数
您可以通过修改表3-1中列出的参数来影响路径的描边方式。这些参数是图形状态的一部分,这意味着您为参数设置的值会影响随后的所有描边,直到您将参数设置为另一个值为止。


表3-1

Line width是线的总宽度,以用户空间的单位表示。这条线横跨路径,两边各占总宽度的一半.说人话就是比如lineWidth是1.绘制的时候是坐标左边0.5右边0.5。但是绘制的时候是一个像素一个像素绘制的。如下图:


大家好我是下图

line join指定Quartz如何绘制已连接线段之间的连接点。Quartz支持表3-2中描述的行连接样式。默认样式是miter连接。


表3-2
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容