Quartz 2D CGGradient与CGShading实现渐变的绘制
Quartz 提供了两种不透明的数据类型来创建渐变CGShading 和 CGGradient,你可以使用其中任何一个来创建轴向或径向渐变。
轴向渐变:沿着一个轴方向线性渐变
径向渐变:一个点为原型,指定半径的范围内辐射渐变
我们用两个图来理解一下这两个概念:
1、轴向渐变(轴:沿着左上角 到 右下角)
2、径向渐变(以中心点为圆心,向半径为50的四周方向辐射渐变)
CGShading与CGGradient之间的关系,以及使用的不同。
CGGradient是CGShading的子集,CGGradient封装了对CGShading的渐变实现函数,因此CGGradient使用起来会比CGShading简单。
下面介绍一下两者的不同:
CGGradient | CGShading |
使用同一个CGGradient实例可以同时绘制轴向渐变和径向渐变 | 在绘制轴向渐变和径向渐变时,需要分别实例化不同的对象 |
在绘制时创建渐变的几何形状 | 在创建对象时创建渐变的几何形状 |
Quartz自动完成计算渐变中每个点的颜色变化 | 你需要提供一个回调函数,来计算渐变中每个点的颜色 |
使用简单,定义两个点和对应的颜色即可 | 需要在回调函数中计算,使用比较麻烦 |
我们先来介绍一下CGGradient的使用,它使用起来很简单,我们通过它来实现上面介绍的轴向渐变和径向渐变的例子
实例化方法:
// 参数 // 1、颜色空间 // 2、指定渐变中的颜色(需要与locations个数对应,每组颜色值为rgba(根据颜色空间决定),例如:1,0,0,1,表示一个红色,透明度为1) // 3、指定渐变的过度位置(值范围:[0,1],0:几何开始的位置;1:几何结束的位置;0.5:几何中间的位置) init?(colorSpace space: CGColorSpace, colorComponents components: UnsafePointer, locations: UnsafePointer ?, count: Int) // 参数 // 1、颜色空间 // 2、指定渐变中的颜色(需要与locations个数对应,每组颜色值为:CGColor) // 3、指定渐变的过度位置(值范围:[0,1],0:几何开始的位置;1:几何结束的位置;0.5:几何中间的位置) init?(colorsSpace space: CGColorSpace?, colors: CFArray, locations: UnsafePointer ?)
具体实现代码:
override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化颜色空间,有两种方法,一般采用第一种(下面两个颜色空间是一样的) // 方法1: let space = CGColorSpaceCreateDeviceRGB() // 方法2: // let space = CGColorSpace(name: CGColorSpace.sRGB)! // 设置渐变的过度位置,范围:[0, 1] var locations: [CGFloat] = [0, 0.5, 1] // 设置过度的颜色组件集合,因为我们的颜色空间是RGB,因此每组的颜色值为rgba,即:[r, g, b, a] // 注意:每组的颜色是在数组中连续的,即:[r,g,b,a, r,g,b,a, r,g,b,a] var colors: [CGFloat] = [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1] // 初始化渐变对象CGGradient guard let gradient = CGGradient(colorSpace: space, colorComponents: &colors, locations: &locations, count: locations.count) else { print("gradient create fail") return } // 绘制轴向渐变 // 参数: // 1、渐变对象实例 // 2、渐变的几何开始点坐标 // 3、渐变的几何结束点坐标 // 4、绘制选项,包括: // (1)drawsBeforeStartLocation:在几何开始点的位置之前的空间部分(超出边界不绘制),用第一个颜色值扩散颜色 // (2)drawsAfterEndLocation:在几何结束点的位置之后的空间部分(超出边界不绘制),用最后一个颜色值扩散颜色 context.drawLinearGradient(gradient, start: .init(x: 0, y: 0), end: .init(x: bounds.width, y: bounds.height), options: .drawsBeforeStartLocation) // 绘制径向渐变 // 参数: // 1、渐变对象实例 // 2、渐变开始的圆心点坐标 // 3、渐变开始的圆半径(辐射半径) // 4、渐变结束的圆心点坐标 // 5、渐变结束的圆半径(辐射半径) // 6、绘制选项,跟上面的一样 // context.drawRadialGradient(gradient, // startCenter: .init(x:bounds.width/2, y:bounds.height/2), // startRadius: 10, // endCenter: .init(x: bounds.width/2, y: bounds.height/2), // endRadius: 50, // options: .drawsBeforeStartLocation) }
代码中注释介绍的已经很详细了,这里我给大家着重介绍一下options参数。我们看下图便一目了然:
红色区域:是渐变开始点绘制之前的颜色填充部分。
蓝色区域:是渐变结束点绘制之后的颜色填充部分。
options选项决定在渐变的开始点之前 还是 结束点之后 的区域内进行颜色填,填充的颜色色值为:
1、开始点之前绘制(drawsBeforeStartLocation):填充色为颜色数组中第一个颜色值
2、结束点之后绘制(drawsAfterEndLocation):填充色为颜色数组中最后一个颜色值
下面是两种options的轴向渐变的绘图结果:(径向渐变也是如此)
drawsBeforeStartLocation:(开始点前采用红色填充,结束点后无填充)
drawsAfterEndLocation:(开始点前无填充,结束点后采用蓝色填充)
接下来介绍CGShading的使用,它在绘制渐变时用起来比CGGradient要复杂,因为需要我们自己来计算渐变过程中每个点的颜色。使用时涉及到以下几个关键对象:
CGFunctionCallbacks:用于提供计算渐变过程中每个点的颜色的回调函数,这就是我们实现渐变的关键点。
CGFunction:为上面的回调函数设置初始值,以及相关值的范围的对象。
CGShading:定义渐变的规则,例如:颜色空间、从几何区域的哪里开始渐变到哪里结束渐变等。
实现轴向渐变与径向渐变的实例化方法不一样,下面是对应这两种实现的实例化方法:
// 初始化轴向渐变对象 init?(axialSpace space: CGColorSpace, start: CGPoint, end: CGPoint, function: CGFunction, extendStart: Bool, extendEnd: Bool) // 初始化径向渐变对象 init?(radialSpace space: CGColorSpace, start: CGPoint, startRadius: CGFloat, end: CGPoint, endRadius: CGFloat, function: CGFunction, extendStart: Bool, extendEnd: Bool)
下面是一个利用CGShading实现轴向渐变的具体实现的demo,代码中注释写的很详细,大家可以参考。
override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化一个回调函数,用于计算每个点的颜色值 // 参数: // 1、CGFunction初始化传入的第一个参数info(指针类型) // 2、输入值 // 3、输出值 // 4、释放info指针的回调函数 var callback = CGFunctionCallbacks(version: 0) { info, inVal, outVal in let numberOfComponents: Int if let info = info { // 注意这里不能用takeRetainedValue()取值,否则会提前释放info的内存,导致下次访问info时出错 numberOfComponents = Unmanaged.fromOpaque(info).takeUnretainedValue().intValue }else{ numberOfComponents = 0 } // 渐变开始的颜色值 let startColor: [CGFloat] = [1, 0, 0, 1] // 渐变结束的颜色值 let endColor: [CGFloat] = [0, 1, 0, 1] let aInVal = inVal.pointee // 获取每次迭代的输入值,下面指定了输入值范围在[0, 1],因此aInVal的值范围在[0, 1] var out = outVal // 下面指定了输出值包含的分量个数为numberOfComponents(即:out中应返回numberOfComponents个元素) for i in 0..<numberOfComponents { // 注意:下面指定了输出值中每个分量(每个元素)的值范围[0, 1],因此这里的计算结果不要超出这个范围 if aInVal < 0.5 { // 计算渐变开始点到中间点区间的颜色分量值 out.pointee = startColor[i] * (0.8 - aInVal) }else{ // 计算渐变中间点到结束点区间的颜色分量值 out.pointee = endColor[i] * (aInVal - 0.2) } out = out.successor() } } releaseInfo: { info in guard let info = info else { return } // 释放info指针内存 Unmanaged .fromOpaque(info).release() } // 初始化颜色空间 let space = CGColorSpaceCreateDeviceRGB() // 获取通道颜色分量的个数,这里要加一个alpha let numberOfComponents = space.numberOfComponents + 1 // rgb + a // 指定上面callback中输入值的递增迭代次数,这里设置1,即:输入值会从domain的范围内,迭代一次(0, 0.0.00167, ..., 1)终止 // 如果设置为2,会迭代两次:第一次(0, 0.0.00167, ..., 1),第二次(0, 0.0.00167, ..., 1) let domainDimension = 1 // 输入值的变化范围,这里指定从0到1,即输入值会根据渐变的开始点到结束点的变化,从0开始递增到1,如(0, 0.0.00167, ..., 1) // 如果设置为[1, 10],则输入值会从1开始递增到10 let domain: [CGFloat] = [0, 1] // 注意:这里的范围必须是递增的 // 指定上面callback中输出值包含的分量个数,如果为4,那么上面callback的输出值out指针应该包含4个分量值(即:rgba)。 // 可以理解为输出值数组的size let rangeDimension = numberOfComponents // 输出值每个分量的范围,即r值的范围[0, 1],g值的范围[0, 1], b值的范围[0, 1], a值的范围[0, 1] // 可以理解为输出值数组中每个元素值的取值范围 let range: [CGFloat] = [0, 1, 0, 1, 0, 1, 0, 1] // 数组下标0和1代表第一个值的范围,一次类推 guard let function = CGFunction(info: Unmanaged .passRetained(NSNumber(integerLiteral: numberOfComponents)).toOpaque(), domainDimension: domainDimension, domain: domain, rangeDimension: rangeDimension, range: range, callbacks: &callback) else { print("function create fail") return } // 渐变的开始点坐标 let start = CGPoint(x: bounds.width/4, y: bounds.height/4) // 渐变的结束点坐标 let end = CGPoint(x: bounds.width * 3 / 4, y: bounds.height * 3 / 4) // 指定渐变开始点之前的区域是否执行填充颜色,相当于使用CGGradient绘制渐变时,调用drawLinearGradient方法时设置的:drawsBeforeStartLocation let extendStart = true // 指定渐变结束点之后的区域是否执行填充颜色,相当于使用CGGradient绘制渐变时,调用drawLinearGradient方法时设置的:drawsAfterEndLocation let extendEnd = true // 初始化一个轴向渐变实例CGShading guard let shading = CGShading(axialSpace: space, start: start, end: end, function: function, extendStart: extendStart, extendEnd: extendEnd) else { print("shading create fail") return } // 绘制渐变 context.drawShading(shading) }
代码中注释介绍的已经很详细了,这里就不详细说明了,下面是实现的效果:
径向渐变的实现也是类似的,只不过是CGShading的实例化方法不一样。下面是采用CGShading实现的径向渐变的效果: