Core Graphics绘图(二)

颜色空间(Color Spaces)

设备(显示器、打印机、扫描仪、照相机)对待颜色的方式不同。每个设备都有自己的颜色范围,设备可以准确的生成这些颜色。在一台设备上产生的颜色可能无法在另一台设备上产生。

Quartz中的颜色由相应颜色空间的一组值表示。Alpha是Graphics States(图形状态)的参数,Quartz使用它来确定如何将新绘制的对象组合到现有页面。

Quartz的Graphics States(图形状态)使用默认混合模式(blend mode),使用公式将源色和目标色的成分结合起来,进行alpha混合:

1
context?.setBlendMode(.normal)

destination = (alpha * source) + (1 - alpha) * destination

source是新绘制组件的颜色;destination是背景颜色的一个组成部分。每个新绘制的形状或图像都执行此公式。

我们可以为接受颜色的地方提供一个alpha值作为最终颜色的组成部分;也可以使用 context?.setAlpha(0.5) 函数设置全局alpha值;如果同时设置两者,则Quartz会将alpha颜色分量乘以全局alpha值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
// 设置全局alpha值。使得页面上的对象和其本身都透明
// context?.setAlpha(0.5)

context?.beginPath()
context?.addRect(CGRect(x: 50, y: 50, width: 100, height: 100))
context?.setFillColor(UIColor.green.cgColor)
// 为路径的单独颜色设置alpha,只会作用于当前路径
// context?.setFillColor(UIColor.green.withAlphaComponent(0.5).cgColor)
context?.fillPath()

context?.beginPath()
context?.addRect(CGRect(x: 75, y: 75, width: 50, height: 50))
context?.setFillColor(UIColor.red.cgColor)
// context?.setFillColor(UIColor.red.withAlphaComponent(0.5).cgColor)
context?.fillPath()

// 绘制透明矩形,该方法以显式清除图形上下文的alpha通道。例如,在为图标创建透明蒙版或使窗口背景透明时,可能要执行此操作。
// 如果提供的上下文是window 或者 bitmap contexts,则 Core Graphics 会清楚rect所表示的矩形
// 如果是其它类型,Core Graphics以依赖于设备的方式填充矩形。
// 注意,不应该在 window 或者 bitmap contex ts 以外的上下文中使用此函数。
// context?.clear(CGRect(x: 75, y: 75, width: 50, height: 50))
}

相关概念

颜色管理

颜色管理:是在设备之间保持颜色一致的过程。不同的成像设备(如扫描仪、显示器和打印机)在不同的颜色空间中工作,并且每个设备都有不同的色域(设备可以显示的颜色范围)。

由于种种因素(设备类型、使用年限、不同的打印技术、墨水纸张等等),从单个显示器上的RGB颜色转换为使用特定墨水和纸张类型的单个打印机上的CMYK颜色可能会导致不可预测的结果。

颜色

颜色:是一种感觉,因此也是一种主观体验。颜色的感觉是视觉的一个组成部分,是由人眼对光线的敏感性引起的。颜色感觉也受到大脑处理信息的方式的影响,而且是因人而异的。因此,颜色感知是一种非常复杂的现象。

计算机使您能够数字化地控制颜色,并且已经开发了许多外设来获取、显示和再现颜色。因此,需要一种机制来维持环境中的颜色控制,这种环境可以包括不同的计算机操作系统和硬件,以及连接到计算机的各种设备和媒体。

色调,饱和度和值(或亮度)

颜色被描述为具有三个维度。这些维度是色调、饱和度和值。

  • 色调:是颜色的名称,它使颜色在光谱中处于正确的位置。
  • 饱和度:是指颜色的强度程度,或颜色的强度。例如:粉色是不饱和红色的一个例子。
  • 值(或亮度):描述从彩色图像反射或透射的光的强度的差异。

加色法和减色法

加色法,是指红、绿、蓝(RGB三原色)三种光它们按不同比例相加而混合出其他色彩的一种方法。

减色法,是指着色剂如油墨或染料组合的过程。各种含量着色剂会吸收或“减去”照明对象的白光光谱的一部分,物体的颜色是不被物体吸收的颜色的光。

显示器使用加色法,输出打印设备使用减色法。

Color Spaces(颜色空间)

颜色空间描述不同的外围设备如何表示颜色以及用于表示颜色的值。它述了一种环境,在这种环境中,颜色被表示、排序、比较或计算。

颜色空间定义了一维、二维、三维或四维的环境,其组成部分(或颜色组成部分)代表强度值。颜色组成部分也称为 颜色通道。

例如,RGB空间是一种三维颜色空间,它是根据红色、绿色和蓝色的强度相互促进来确定一种颜色。红、绿和蓝是它的颜色通道。

Apple 的 ColorSync技术 直接支持几种不同的颜色空间,让你在最适合你需要的任何颜色数据中都很方便。ColorSync颜色空间可以分为几个组或基族,它们是:

  • Gray spaces:用于灰度显示和打印
  • RGB-based color spaces:主要用于显示器和扫描仪
  • CMYK-based color spaces:主要用于彩色打印
  • Device-independent color spaces:如Lab,主要用于颜色比较、色差和颜色转换
  • Named color spaces:主要用于打印和图形设计
  • Heterogeneous HiFi color spaces:也称为多通道颜色空间,主要用于涉及使用红橙色、绿色和蓝色的新印刷工艺,也用于专色,例如金和银金属

基本族中的所有颜色空间通过非常简单的数学公式相互关联,或者仅在存储格式的细节上有所不同。

颜色空间

Quartz支持色彩管理系统使用的标准颜色空间(standard color spaces)、用于与设备无关的颜色空间(device-independent color spaces)、还支持通用(generic)、索引(indexed)和模式( pattern color spaces)颜色空间。

iOS不支持device-independent、generic color spaces,iOS程序只使用 device color spaces。

device color spaces

设备颜色空间主要由iOS应用程序使用,通过使用以下功能之一来创建设备色彩空间:

1
2
3
4
5
6
// 用于设备相关的灰度颜色空间
let graySpace = CGColorSpaceCreateDeviceGray()
// 用于设备依赖的RGB颜色空间
let rgbSpace = CGColorSpaceCreateDeviceRGB()
// 用于设备依赖的CMYK颜色空间
let cmykSpace = CGColorSpaceCreateDeviceCMYK()

Quartz提供了一组用于设置填充颜色(fill color),笔触颜色(stroke color),颜色空间(color spaces)和Alpha的功能。这些颜色参数都应用于图形状态(Graphics States),这意味着设置之后,该设置将一直有效,直到设置为另一个值。

一个颜色必须有一个关联的颜色空间,否则Quartz将不知道如何解释颜色值。并且还需要为绘图目标提供适当的颜色空间。

Quartz提供了便捷的方法为设备颜色空间设置颜色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.beginPath()
context?.addArc(center: CGPoint(x: 100, y: 100), radius: 50, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
context?.closePath()

// 通过创建颜色空间来设置颜色
// let rgbSpace = CGColorSpaceCreateDeviceRGB()
// let values:[CGFloat] = [0.3, 1.0, 0.9, 1.0]
// let color = CGColor(colorSpace: rgbSpace, components: values)!
// context?.setFillColor(color)

context?.setLineWidth(2.0)
// 通过 Quartz 提供的便捷的方法来设置颜色
context?.setStrokeColor(red: 237/225.0, green: 101/255.0, blue: 90/255.0, alpha: 1.0)
context?.setFillColor(red: 245/255.0, green: 187/255.0, blue: 65/255.0, alpha: 1.0)
context?.drawPath(using: .fillStroke)
}

渲染意图(Rending Intent)

渲染意图指定Quartz如何将颜色从源颜色空间映射到图形上下文的目标颜色空间范围内的颜色。如果没有明确的设置,Quartz默认对位图(采样)图像采用 .perceptual,其它所有绘图采用 .relativeColorimetric

1
2
3
4
5
6
7
8
/* 设置渲染意图
* .defaultIntent 使用默认的渲染意图。
* .absoluteColorimetric 绝对色度渲染意图。
* .relativeColorimetric 相对色度渲染意图。
* .perceptual 感知渲染意图。
* .saturation 饱和度渲染意图。
*/
context?.setRenderingIntent(.relativeColorimetric)

变换(Transforms)

Quartz 2D绘图模型定义了两个完全独立的坐标空间:用户空间(表示文档页面)和设备空间(表示设备的原生分辨率)。

用户空间坐标是浮点数,与设备空间中像素的分辨率无关。当您想打印或显示文档时,Quartz将用户空间坐标映射到设备空间坐标。因此,您不必重写应用程序或编写额外代码来调整应用程序的输出,以在不同设备上实现最佳显示。

当前变换矩阵(current transformation matrix,又称CTM),可以通过操作CTM来修改默认用户空间,Quartz 提供了内置的变换函数。在绘制图像之前,可以操纵CTM旋转,缩放或平移页面,从而变换要绘制的对象。

如下在图形上下文中绘制一张图片:

1
2
3
4
5
6
7
8
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
/* 在指定区域内绘制图像,
* rect:会不成比例的缩放图片,以适应rect参数指定的边界。
* byTiling:为true,会以rect为起点,把图片平铺上下文的整个区域。
*/
context?.draw((UIImage(named: "butterfly")?.cgImage!)!, in: CGRect(x: 50, y:0 , width: 100, height: 200), byTiling: true)
}

可以发现图片是颠倒的,这是因为 Quartz 2D的图形绘制引擎坐标是左下角为原点,y轴向上。和UIKit坐标系坐标轴上下相反,所以图片上下颠倒。下面通过修改 CTM 属性来调整图片的绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let imgRect = CGRect(x: 50, y:0 , width: 100, height: 180)

// 平移,为旋转作准备
context?.translateBy(x: imgRect.width + imgRect.origin.x * 2, y: imgRect.size.height + imgRect.origin.y * 2)

// 以上下文的坐标原点(左上角)进行旋转。由于UIkit坐标系先对于 Quartz 2D 坐标系颠倒了y轴,所以旋转角度为正值顺时针旋转,负值逆时针
context?.rotate(by: CGFloat.pi)

// 把图片进行缩放,避免变形
context?.scaleBy(x: 1.0, y: 0.5)

context?.draw((UIImage(named: "butterfly")?.cgImage!)!, in: imgRect, byTiling: false)
}

注意,我们通过 CTM 进行平移、旋转、缩放操作时,它们执行的顺序不一样,会导致不同结果。

Quartz中 仿射变换(CGAffineTransform) 可以实现与 CTM函数 相同的变换操作,使用仿射变换函数构造矩阵,调用函数CGContextConcatCTM应用于CTM,达到变换效果。

如下操作通过放射变换达到与CTM等价的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let imgRect = CGRect(x: 50, y:0 , width: 100, height: 180)

var transform = CGAffineTransform(translationX: imgRect.origin.x * 2 + imgRect.size.width, y: imgRect.origin.y * 2 + imgRect.size.height)
transform = transform.rotated(by: CGFloat.pi)
transform = transform.scaledBy(x: 1.0, y: 0.5)

// 使用指定的矩阵转换上下文中的用户坐标系统
context?.concatenate(transform)

context?.draw((UIImage(named: "butterfly")?.cgImage!)!, in: imgRect)
}

注意,连接矩阵的顺序很重要——矩阵乘法不是可交换的。也就是说,矩阵A乘以矩阵B的结果不一定等于矩阵B乘以矩阵A的结果。

Patterns(模式、样式)

  • Pattern 是在图形上下文中重复绘制的一系列绘图操作。可以像使用颜色一样使用Pattern。

使用 Pattern 绘制时,Quartz 将页面划分为一组模式单元格,每个单元格的大小与模式图像相同,并使用提供的回调绘制每个单元格。

  • pattern cellpattern 的基本组成部分。

当您绘制 pattern cell(模式单元格) 时,Quartz 使用 pattern space 作为坐标系统。模式空间是一个抽象空间,它通过创建 pattern 时指定的转换矩阵映射到默认用户空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 在此回调函数中绘制 Colored Pattern Cell
* pointer 是与 pattern 相关联的私有数据的通用指针
* context 表示绘制图案单元的图形上下文
*/
var callBacks = CGPatternCallbacks(version: 1, drawPattern: { (pointer, context) in
print(pointer?.load(as: Int.self) ?? 111) // 打印两次分别输出:321 和 0
}) { (pointer) in
print("释放资源")
};

var arr = 321
///创建 pattern 对象
/// - Parameters:
/// - info: 一个指针,指向 pattern 绘制函数中使用数据的私有存储的指针
/// - bounds: 在模式空间中指定pattern cell的边界框
/// - matrix: 一个CGAffineTransform矩阵,表示从模式空间到使用模式的上下文的默认用户空间的转换
/// - xStep: pattern cell 之间的水平间距。如果pattern cell之间水平间距为0,则此值应设置为 pattern cell的width
/// - yStep: pattern cell 之间的垂直间距。
/// - tiling: 一个 CGPatternTiling 常量,指定的平铺模式。
/// - isColored: 定模式单元格是着色模式(true)还是模板模式(false)
/// - callbacks: 指向模式回调函数表的指针,该函数由 CGPatternCallbacks 创建
let pattern = CGPattern(info: &arr, bounds: rect, matrix: CGAffineTransform.identity, xStep: 40, yStep: 40, tiling: CGPatternTiling.constantSpacing, isColored: false, callbacks: &callBacks);

colored Patterns(着色模式) 和 Stencil (Uncolored) Patterns(未着色模式)

colored Patterns有与之相关的内在颜色,是在模式单元格创建过程中指定的,而不是在模式绘制过程中指定的。

Stencil (Uncolored) Patterns:当pattern cell被定义时,没有颜色与它相关联。颜色是在模式绘制过程中指定的,而不是在模式单元格创建过程中指定的。

Tiling(平铺)

平铺是将模式单元格(pattern cell)呈现到页面的一部分的过程。当 Quartz 向设备呈现 pattern 时,Quartz 可能需要调整 pattern 以适应设备空间。因为用户空间单位和设备像素之间存在差异,所以在用户空间中定义的“pattern cell”在呈现到设备时可能不完全适合。
Quartz 有三个平铺选项,用来调整 pattern:

1、CGPatternTiling.noDistortion
以稍微调整 pattern cell 之间的间距为代价,但不超过一个设备像素。称为:无失真。

2、CGPatternTiling.constantSpacingMinimalDistortion
单元之间的间距,以略微扭曲 pattern cell 为代价,但不超过一个设备像素。称为:具有最小失真的恒定间距

3、CGPatternTiling.constantSpacing
单元之间的间距一样,如同 .constantSpacingMinimalDistortion,可能被另外扭曲,以允许更有效的实现。称为:恒定间距

Patterns如何工作

Pattern 的操作与颜色类似,可以设置填充 或 描边Pattern,然后调用绘制函数。Quartz 使用你设置的 Pattern 来进行绘制时,需要先调用 context?.setFillPattern 来设置模式,然后用此 pattern 绘制填充的矩形。使用 colorpattern 绘制的区别在于必须定义图案。

使用 Pattern 填充或描边时,Quartz 会执行以下任务来绘制每个图案单元格:

  1. 保存图像状态(graphics state);
  2. 将当前转换矩阵转换为 Pattern Cell 的原点;
  3. CTM 与 模式矩阵连接起来;
  4. 剪辑到 Pattern Cell 的边界矩形;
  5. 调用绘图回调函数来绘制 Pattern Cell
  6. 恢复图形状态(graphics state);

Quartz负责为处理 Tiling(平铺),反复将 Pattern Cell 呈现到绘图空间,直到绘制完整个空间,可以使用描边或填充Pattern CellPattern Cell的大小可以是任意的,如果您想要查看模式,您应该确保模式单元格与绘图空间相匹配,若不匹配,Pattern Cell将会被剪裁。

使用 Patterns 进行绘制

1、colored Patterns(着色模式)

通过 Apple 官方的例子,colored Patterns在 iOS12.0 模拟器上是无法设置颜色的,其它版本正常。真机12.x上也是正常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 绘制 Colored Patterns
override func draw(_ rect: CGRect) {
// 1、在回调函数中绘制Colored Pattern Cell
// 在绘制时需要设置 color,使其成为 colored pattern
var callBacks = CGPatternCallbacks(version: 0, drawPattern: { (pointer, context) in

context.setFillColor(red: 1.0, green: 0, blue: 0, alpha: 1.0)
context.setStrokeColor(red: 0, green: 0, blue: 1.0, alpha: 1.0)
context.setLineWidth(2.0)

context.beginPath()
context.addRect(.init(x: 10, y: 10, width: 20, height: 20));
context.closePath()
context.drawPath(using: .fillStroke)

}) { (pointer) in
print("释放资源")
};

let context = UIGraphicsGetCurrentContext()
context?.saveGState()

// 2、设置Colored Pattern 的颜色空间
let patternSpace = CGColorSpace(patternBaseSpace: nil)
context?.setFillColorSpace(patternSpace!)
//context?.setStrokeColorSpace(patternSpace!)

// 3、创建 Colored Pattern
// 由于是 Colored Pattern,所以 isColored 必须为 true
let pattern = CGPattern(info: nil, bounds: CGRect(x: 0, y: 0, width: 40, height: 40), matrix: CGAffineTransform.identity, xStep: 40, yStep: 40, tiling: CGPatternTiling.noDistortion, isColored: true, callbacks: &callBacks);

// 4、为图形上下文设置fill pattern
// 由于是 colored pattern,colorComponents需要传入一个alpha
var alp: CGFloat = 1.0
context?.setFillPattern(pattern!, colorComponents: &alp)

// 5、填充图形上下文
context?.fill(rect)
context?.restoreGState()
}

2、Stencil (Uncolored) Patterns(未着色模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
override func draw(_ rect: CGRect) {

// 1、在回调函数中绘制 Stencil pattern cell
// 此回调函数与 colored pattern cell 绘制回调函数的唯一区别是,绘图回调中不指定任何颜色
var callBacks = CGPatternCallbacks(version: 1, drawPattern: { (pointer, context) in

let psize: CGFloat = 40
let r = 0.8 * psize / 2;
let theta = CGFloat.pi * 2 * (2.0 / 5.0)
context.translateBy(x: psize / 2, y: psize / 2)

context.beginPath()
context.move(to: CGPoint(x: 0, y: r))
for k in 0..<5 {
context.addLine(to: CGPoint(x: r * sin(CGFloat(k) * theta), y: r * cos(CGFloat(k) * theta)))
}

context.closePath()
context.fillPath()

}) { (pointer) in
print("释放资源")
};

// 2、创建 Stencil Pattern,注意:此时isColored必须传 false
let pattern = CGPattern(info: nil, bounds: CGRect(x: 0, y: 0, width: 40, height: 40), matrix: CGAffineTransform.identity, xStep: 40, yStep: 40, tiling: CGPatternTiling.constantSpacing, isColored: false, callbacks: &callBacks);


let context = UIGraphicsGetCurrentContext()

// 3、设置 Stencil Pattern 的颜色空间,用于Stencil pattern cell的绘制
let baseSpace = CGColorSpaceCreateDeviceRGB()
let patternSpace = CGColorSpace(patternBaseSpace: baseSpace)
context?.setFillColorSpace(patternSpace!)

// 4、将 Stencil Pattern 进行描边 或者 填充
// 由于 pattern cell 绘制回调函数中不提供颜色,因此此时必须传入颜色值。
let colors:[CGFloat] = [246/255.0, 190/255.0, 73/255.0, 1]
context?.setFillPattern(pattern!, colorComponents: colors)

// 5、填充绘制到整个图形上下文的空间中
context?.fill(rect)
}

阴影(Shadows)

阴影是画在图形对象下面并与之偏移的图像,这样阴影就模仿了光源投射在图形对象上的效果。

阴影有三个属性:

  • x偏移量,它指定阴影在水平方向上与图像的偏移距离。
  • y偏移量,它指定阴影在垂直方向上与图像的偏移距离。
  • 模糊值,它指定图像是具有硬边缘,还是漫反射边缘。

Quartz中的 Shadowsgraphics state(图形状态)的一部分,可以通过下面方法设置黑色和彩色阴影,彩色阴影颜色的值取决于您想要绘制的颜色空间。

1
2
context?.setShadow(offset: , blur: )
context?.setShadow(offset: , blur: , color: )

在iOS中,如果使用Quartz 2D API创建PDF或位图上下文,则正y的偏移量表示向上位移;如果图形上下文是由UIKit创建的,则正y偏移表示向下位移。阴影绘制约定不受当前转换矩阵的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
override func draw(_ rect: CGRect) {

let context = UIGraphicsGetCurrentContext()

context?.saveGState()
let shadowOffset = CGSize(width: -15, height: 20)

// 方式一:设置默认黑色阴影
context?.setShadow(offset: shadowOffset, blur: 5)
context?.setFillColor(red: 0, green: 1, blue: 0, alpha: 1)

context?.fill(CGRect(x: 50, y: 50, width: 50, height: 50))

// 方式二:设置彩色阴影
let colorSpace = CGColorSpaceCreateDeviceRGB()
let values:[CGFloat] = [1, 0, 0, 0.6]
let color = CGColor(colorSpace: colorSpace, components: values)
context?.setShadow(offset: shadowOffset, blur: 5, color: color)


context?.setFillColor(red: 0, green: 0, blue: 1, alpha: 1)
context?.fill(CGRect(x: 80, y: 100, width: 50, height: 50))

context?.restoreGState()
}

渐变色(Gradients)

Quartz 为创建 Gradients 提供了两种不透明的数据类型:CGShadingRefCGGradientRef。可以使用其中任何一个来创建轴向或径向渐变。渐变是一种从一种颜色到另一种颜色的填充。

  • axial gradient:轴向渐变(也称为线性渐变)沿两个端点之间的轴变化。垂直于坐标轴的直线上的所有点都有相同的颜色值。

  • radial gradient:径向渐变是在两个定义的端点之间沿轴径向变化的填充,这两个端点通常都是圆。如果点位于中心点落在轴上的圆的圆周上,则它们共享相同的颜色值。梯度的圆形截面的半径由端圆的半径定义;每个中间圆的半径从一端到另一端线性地变化。

CGShading 和 CGGradient

CGShadingCGGradient 都可以创建渐变,它们的区别是:

CGShadingRef 不透明数据类型使您可以控制渐变中每个点的颜色是如何计算的。在创建CGShading对象之前,您必须创建一个CGFunction对象(CGFunctionRef),它定义了一个在渐变中计算颜色的函数。

CGGradient CGShading
可以使用同一对象绘制轴向和径向渐变 需要为轴向和径向渐变创建单独的对象
在绘图时设置渐变的几何形状 在创建对象时设置渐变的几何形状
Quartz计算渐变中每个点的颜色 必须提供一个回调函数来计算渐变中每个点的颜色
易于定义两个以上的位置和颜色 需要你安排回调函数来使用两个以上的位置和颜色,因此您需要做更多的工作。

CGGradient的使用

1、绘制红色到蓝色的线性渐变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
override func draw(_ rect: CGRect) {
let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear)
let components: [CGFloat] = [1.0, 0, 0, 1.0, // start color 红色
0, 0, 1.0, 1.0] // end color 蓝色
let locations: [CGFloat] = [0.0, 1.0]

/// 根据相关的参数创建 CGGradient 对象
/// - Parameters:
/// - space: 用于渐变的颜色空间。不能使用 Indexed and Pattern ColorSpace
/// - components: 定义渐变的每种颜色的颜色组成部分
/// - locations: 为components中每种颜色提供的位置。取值范围为0~1,如果该数组中不包括0和1,则Quartz将为这些位置使用最接近0和1的颜色。
/// - count: 提供的位置(locations)个数
let gradient = CGGradient(colorSpace: colorSpace!, colorComponents: components, locations: locations, count: 2)

let context = UIGraphicsGetCurrentContext()

let startPt = CGPoint(x: 0, y: 0)
let endPt = CGPoint(x: rect.width, y: rect.height)

/// 绘制渐变填充
/// - Parameters:
/// - gradient: 渐变对象
/// - startPoint: 定义渐变起点的坐标
/// - endPoint: 定义渐变终点的坐标
/// - options:用于控制填充是延伸到起点还是终点之外。如: drawsBeforeStartLocation 表示填充超出起始的颜色,该颜色为CGGradient中定义locations位于0的纯色
context?.drawLinearGradient(gradient!, start: startPt, end: endPt, options: .drawsBeforeStartLocation)
}

