Quartz 2D是iOS和OS X环境下的2D绘图引擎。涉及内容包括:基于路径的绘图,透明度绘图,遮盖,阴影,透明层,颜色管理,防锯齿渲染,生成PDF,以及PDF元数据相关处理。Quartz 2D也被称为Core Graphics,缩写为CG。 Quartz 2D与Core Graphics统称为Quartz。Quartz 2D接口是Core Graphics背后的二维图形库。
一.绘制视图
如果是自定义视图(UIView),则必须重写drawRect:方法,在此提供相应的绘制代码。
视图的绘制周期:
在iOS上绘制的时候比较麻烦。首先为需要绘制的视图或视图的部分区域设置一个需要绘制的标志,在事件循环的每一轮中,绘制引擎去检查是否有需要更新的内容,如果有就会调用视图的drawRect:方法进行绘制,因此我们需要绘制的视图中重写drawRect:方法。
只要iOS任务一个视图需要被刷新或者重绘,drawRect:方法就会被调用。这就意味着,drawRect:的调用频率很高,特别在动画及改变大小、位置的操作过程中,所以应当是极为轻量的。作为开发者,你绝对不应该从你的代码里直接调用drawRect:,也不要在这个方法里分配或者释放内存。
一旦drawRect:方法被调用,就可以使用任何的UIKit、Quartz 2D、OpenGL ES等技术对视图的内容进行绘制了。
绘图的过程中出了使用drawRect:方法,还有setNeedsDisplay:和setNeedsDisplayInRect:。setNeedsDisplay:和setNeedsDisplayInRect:方法是设置视图或者视图部分区域是否需要重写绘制,setNeedsDisplay:是重新绘制整个视图,setNeedsDisplayInRect:是重新绘制视图的部分区域。
原则上,尽量不要绘制视图的全部,以减少绘制带来的开销。触发视图重新绘制的动作有如下几种:
1.遮挡你的视图的其他视图被移动或删除操作的时候;
2.将视图的hidden属性声明设置为NO,使其从隐藏状态变为可见;
3.将视图滚出屏幕,然后再重新回到屏幕上;
4.显示调用视图的setNeedsDisplay:或setNeedsDisplayInRect:方法。
UIView中的绘制和更新视图方法:
- (void)drawRect:(CGRect)rect
- (void)setNeedsDisplay
- (void)setNeedsDisplayInRect:(CGRect)invalidRect
@property(nonatomic) CGFloat contentScaleFactor
内容比例因子:
比例因子决定了在视图内容从逻辑坐标空间(以点为单位)到硬件坐标空间(以像素为单位)的映射。这个值通常是1.0或2.0。例如,如果定标系数是2.0,并且视图帧大小为50×50点(points),用于呈现该内容的位图的大小为100×100像素(pixels)。
setNeedsDisplay和setNeedsLayout:
1.UIView的setNeedsDisplay和setNeedsLayout方法
综上所诉,setNeedsDisplay方便绘图,而layoutSubViews方便出来数据。
2.addSubview会触发layoutSubviews。
3.设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4.滚动一个UIScrollView会触发layoutSubviews。
5.旋转Screen会触发父UIView上的layoutSubviews事件。
6.改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
1.如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量值).
3.通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4.直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
以上1,2推荐;而3,4不提倡
2.若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
3.若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕
二.图形上下文
在调用drawRect:方法之前,视图对象会自动配置起绘制环境,是代码立即执行进行绘制。作为这些配置的一部分,UIView对象会为当前绘制环境创建一个图形上下文(对应于CGContextRef封装类型)。在前面的实例中就是采用这种默认方式的图形上下文。
我们也可以在drawRect:方法中通过(CGContextRef)UIGraphicsGetCurrentContext(void)函数获得访问图形上下文对象。图形上下文仅对当前的drawRect:方法调用有效,不要把图形上下文对象设置为成员变量。
- - (void) drawRect: (CGRect) rect {
- UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
- [[UIColor blueColor] setFill];
- [p fill];
- }
- - (void) drawRect: (CGRect) rect {
- CGContextRef con = UIGraphicsGetCurrentContext();
- CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
- CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
- CGContextFillPath(con);
- }
- @interface MyLayerDelegate : NSObject
- @end
- @implementation MyLayerDelegate
- - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx {
- UIGraphicsPushContext(ctx);
- UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
- [[UIColor blueColor] setFill];
- [p fill];
- UIGraphicsPopContext();
- }
- @end
- @interface MyView () {
- MyLayerDelegate* _layerDeleagete;
- }
- @end
- MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];
- CALayer *myLayer = [CALayer layer];
- _layerDelegate = [[MyLayerDelegate alloc] init];
- myLayer.delegate = _layerDelegate;
- [myView.layer addSublayer:myLayer];
- [myView setNeedsDisplay]; // 调用此方法,drawLayer: inContext:方法才会被调用。
- - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {
- CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
- CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
- CGContextFillPath(con);
- }
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
- UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
- [[UIColor blueColor] setFill];
- [p fill];
- UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
- CGContextRef con = UIGraphicsGetCurrentContext();
- CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
- CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
- CGContextFillPath(con);
- UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- - (void)drawRect:(CGRect)rect {
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- CGContextSaveGState(ctx);
- {
- // 绘图代码
- }
- CGContextRestoreGState(ctx);
- }
三.UIKit填充与描边
UIKit提供非常基本的绘图功能,主要的API有:
1.UIRectFill(CGRect rect),填充矩形函数;
2.UIRectFrame(CGRect rect),填充描边函数;
3.UIBezierPath,绘制常见路径类,包括线段、弧线、矩形、矩形、圆角矩形和椭圆的方法。
例如:
- (void)drawRect:(CGRect)rect
{
[[UIColor browColor] setFill];//为当前的图形上下文设置要填充的颜色
UIRectFill(rect);//按照刚才设置的颜色进行填充矩形
[[UIColor whiteColor] setStroke];//设置图形上下文白色描边
CGRect frame = CGRectMake(20,30,100,300);
UIRectFrame(frame);
}
UIRectFill(CGRect rect):向当前绘图环境所创建的内存中的图片上填充一个矩形。
UIRectFillUsingBlendMode(CGRect rect , CGBlendMode blendMode):向当前绘图环境所创建的内存中的图片上填充一个矩形,绘制使用指定的混合模式。
UIRectFrame(CGRect rect):向当前绘图环境所创建的内存中的图片上绘制一个矩形边框。
UIRectFrameUsingBlendMode(CGRect rect , CGBlendMode blendMode):向当前绘图环境所创建的内存中的图片上绘制一个矩形边框,绘制使用指定的混合模式。
上面4个方法都是直接绘制在当前绘图环境所创建的内存中的图片上,因此,这些方法都不需要传入CGContextRef作为参数。
UIKit虽然提供了UIBezierPath等类,但是对于线段、渐变、阴影、反锯齿等高性能特性支持还是不及Quartz 2D。
四.UIKit绘制图像
UIImage类中绘制图像主要的方法:
- (void)drawAtPoint:(CGPoint)point
设置绘制定点。
- (void)drawAtPoint:(CGPoint)point
blendMode:(CGBlendMode)blendMode
alpha:(CGFloat)alpha
设置绘制定点,并使用自定义混合选项。
- (void)drawInRect:(CGRect)rect
图片绘制在指定的矩形里。
- (void)drawInRect:(CGRect)rect
blendMode:(CGBlendMode)blendMode
alpha:(CGFloat)alpha
图片绘制在指定的矩形里,并使用自定义混合选项。
- (void)drawAsPatternInRect:(CGRect)rect
在指定的矩形里绘制图片,如果图片大小超出了指定矩形,形式上与-drawAtPoint:方法类似了,如果图片大小小于指定的矩形,就会有平铺的效果。
五.绘制文本
- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs
文本在指定点绘制。
- (void)drawInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs
文本在指定的矩形里绘制。
六.绘制渐变
Quartz提供了两个不透明数据odgago创建渐变:CGShadingRef和CGGradientRef。我们可以使用任何一个来创建轴向(axial)或径向(radial)渐变。一个渐变是从一个颜色到另外一种颜色的填充。
一个轴向渐变(也称为线性渐变)沿着由两个端点连接的轴线渐变。所有位于垂直于轴线的某条线上的点都具有相同的颜色值。
一个径向渐变也是沿着两个端点连接的轴线渐变,不过路径通常由两个圆来定义。
本章提供了一些我们使用Quartz能够创建的轴向和径向渐变的类型的例子,并比较绘制渐变的两种方法,然后演示了如何使用每种不透明数据类型来创建渐变。
轴向和径向渐变实例
Quartz函数提供了一个丰富的功能来创建渐变效果。这一部分显示了一些我们能达到的效果。图8-1中的轴向渐变由橙色向黄色渐变。在这个例子中,渐变轴相对于原点倾斜了45度角。
Figure 8-1 An axial gradient along a 45 degree axis
Quartz也允许我们指定一系列的颜色和位置值,以沿着轴来创建更复杂的轴向渐变,如图8-2所示。起始点的颜色值是红色,结束点的颜色是紫罗兰色。同时,在轴上有五个位置,它们的颜色值分别被设置为橙、黄、绿、蓝和靛蓝。我们可以把它看成沿着同一轴线的六段连续的线性渐变。虽然这里的轴线与图8-1是一样的,但这不是必须的。轴线的角度由我们提供的两个端点定义。
Figure 8-2 An axial gradient created with seven locations and colors
图8-3显示了一个径向渐变,它从一个小的明亮的红色圆渐变到一个大小黑色的圆。
Figure 8-3 A radial gradient that varies between two circles
使用Quartz,我们不局限于创建颜色值改变的渐变;我们可以只修改alpha值,或者创建alpha值与其它颜色组件一起改变的渐变。图8-4显示了一个渐变,其红、绿、蓝组件的值是不变的,但alpha值从1.0渐变到0.1。
注意:如果我们使用alpha值来改变一个渐变,则在绘制一个PDF内容时我们不能捕获这个渐变。因此,这样的渐变无法打印。如果需要绘制一个渐变到PDF,则需要让alpha值为1.0。
Figure 8-4 A radial gradient created by varying only the alpha component
我们可以把一个圆放置到一个径向渐变中来创建各种形状。如果一个圆是另一个的一部分或者完全在另一个的外面,则Quartz创建了圆锥和一个圆柱。径向渐变的一个通常用法就是创建一个球体阴影,如图8-5所示。在这种情况下,一个单一的点(半径为0的圆)位于一个大圆以内。
Figure 8-5 A radial gradient that varies between a point and a circle
我们可以像图8-6一样通过内嵌几个径向渐变来创建更复杂的效果。它使用同心圆来创建图形中的各环形部分。
Figure 8-6 Nested radial gradients
CGShading和CGGradient对象的对比
我们有两个对象类型用于创建渐变,你可能想知道哪一个更好用。本节就来回答这个问题。
CGShadingRef这个不透明数据类型给我们更多的控制权,以确定如何计算每个端点的颜色。在我们创建CGShading对象之前,我们必须创建一个CGFunction对象(CGFunctionRef),这个对象定义了一个用于计算渐变颜色的函数。写一个自定义的函数让我们能够创建平滑的渐变,如图8-3,8-3和8-5及更多非传统的效果,如图8-12所示。
当创建一个CGShading对象时,我们指定其是轴向还是径向。除了计算函数外,我们还需要提供一个颜色空间、起始点和结束点或者是半径,这取决于是绘制轴向还是径向渐变。在绘制时,我们只是简单地传递CGShading对象及绘制上下文给CGContextDrawShading函数。Quartz为渐变上的每个点调用渐变计算函数。
一个CGGradient对象是CGShading对象的子集,其更易于使用。CGGradientRef不透明类型易于作用,因为Quartz在渐变的每一个顶点上计算颜色值。我们不需要提供一个渐变计算函数。当创建一个渐变对象时,我们提供一个位置和颜色的数组。Quartz使用对应的颜色值来计算每个梯度的渐变,。我们可以使用单一的起始与结束点来设置一个渐变对象,如图8-1所示,或者提供一组端点来创建一个类似于图8-2的的效果。使用CGShading对象可以提供多于两个位置的能力。
当我们创建一个CGGradient对象时,我们需要设置一个颜色空间、位置、和每个位置对应的颜色值。当使用一个渐变对象绘制上下文时,我们指定Quartz是绘制一个轴向还是径向渐变。在绘制时,我们指定开始结束点或半径,这取决于我们是绘制轴向还是径向渐变。而CGShading的几何形状是在创建时定义的,而不是绘制时。
表8-1总结了两种不透明数据类型之间的区别。
扩展渐变端点外部的颜色
当我们创建一个渐变时,我们可以选择使用纯色来填充渐变端点外部的空间。Quartz使用使用渐变边界上的颜色作为填充颜色。我们可以扩展渐变起点、终点或两端的颜色。我们可以扩展使用CGShading对象或CGGradient对象创建的轴向或径向渐变。
图8-7演示了一个轴向渐变,它扩展了起点和终点两侧的区域。图片中的线段显示了渐变的轴线。我们可以看到,填充颜色与起点和终点的颜色是对应的。
Figure 8-7 Extending an axial gradient
图8-8对比了一个未使用扩展的径向渐变和一个在起点和终点两侧使用扩展的径向渐变。Quartz获取了起点和终点的颜色值,并使用这边纯色值来扩展立面。
Figure 8-8 Extending a radial gradient
使用CGGradient对象
一个CGGradient对象是一个渐变的抽象定义—它简单地指定了颜色值和位置,但没有指定几何形状。我们可以在轴向和径向几何形状中使用这个对象。作为一个抽象定义,CGGradient对象可能比CGShading对象更容易重用。没有将几何形状存储在CGGradient对象中,这样允许我们使用相同的颜色方案来绘制不同的几何图形,而不需要为多个图形创建多个CGGradient对象。
因为Quartz为我们计算渐变,使用一个CGGradient对象来创建和绘制一个渐变则更直接,只需要以下几步:
- 创建一个CGGradient对象,提供一个颜色空间,一个饱含两个或更多颜色组件的数组,一个包含两个或多个位置的数组,和两个数组中元素的个数。
- 调用CGContextDrawLinearGradient或CGContextDrawRadialGradient函数并提供一个上下文、一个CGGradient对象、绘制选项和开始结束几何图形来绘制渐变。
- 当不再需要时释放CGGradient对象。
一个位置是一个值区间在0.0到1.0之间的CGFloat值,它指定了沿着渐变的轴线的标准化距离。值0.0指定的轴线的起点,1.0指定了轴线的终点。其它的值指定了一个距离的比例。最低限度情况下,Quartz使用两个位置值。如果我们传递NULL值作为位置数组参数,则Quartz使用0作为第一个位置,1作为第二个位置。
每个颜色的颜色组件的数目取决于颜色空间。对于离屏绘制,我们使用一个RGB颜色空间。因为Quartz使用alpha来绘制,每个离屏颜色都有四个组件—红、绿、蓝和alpha。所以,对于离屏绘制,我们提供的颜色组件数组的元素的数目必须是位置数目的4倍。Quartz的RGBA颜色组件可以在0.0到1.0之间改变。
代码清单8-1是创建一个CGGradient对象的代码片断。在声明了必须的变量后,代码设置了位置和颜色组件数组。然后创建了一个通用的RGB颜色空间。(在iOS中,不管RGB颜色空间是否可用,我们都应该调用CGColorSpaceCreateDeviceRGB)。然后,它传递必要的参数到CGGradientCreateWithColorComponents函数。我们同样可以使用CGGradientCreateWithColors,如果我们的程序设置了CGColor对象,这是一种便捷的方法。
Listing 8-1 Creating a CGGradient object
CGGradientRef myGradient;
CGColorSpaceRef myColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 0.5, 0.4, 1.0, // Start color
0.8, 0.8, 0.3, 1.0 }; // End color
myColorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
myGradient = CGGradientCreateWithColorComponents (myColorspace, components,
locations, num_locations);
在创建了CGGradient对象后,我们可以使用它来绘制一个轴向或线性渐变。代码清单8-2声明并设置了线性渐变的起始点然后绘制渐变。图8-1显示了结果。代码没有演示如何获取CGContext对象。
Listing 8-2 Painting an axial gradient using a CGGradient object
CGPoint myStartPoint, myEndPoint;
myStartPoint.x = 0.0;
myStartPoint.y = 0.0;
myEndPoint.x = 1.0;
myEndPoint.y = 1.0;
CGContextDrawLinearGradient (myContext, myGradient, myStartPoint, myEndPoint, 0);
代码清单8-3使用代码清单8-1中创建的CGGradient对象来绘制图8-9中径向渐变。这个例子同时也演示了使用纯色来填充渐变的扩展区域。
Listing 8-3 Painting a radial gradient using a CGGradient object
CGPoint myStartPoint, myEndPoint;
CGFloat myStartRadius, myEndRadius;
myStartPoint.x = 0.15;
myStartPoint.y = 0.15;
myEndPoint.x = 0.5;
myEndPoint.y = 0.5;
myStartRadius = 0.1;
myEndRadius = 0.25;
CGContextDrawRadialGradient (myContext, myGradient, myStartPoint,
myStartRadius, myEndPoint, myEndRadius,
kCGGradientDrawsAfterEndLocation);
Figure 8-9 A radial gradient painted using a CGGradient object
图8-4中的径向渐变使用代码清单8-4中的变量来创建。
Listing 8-4 The variables used to create a radial gradient by varying alpha
CGPoint myStartPoint, myEndPoint;
CGFloat myStartRadius, myEndRadius;
myStartPoint.x = 0.2;
myStartPoint.y = 0.5;
myEndPoint.x = 0.65;
myEndPoint.y = 0.5;
myStartRadius = 0.1;
myEndRadius = 0.25;
size_t num_locations = 2;
CGFloat locations[2] = { 0, 1.0 };
CGFloat components[8] = { 0.95, 0.3, 0.4, 1.0,
0.95, 0.3, 0.4, 0.1 };
代码清单8-5显示了用于创建图8-10中的灰色渐变的变量,其中有3个位置。
Listing 8-5 The variables used to create a gray gradient
size_t num_locations = 3;
CGFloat locations[3] = { 0.0, 0.5, 1.0};
CGFloat components[12] = { 1.0, 1.0, 1.0, 1.0,
0.5, 0.5, 0.5, 1.0,
1.0, 1.0, 1.0, 1.0 };
Figure 8-10 An axial gradient with three locations
使用CGShading对象
我们通过调用函数CGShadingCreateAxial或CGShadingCreateRadial创建一个CGShading对象来设置一个渐变,调用这些函数需要提供以下参数:
- CGColorSpace对象:颜色空间
- 起始点和终点。对于轴向渐变,有轴线的起始点和终点的坐标。对于径向渐变,有起始圆和终点圆中心的坐标。
- 用于定义渐变区域的圆的起始半径与终止半径。
- 一个CGFunction对象,可以通过CGFunctionCreate函数来获取。这个回调例程必须返回绘制到特定点的颜色值。
- 一个布尔值,用于指定是否使用纯色来绘制起始点与终点的扩展区域。
我们提供给CGShading创建函数的CGFunction对象包含一个回调结构体,及Quartz需要实现这个回调的所有信息。也许设置CGShasing对象的最棘手的部分是创建CGFunction对象。当我们调用CGFunctionCreate函数时,我们提供以下参数:
- 指向回调所需要的数据的指针
- 回调的输入值的个数。Quartz要求回调携带一个输入值。
- 一个浮点数的数组。Quartz只会提供数组中的一个元素给回调函数。一个转入值的范围是0(渐变的开始点的颜色)到1(渐变的结束点的颜色)。
- 回调提供的输出值的数目。对于每一个输入值,我们的回调必须为每个颜色组件提供一个值,以及一个alpha值来指定透明度。颜色组件值由Quartz提供的颜色空间来解释,并会提供给CGShading创建函数。例如,如果我们使用RGB颜色空间,则我们提供值4作为输出值(R,G,B,A)的数目。
- 一个浮点数的数组,用于指定每个颜色组件的值及alpha值。
-
一个回调数据结构,包含结构体的版本(设置为0)、生成颜色组件值的回调、一个可选的用于释放回调中info参数表示的数据。该回调类似于以下格式:
void myCalculateShadingValues (void info, const CGFloat in, CGFloat *out)
在创建CGShading对象后,如果需要我们可以设置额外的裁减操作。然后调用CGContextDrawShading函数来使用渐变来绘制上下文的裁减区域。当调用这个函数时,Quartz调用回调函数来获取从起点到终点这个范围内的颜色值。
当不再需要CGShading对象时,我们调用CGShadingRelease来释放它。
下面我们将一步步地通过代码来看看如何使用CGShading对象来绘制渐变。
使用CGShading对象绘制一个轴向渐变
绘制轴向和径向渐变的步骤是差不多的。这个例子演示了如何使用一个CGShading对象来绘制一个轴向渐变,并在图形上下文中绘制一个半圆形的裁减路径,然后将渐变绘制到裁减区域来达到图8-11的效果。
Figure 8-11 An axial gradient that is clipped and painted
为了绘制图中的轴向渐变,需要按以下步骤来处理:
- 设置CGFunction对象来计算颜色值
- 创建轴向渐变的CGShading对象
- 裁减上下文
- 使用CGShading对象来绘制轴向渐变
- 释放对象
设置CGFunction对象来计算颜色值
我们可以以我们想要的方式来计算颜色值,我们的颜色计算函数包含以下三个参数:
- void *info:这个值可以为NULL或者是一个指向传递给CGShading创建函数的数据。
- const CGFloat *in:Quartz传递in数组给回调。数组中的值必须在为CGFunction对象定义的输入值范围内。例如,输入范围是0到1;看代码清单8-7
- CGFloat *out:我们的回调函数传递out数组给Quartz。它包含用于颜色空间中每个颜色组件的元素及一个alpha值。输出值应该在CGFunction对象中定义的输出值的范围内,例如,输出范围是0到1;看代码清单8-7。
更多关于参数的信息可以查看CGFunctionEvaluateCallback。
代码清单8-6演示了一个函数,它通过将一个常数数组中的值乘以输入值来计算颜色组件值。因为输入值在0到1之间,所以输入值位于黑色(对于RGB来说值为0, 0, 0)和紫色(1, 0, 0.5)之间。注意最后一个组件通常设置为1,表示颜色总是完全不透明的。
Listing 8-6 Computing color component values
static void myCalculateShadingValues (void *info,
const CGFloat *in,
CGFloat *out)
{
CGFloat v;
size_t k, components;
static const CGFloat c[] = {
1, 0, .5, 0 };
components = (size_t)info;
v = *in;
for (k = 0; k < components -1; k++)
*out++ = c[k] * v;
*out++ = 1;
}
在写完回调计算颜色值后,我们将其打包以作为CGFunction对象的一部分。代码清单显示了一个函数,它创建了一个包含代码清单8-6中的回调函数的CGFunction对象。
Listing 8-7 Creating a CGFunction object
static CGFunctionRef myGetFunction (CGColorSpaceRef colorspace)
{
size_t numComponents;
static const CGFloat input_value_range [2] = { 0, 1 };
static const CGFloat output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
static const CGFunctionCallbacks callbacks = { 0,
&myCalculateShadingValues,
NULL };
numComponents = 1 + CGColorSpaceGetNumberOfComponents (colorspace);
return CGFunctionCreate ((void *) numComponents,
1,
input_value_range,
numComponents,
output_value_ranges,
&callbacks);
}
创建一个轴向渐变的CGShading对象
为了创建一个CGShading对象,我们调用CGShadingCreateAxial函数,如代码清单8-8所示。我们传递一个颜色空间,开始点和结束点,一个CGFunction对象,和一个用于指定是否填充渐变的开始点和结束点扩展的布尔值。
Listing 8-8 Creating a CGShading object for an axial gradient
CGPoint startPoint,
endPoint;
CGFunctionRef myFunctionObject;
CGShadingRef myShading;
startPoint = CGPointMake(0,0.5);
endPoint = CGPointMake(1,0.5);
colorspace = CGColorSpaceCreateDeviceRGB();
myFunctionObject = myGetFunction (colorspace);
myShading = CGShadingCreateAxial (colorspace,
startPoint, endPoint,
myFunctionObject,
false, false);
裁减上下文
当绘制一个渐变时,Quartz填充当前上下文。绘制一个渐变与操作颜色和模式不同,后者是用于描边或填充一个路径对象。因此,如果要我们的渐变出现在一个特定形状中,我们需要裁减上下文。代码清单8-9的代码添加了一个半圆形到当前上下文,以便渐变绘制到这个裁减区域,如图8-11。
如果我们仔细看,会发现代码绘制的是一个半圆,而图中显示的是一个半椭圆形。为什么呢?我们会看到,当我们查看后面完整的绘制代码时,上下文被缩放了。稍后会详细说明。虽然我们不需要使用缩放或裁减,这些在Quartz 2D中的选项可以帮助我们达到有趣的效果。
Listing 8-9 Adding a semicircle clip to the graphics context
CGContextBeginPath (myContext);
CGContextAddArc (myContext, .5, .5, .3, 0,
my_convert_to_radians (180), 0);
CGContextClosePath (myContext);
CGContextClip (myContext);
使用CGShading对象来绘制轴向渐变
调用函数CGContextDrawShading使用CGShading对象为指定的颜色渐变来填充当前上下文:
CGContextDrawShading (myContext, myShading);
释放对象
当我们不再需要CGShading对象时,可以调用函数CGShadingRelease来释放它。我们需要同时释放CGColorSpace对象和CGFunction对象,如代码清单8-10所示:
Listing 8-10 Releasing objects
CGShadingRelease (myShading);
CGColorSpaceRelease (colorspace);
CGFunctionRelease (myFunctionObject);
使用CGShading对象绘制轴向渐变的完整例程
代码清单8-11显示了绘制一个轴向渐变的完整例程,使用8-7中的CGFunction对象和8-6中的回调函数。
Listing 8-11 Painting an axial gradient using a CGShading object
void myPaintAxialShading (CGContextRef myContext,
CGRect bounds)
{
CGPoint startPoint,
endPoint;
CGAffineTransform myTransform;
CGFloat width = bounds.size.width;
CGFloat height = bounds.size.height;
startPoint = CGPointMake(0,0.5);
endPoint = CGPointMake(1,0.5);
colorspace = CGColorSpaceCreateDeviceRGB();
myShadingFunction = myGetFunction(colorspace);
shading = CGShadingCreateAxial (colorspace,
startPoint, endPoint,
myShadingFunction,
false, false);
myTransform = CGAffineTransformMakeScale (width, height);
CGContextConcatCTM (myContext, myTransform);
CGContextSaveGState (myContext);
CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1));
CGContextSetRGBFillColor (myContext, 1, 1, 1, 1);
CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1));
CGContextBeginPath (myContext);
CGContextAddArc (myContext, .5, .5, .3, 0,
my_convert_to_radians (180), 0);
CGContextClosePath (myContext);
CGContextClip (myContext);
CGContextDrawShading (myContext, shading);
CGColorSpaceRelease (colorspace);
CGShadingRelease (shading);
CGFunctionRelease (myShadingFunction);
CGContextRestoreGState (myContext);
}
使用CGShading对象绘制一个径向渐变
这个例子演示了如何使用CGShading对象来生成如图8-12所示的输出
Figure 8-12 A radial gradient created using a CGShading object
为了绘制一个径向渐变,我们需要按以下步骤来处理:
- 设置CGFunction对象来计算颜色值
- 创建径向渐变的CGShading对象
- 使用CGShading对象来绘制径向渐变
- 释放对象
设置CGFunction对象来计算颜色值
计算径向渐变和轴向渐变颜色值的函数没有什么区别。事实上,我们可以依照上面的轴向的”设置CGFunction对象来计算颜色值”。代码清单8-12用于计算颜色,使用颜色按正弦变化。图8-12与图8-11的结果非常不同。虽然颜色输出值不同,代码清单8-12的代码与8-6中的函数遵循相同的原型。每个函数获取一个输入值并计算N个值,即颜色空间的每个颜色组件加一个alpha值。
Listing 8-12 Computing color component values
static void myCalculateShadingValues (void *info,
const CGFloat *in,
CGFloat *out)
{
size_t k, components;
double frequency[4] = { 55, 220, 110, 0 };
components = (size_t)info;
for (k = 0; k < components - 1; k++)
*out++ = (1 + sin(*in * frequency[k]))/2;
*out++ = 1; // alpha
}
在写完颜色计算函数后调用它,我们需要创建一个CGFunction对象,如在轴向中”设置CGFunction对象来计算颜色值”所描述的一样。
创建径向渐变的CGShading对象
为了创建一个CGShading对象或者一个径向渐变,我们调用CGShadingCreateRadial函数,如代码清单8-13所求,传递一个颜色空间、开始点和结束点,开始半径和结束半径,一个CGFunction对象,和一个用于指定是否填充渐变的开始点和结束点扩展的布尔值。
Listing 8-13 Creating a CGShading object for a radial gradient
CGPoint startPoint, endPoint;
CGFloat startRadius, endRadius;
startPoint = CGPointMake(0.25,0.3);
startRadius = .1;
endPoint = CGPointMake(.7,0.7);
endRadius = .25;
colorspace = CGColorSpaceCreateDeviceRGB();
myShadingFunction = myGetFunction (colorspace);
CGShadingCreateRadial (colorspace,
startPoint,
startRadius,
endPoint,
endRadius,
myShadingFunction,
false,
false);
使用CGShading对象来绘制径向渐变
调用函数CGContextDrawShading使用CGShading对象为指定的颜色渐变来填充当前上下文:
CGContextDrawShading (myContext, myShading);
注意我们使用相同的函数来绘制渐变,而不管它是轴向还是径向。
释放对象
当我们不再需要CGShading对象时,可以调用函数CGShadingRelease来释放它。我们需要同时释放CGColorSpace对象和CGFunction对象,如代码清单8-14所示:
Listing 8-10 Releasing objects
CGShadingRelease (myShading);
CGColorSpaceRelease (colorspace);
CGFunctionRelease (myFunctionObject);
使用CGShading对象绘制径向渐变的完整例程
代码清单8-15显示了绘制一个轴径向渐变的完整例程,使用8-7中的CGFunction对象和8-12中的回调函数。
Listing 8-15 A routine that paints a radial gradient using a CGShading object
void myPaintRadialShading (CGContextRef myContext, CGRect bounds); { CGPoint startPoint, endPoint; CGFloat startRadius, endRadius; CGAffineTransform myTransform; CGFloat width = bounds.size.width; CGFloat height = bounds.size.height; startPoint = CGPointMake(0.25,0.3); startRadius = .1; endPoint = CGPointMake(.7,0.7); endRadius = .25; colorspace = CGColorSpaceCreateDeviceRGB(); myShadingFunction = myGetFunction (colorspace); shading = CGShadingCreateRadial (colorspace, startPoint, startRadius, endPoint, endRadius, myShadingFunction, false, false); myTransform = CGAffineTransformMakeScale (width, height); CGContextConcatCTM (myContext, myTransform); CGContextSaveGState (myContext); CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1)); CGContextSetRGBFillColor (myContext, 1, 1, 1, 1); CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1)); CGContextDrawShading (myContext, shading); CGColorSpaceRelease (colorspace); CGShadingRelease (shading); CGFunctionRelease (myShadingFunction); CGContextRestoreGState (myContext); }
七.Quartz路径
Core Graphics(Quartz 2D)中有4个基本图元用于描述路径:点、线段、弧和贝赛尔曲线。
1.点
一个点完全不占空间,所以画一个点不会再屏幕上显示任何东西,我们可以在路径里加入很多的点,想加多少加多少。
2.线段
线段有两个点定义:起点 和 终点。线段可以通过描边绘制出来,我们可以通过设置图形上下文,如画笔宽度或者颜色等参数,就可以绘制出两点之间的线段。线段没有面积,所以他们不能被填充。
3.弧
弧可以由一个圆心点、半径、起始角和结束角描述的。园是弧的特例,只需要设置为起始角0°,结束角为360°就可以了。因为弧是占有一定面积的路径,所以可以被填充、描边和描边填充出来。
4.贝赛尔曲线
任何一条曲线都可以通过与它相切的控制线两端的点的位置来定义。因此,贝塞尔曲线可以用4个点描述,其中两个点描述两个端点,另外两个描述每一端的切线。贝塞尔曲线可分为:二次方贝塞尔曲线和高阶贝塞尔曲线。
例如:
CGContextRef context = UIGraphicsGetCurrentContext();//获得访问图形上下文对象
CGContextMoveToPoint(context ,75,10);//在<span style="font-family: Arial, Helvetica, sans-serif;">(75,10)绘制起始点</span>
<pre name="code" class="objc">CGContextAddLineToPoint(context ,10,150);//绘制从(75,10)到(10,150)的线段
CGContextAddLineToPoint(context ,160,150);//绘制从(10,150)到(160,150)的线段CGContextClosePath(context );//闭合
定制和绘制路径是两个不同的操作,先定义路径,在绘制它。CGContextDrawPath(context,kCGPathFillStroke)函数实现绘制路径,其中kCGPathFillStroke参数是填充描边处理,此外还有:kCGPathFillStroke和kCGPathStroke,分别代表填充和描边处理。
绘制路径相关函数: