颜色空间(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 () context?.beginPath() context?.addRect(CGRect (x: 50 , y: 50 , width: 100 , height: 100 )) context?.setFillColor(UIColor .green.cgColor) context?.fillPath() context?.beginPath() context?.addRect(CGRect (x: 75 , y: 75 , width: 50 , height: 50 )) context?.setFillColor(UIColor .red.cgColor) context?.fillPath() }
相关概念 颜色管理 颜色管理:是在设备之间保持颜色一致的过程。不同的成像设备(如扫描仪、显示器和打印机)在不同的颜色空间中工作,并且每个设备都有不同的色域(设备可以显示的颜色范围)。
由于种种因素(设备类型、使用年限、不同的打印技术、墨水纸张等等),从单个显示器上的RGB颜色转换为使用特定墨水和纸张类型的单个打印机上的CMYK颜色可能会导致不可预测的结果。
颜色 颜色:是一种感觉,因此也是一种主观体验。颜色的感觉是视觉的一个组成部分,是由人眼对光线的敏感性引起的。颜色感觉也受到大脑处理信息的方式的影响,而且是因人而异的。因此,颜色感知是一种非常复杂的现象。
计算机使您能够数字化地控制颜色,并且已经开发了许多外设来获取、显示和再现颜色。因此,需要一种机制来维持环境中的颜色控制,这种环境可以包括不同的计算机操作系统和硬件,以及连接到计算机的各种设备和媒体。
色调,饱和度和值(或亮度) 颜色被描述为具有三个维度。这些维度是色调、饱和度和值。
色调:是颜色的名称,它使颜色在光谱中处于正确的位置。
饱和度:是指颜色的强度程度,或颜色的强度。例如:粉色是不饱和红色的一个例子。
值(或亮度):描述从彩色图像反射或透射的光的强度的差异。
加色法和减色法 加色法,是指红、绿、蓝(RGB三原色)三种光它们按不同比例相加而混合出其他色彩的一种方法。
减色法,是指着色剂如油墨或染料组合的过程。各种含量着色剂会吸收或“减去”照明对象的白光光谱的一部分,物体的颜色是不被物体吸收的颜色的光。
显示器使用加色法,输出打印设备使用减色法。
Color Spaces(颜色空间) 颜色空间描述不同的外围设备如何表示颜色以及用于表示颜色的值。它述了一种环境,在这种环境中,颜色被表示、排序、比较或计算。
颜色空间定义了一维、二维、三维或四维的环境,其组成部分(或颜色组成部分)代表强度值。颜色组成部分也称为 颜色通道。
例如,RGB空间是一种三维颜色空间,它是根据红色、绿色和蓝色的强度相互促进来确定一种颜色。红、绿和蓝是它的颜色通道。
Apple 的 ColorSync技术 直接支持几种不同的颜色空间,让你在最适合你需要的任何颜色数据中都很方便。ColorSync颜色空间可以分为几个组或基族,它们是:
Gray spaces:用于灰度显示和打印
RGB-based color spaces:主要用于显示器和扫描仪
CMYK-based color spaces:主要用于彩色打印
Device-independent color spaces:如La b,主要用于颜色比较、色差和颜色转换
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 ()let rgbSpace = CGColorSpaceCreateDeviceRGB ()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() context?.setLineWidth(2.0 ) 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 context?.setRenderingIntent(.relativeColorimetric)
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 () 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 ) 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 cell
是 pattern
的基本组成部分。
当您绘制 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 var callBacks = CGPatternCallbacks (version: 1 , drawPattern: { (pointer, context) in print (pointer?.load(as : Int .self ) ?? 111 ) }) { (pointer) in print ("释放资源" ) }; var arr = 321 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 绘制填充的矩形。使用 color
和 pattern
绘制的区别在于必须定义图案。
使用 Pattern
填充或描边时,Quartz
会执行以下任务来绘制每个图案单元格:
保存图像状态(graphics state);
将当前转换矩阵转换为 Pattern Cell
的原点;
将 CTM
与 模式矩阵连接起来;
剪辑到 Pattern Cell
的边界矩形;
调用绘图回调函数来绘制 Pattern Cell
;
恢复图形状态(graphics state);
Quartz负责为处理 Tiling(平铺),反复将 Pattern Cell
呈现到绘图空间,直到绘制完整个空间,可以使用描边或填充Pattern Cell
。Pattern 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 override func draw (_ rect: CGRect) { 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() let patternSpace = CGColorSpace (patternBaseSpace: nil ) context?.setFillColorSpace(patternSpace!) 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); var alp: CGFloat = 1.0 context?.setFillPattern(pattern!, colorComponents: &alp) 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) { 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 ("释放资源" ) }; 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 () let baseSpace = CGColorSpaceCreateDeviceRGB () let patternSpace = CGColorSpace (patternBaseSpace: baseSpace) context?.setFillColorSpace(patternSpace!) let colors:[CGFloat ] = [246 /255.0 , 190 /255.0 , 73 /255.0 , 1 ] context?.setFillPattern(pattern!, colorComponents: colors) context?.fill(rect) }
阴影(Shadows) 阴影是画在图形对象下面并与之偏移的图像,这样阴影就模仿了光源投射在图形对象上的效果。
阴影有三个属性:
x偏移量,它指定阴影在水平方向上与图像的偏移距离。
y偏移量,它指定阴影在垂直方向上与图像的偏移距离。
模糊值,它指定图像是具有硬边缘,还是漫反射边缘。
Quartz中的 Shadows
是 graphics 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
提供了两种不透明的数据类型:CGShadingRef
和 CGGradientRef
。可以使用其中任何一个来创建轴向或径向渐变。渐变是一种从一种颜色到另一种颜色的填充。
CGShading 和 CGGradient CGShading
和 CGGradient
都可以创建渐变,它们的区别是:
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 , 0 , 0 , 1.0 , 1.0 ] 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) }
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 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 , 0 , 0 , 1.0 , 1.0 ] 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 )) context?.endTransparencyLayer() }
学习博客 Quartz 2D编程指南
Introduction to Color Management Overview
南峰子翻译的Quartz2D编程指南
Quartz 2D(一)概念、图形上下文、路径
Patterns模型的应用
通过 Core Graphics 绘制渐变颜色