2、绘制红色到蓝色的径向渐变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let startCenter = CGPoint(x: 50, y: 50)
let startRd: CGFloat = 25
let endCenter = CGPoint(x: 150, y: 150)
let endRd: CGFloat = 50

/// 绘制沿提供的起始圆和结束圆定义的区域变化的渐变填充
/// - Parameters:
/// - gradient: 渐变对象
/// - startCenter: 起始圆中心的坐标
/// - startRadius: 起始圆的半径
/// - endCenter: 结束圆中心的坐标
/// - endRadius: 结束圆的半径
/// - options: 用于控制渐变是在开始圆之前绘制还是在结束圆之后绘制。
context?.drawRadialGradient(gradient!, startCenter: startCenter, startRadius: startRd, endCenter: endCenter, endRadius: endRd, options: .drawsBeforeStartLocation)

当然你也可以使用同一个 CGGradient 对象来同时绘制渐变,我们可以把上面的渐变叠加绘制,效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear)
let components: [CGFloat] = [1.0, 0, 0, 1.0, // start color
0, 0, 1.0, 1.0] // end color
let locations: [CGFloat] = [0.0, 1.0]

let gradient = CGGradient(colorSpace: colorSpace!, colorComponents: components, locations: locations, count: 2)

let context = UIGraphicsGetCurrentContext()

let startPt = CGPoint(x: 0, y: 0)
let endPt = CGPoint(x: rect.width, y: rect.height)
context?.drawLinearGradient(gradient!, start: startPt, end: endPt, options: .drawsBeforeStartLocation)

CGShading的使用

关于 CGShading 的 API 的使用比较繁琐,后续研究了再作补充。

透明层(Transparency Layers)

Transparency Layer 由两个或多个对象组成,这些对象组合在一起产生一个复合图形。生成的组合被视为单个对象。当你想要对一组对象应用效果时,透明层是非常有用的。

如果没有透明层,三个矩形的阴影将不会作为整体设置阴影:

如下操作,通过使用透明层,使得三个矩形可以作为一个整体来设置阴影:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
override func draw(_ rect: CGRect) {

let context = UIGraphicsGetCurrentContext()

let shadowOffset = CGSize(width: 10, height: 20)
context?.setShadow(offset: shadowOffset, blur: 10)

let wd = rect.width
let ht = rect.height

// 透明层开始
// 指定上下文中的所有后续绘图操作都被组合到一个完全透明的背景中
context?.beginTransparencyLayer(auxiliaryInfo: nil)

context?.setFillColor(red: 0, green: 1, blue: 0, alpha: 1)
context?.fill(CGRect(x: wd/3 + 50, y: ht/2, width: 50, height: 50))

context?.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
context?.fill(CGRect(x: wd/3 + 25, y: ht/2 - 25, width: 50, height: 50))

context?.setFillColor(red: 0, green: 0, blue: 1, alpha: 1)
context?.fill(CGRect(x: wd/3, y: ht/4, width: 50, height: 50))

// 结束透明层
// 使用上下文的全局alpha和阴影状态将结果合成到上下文中
context?.endTransparencyLayer()
}

学习博客

Quartz 2D编程指南

Introduction to Color Management Overview

南峰子翻译的Quartz2D编程指南

Quartz 2D(一)概念、图形上下文、路径

Patterns模型的应用

通过 Core Graphics 绘制渐变颜色

文章作者: Czm
文章链接: http://yoursite.com/2020/09/07/Core-Graphics%E7%BB%98%E5%9B%BE-%E4%BA%8C/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Czm