Pattern

Pattern可翻译为样式、模型。类似于iOS中自定义的view,可以重复多次把自定义的view添加到控制器的view上。样式(Pattern)是绘制操作的一个序列,可以重复的绘制到一个图形上下文上。

样式单元格

样式单元格是样式的基本组件,在创建的时候就要定义单元格的边界,并在这个范围内进行绘制。在图形上下文上绘制单元格时,可以随意指定两个单元格之间上下左右间距,也可以指定负数,单元格重合而已。像极了添加view控件。

着色样式和模板样式

着色样式是有固定颜色的,在创建样式单元格的时候就指定其颜色了,跟绘制流程没有关系。

模板样式即为非着色样式,在创建样式单元格的时候只指定了形状,并没有指定颜色,在绘制过程中指定颜色。

在Quartz 2D中可以使用这两种样式实现样式绘图,将创建好的样式绘制到page上的过程称为平铺,有三种平铺选项:1、没有失真,2、最小的失真的恒定间距,3、恒定间距。

绘制着色样式

绘制着色样式一般需要下面几步:1、设置样式单元格的回调函数,2、设置着色模式单元格,3、绘制操作。下面是绘制着色样式的示例代码和运行结果:

       
       
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
       
       
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
clorePatternPaingting(context, CGRectMake(0, 0, 300, 500));
}
void clorePatternPaingting(CGContextRef context, CGRect rect) {
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);
static const CGPatternCallbacks call = {0, &drawPattern, NULL};
CGPatternRef pattern = CGPatternCreate(NULL, CGRectMake(0, 0, 30, 30), CGAffineTransformIdentity, 30, 30, kCGPatternTilingConstantSpacing, true, &call);
CGFloat alpha = 1.0;
CGContextSetFillPattern(context, pattern, &alpha);
CGPatternRelease(pattern);
CGContextFillRect(context, rect);
}
void drawPattern(void *info, CGContextRef context) {
CGContextSetRGBFillColor(context, 1, 0, 0, 1);
CGContextFillRect(context, CGRectMake(0, 0, 10, 10));
CGContextSetRGBFillColor(context, 0, 1, 0, 1);
CGContextFillRect(context, CGRectMake(10, 0, 10, 10));
CGContextSetRGBFillColor(context, 0, 0, 1, 1);
CGContextFillRect(context, CGRectMake(0, 10, 10, 10));
CGContextSetRGBFillColor(context, 0.5, 0, 0.5, 1);
CGContextFillRect(context, CGRectMake(10, 10, 10, 10));
}

运行结果:

绘制着色模式的运行结果绘制着色模式的运行结果

代码解释:

上述代码块中第三个函数drawPattern的作用就是第1步:设置样式单元格的回调函数。参数info:是指向模式相关数据的指针,可以传NULLcontext:是图形上下文。在该函数中指定了每个颜色块的大小和填充色。因为是着色样式,所以指定填充色。

第二个函数,也是在drawRect方法中调用的函数clorePatternPaingting,执行了第2步:设置着色模式单元格和第3步:绘制操作。下面对每块代码进行解释:

清空基本颜色空间:

       
       
1
2
3
       
       
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);

因为我们要使用着色样式里面的颜色,所以清空基本颜色空间,以确保着色样式上的颜色能显示。可以试一下把这段代码注释的结果,绘制着色样式的空间为黑色,因为基本颜色空间没清空也没着色,所以呈黑色。

设置着色模式单元格:

       
       
1
2
       
       
static const CGPatternCallbacks call = {0, &drawPattern, NULL};
CGPatternRef pattern = CGPatternCreate(NULL, CGRectMake(0, 0, 30, 30), CGAffineTransformIdentity, 30, 30, kCGPatternTilingConstantSpacing, true, &call);

模式单元格的信息保存在CGPatternRef类型的CGPattern对象中,使用CGPatternCreate创建该对象。需要传入的参数依次解释为:

  1. info:是一个指针,指向我们要传递给绘制回调函数的数据。这里传的是NULL
  2. bound:指定模式单元格的大小
  3. matrix:指定模式矩阵,它将模式坐标系统映射到图形上下文的默认坐标系统。如果希望两个坐标系统是一样的,则可以使用单位矩阵。
  4. xStep, yStep:指定单元格之间的水平和竖直间距。
  5. tiling:平铺模式,可以是kCGPatternTilingNoDistortion、kCGPatternTilingConstantSpacingMinimalDistortion、kCGPatternTilingConstantSpacing
  6. isColored:指定模式单元格是着色模式(true)还是模板模式(false)
  7. callbacks:是一个指向CGPatternCallbacks结构体的指针。格式如下:
       
       
1
2
3
4
5
       
       
struct CGPatternCallbacks {
unsigned int version;
CGPatternDrawPatternCallback __nullable drawPattern;
CGPatternReleaseInfoCallback __nullable releaseInfo;
};

version一般设置为0drawPattern是指向设置样式单元格的回调函数,releaseInfo是指向回调函数,释放CGPattern对象时调用,以释放存储在我们传递给绘制回调的info参数中的数据。如果在这个参数中没有传递任何数据传NULL

所以CGPatternCallbacks结构体定义为static const CGPatternCallbacks call = {0, &drawPattern, NULL};

绘制着色样式

       
       
1
2
3
4
       
       
CGFloat alpha = 1.0;
CGContextSetFillPattern(context, pattern, &alpha);
CGPatternRelease(pattern);
CGContextFillRect(context, rect);

调用函数CGContextSetFillPattern使用着色样式进行填充操作,或者调用函数CGContextSetStrokePattern进行描边操作。需要指定着色样式在填充或描边过程的透明度。

样式使用完毕后需要释放,调用函数:CGPatternRelease

将定义的着色样式使用函数CGContextFillRect填充到指定rect的矩形中。

这一系列操作都放到函数clorePatternPaingting内,在drawRect方法中直接调用该函数即可完成着色样式的定义和绘制。

绘制模板样式

绘制模板样式的步骤和绘制着色样式的一样:1、设置样式单元格的回调函数,2、设置模板模式单元格,3、绘制操作。下面是绘制模板样式的示例代码和运行结果:

       
       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
       
       
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
stencilPatternPaingting(context, CGRectMake(0, 0, 300, 500));
}
void stencilPatternPaingting(CGContextRef context, CGRect rect) {
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(baseSpace);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);
CGColorSpaceRelease(baseSpace);
static const CGPatternCallbacks cell = {0, &drawStencilPattern, NULL};
CGPatternRef pattern = CGPatternCreate(NULL, CGRectMake(0, 0, 30, 30), CGAffineTransformIdentity, 40, 40, kCGPatternTilingConstantSpacing, false, &cell);
static const CGFloat color[] = {1, 0, 0, 1};
CGContextSetFillPattern(context, pattern, color);
CGPatternRelease(pattern);
CGContextFillRect(context, rect);
}
void drawStencilPattern(void *info, CGContextRef context) {
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 30, 30));
CGContextFillPath(context);
}

运行结果:

绘制模板样式的运行结果绘制模板样式的运行结果

代码解释参考上文中着色样式的代码解释。区别1是用下述代码设置样式单元格的颜色:

       
       
1
2
       
       
static const CGFloat color[] = {1, 0, 0, 1};
CGContextSetFillPattern(context, pattern, color);

区别2是CGPatternCreate函数中传的参数是false

阴影

阴影有三个属性:x偏移量,y偏移量,模糊度

阴影通过函数CGContextSetShadow设置,指定图形上下文、x偏移量、y偏移量、模糊度。设置阴影后,绘制的对象都会有阴影,阴影的色值为0.3透明度的黑色,颜色RGBA值为{0, 0, 0, 0.3}。

也可以是使用函数CGContextSetShadowWithColor设置彩色阴影。

在调用CGContextSetShadowCGContextSetShadowWithColor之前保存图形状态,可以通过恢复图形状态来关闭阴影。或者设置阴影颜色为NULL关闭阴影。

分别设置彩色阴影和普通阴影:

       
       
1
2
3
4
5
6
7
8
9
       
       
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(10, 10), 2, [UIColor redColor].CGColor);
CGContextSetRGBFillColor (context, 0, 1, 0, 1);
CGContextAddEllipseInRect(context, CGRectMake(40, 40, 100, 100));
CGContextFillPath(context);
CGContextSetShadow(context, CGSizeMake(10, 10), 2);
CGContextFillRect(context, CGRectMake(200, 40, 100, 100));
CGContextRestoreGState(context);

运行结果:

圆形的红色阴影和矩形的灰色阴影圆形的红色阴影和矩形的灰色阴影

x偏移量正值在图形右边,负值在图形左边。y偏移量和坐标有关,在iOS中,UIKit坐标系y轴和Quartz 2D坐标系y轴相反,所以如果是由UIKit创建的图形上下文则正值在图形下面,负值在图形上面。macOS的坐标系和Quartz 2D坐标系一样,所以正值在图形上面,负值在图形下面。阴影绘制不受CTM影响。

渐变

可以使用函数CGShadingRefCGGradientRef创建渐变。渐变分为轴向渐变和径向渐变。渐变的颜色变化有很多,可以是一种颜色到另一种颜色过度,也可以是多种颜色依次过度变化,还有是一种颜色的透明度alpha变化,但是这种无法绘制到PDF上,无法打印。

CGShadingCGGradient的区别:

CGGradientCGShading
可以使用同一对象绘制轴向和径向梯度需要创建单独的对象的轴向和径向梯度
在绘图时设置渐变的几何体在对象创建时间设置渐变的几何体
Quartz计算梯度中每个点的颜色需要提供一个回调函数,用来计算渐变中每个点的颜色
可以定义两个以上的位置和颜色需要设计回调,使用两个以上的位置和颜色

CGGradient

使用CGGradient创建渐变。

  1. 首先调用函数CGGradientCreateWithColorComponents创建CGGradient对象,参数含义分别是:颜色空间、颜色数组(采用RGBA颜色)、位置数组(0.0-1.0之间的数值,0.0为轴线起点,1.0为轴线终点。传递NULL默认0为起始位置,1为终点位置,相当于{0.0, 1.0})、数组中元素个数。
  2. 调用CGContextDrawLinearGradient函数绘制轴向渐变或CGContextDrawRadialGradient函数绘制径向渐变,参数含义分别是:图形上下文、CGGradient对象、起始位置、终止位置、绘图选项。
  3. 绘制完毕后,释放颜色空间和CGGradient对象。

绘制轴向渐变:

       
       
1
2
3
4
5
6
7
8
       
       
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat compoents[] = {1,0,1,1, 0.3,0.5,1,1.0};
CGFloat locations[] = {0.0, 1.0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
CGContextDrawLinearGradient(context, gradient, CGPointMake(0, 0), CGPointMake(0, 100), kCGGradientDrawsBeforeStartLocation);
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);

使用CGGradient绘制轴向渐变使用CGGradient绘制轴向渐变

绘制径向渐变:

       
       
1
2
3
4
5
6
7
8
       
       
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat compoents[] = {1,0,1,1, 0.3,0.5,1,1.0};
CGFloat locations[] = {0.0, 1.0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
CGContextDrawRadialGradient(context, gradient, CGPointMake(200, 50), 20, CGPointMake(200, 400), 70, kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);

使用CGGradient绘制径向渐变使用CGGradient绘制径向渐变

CGShading

绘制轴向渐变需要以下步骤:

  1. 设置 CGFunction 对象来计算颜色值
  2. 创建轴向渐变的 CGShading 对象
  3. 裁减上下文
  4. 使用 CGShading 对象来绘制轴向渐变
  5. 释放对象

绘制轴向渐变完整代码:

       
       
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
       
       
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFunctionRef functionObj = getFunction(colorspace);
CGShadingRef shading = CGShadingCreateAxial(colorspace, CGPointMake(0, 0.5), CGPointMake(1, 0.5), functionObj, false, false);
CGAffineTransform myTransform = CGAffineTransformMakeScale(100, 100);
CGContextConcatCTM (context, myTransform);
CGContextSaveGState (context);
CGContextBeginPath(context);
CGContextAddArc(context, .5, .5, .3, 0, M_PI, 0);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawShading(context, shading);
CGShadingRelease(shading);
CGColorSpaceRelease(colorspace);
CGFunctionRelease(functionObj);
CGContextRestoreGState (context);
}
static CGFunctionRef getFunction(CGColorSpaceRef colorspace) {
size_t numComponents = 1 + CGColorSpaceGetNumberOfComponents(colorspace);
CGFloat input_value_range[] = {0, 1};
CGFloat output_value_range[] = {0, 1, 0, 0, 0,0,1,1};
CGFunctionCallbacks callbacks = {0, &calculateShadingValues, NULL};
return CGFunctionCreate((void *) numComponents, 1, input_value_range, numComponents, output_value_range, &callbacks);
}
static void calculateShadingValues(void *info, const CGFloat *in, CGFloat *out) {
CGFloat v;
size_t k, compoents;
static const CGFloat c[] = {1,0,0.5,1};
compoents = (size_t)info;
v = *in;
for (k = 0; k < compoents - 1; k ++)
*out ++ = c[k] * v;
*out++ = 1;
}

运行结果:

使用CGShading绘制轴向渐变使用CGShading绘制轴向渐变

绘制径向渐变,需要以下步骤:

  1. 设置 CGFunction 对象来计算颜色值
  2. 创建径向渐变的 CGShading 对象
  3. 使用 CGShading 对象来绘制径向渐变
  4. 释放对象

绘制径向渐变完整代码:

       
       
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
44
45
       
       
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFunctionRef functionObj = getFunction(colorspace);
CGShadingRef shading = CGShadingCreateRadial(colorspace, CGPointMake(0.25, 0.3), 0.1, CGPointMake(0.7, 0.7), 0.25, functionObj, false, false);
CGAffineTransform myTransform = CGAffineTransformMakeScale(100, 100);
CGContextConcatCTM (context, myTransform);
CGContextSaveGState (context);
CGContextClipToRect (context, CGRectMake(0, 0, 1, 1));
CGContextSetRGBFillColor (context, 1, 1, 1, 1);
CGContextFillRect (context, CGRectMake(0, 0, 1, 1));
CGContextDrawShading(context, shading);
CGShadingRelease(shading);
CGColorSpaceRelease(colorspace);
CGFunctionRelease(functionObj);
CGContextRestoreGState (context);
}
static CGFunctionRef getFunction(CGColorSpaceRef colorspace) {
size_t numComponents = 1 + CGColorSpaceGetNumberOfComponents(colorspace);
CGFloat input_value_range[] = {0, 1};
CGFloat output_value_range[] = {0, 1, 0, 0, 0,0,1,1};
CGFunctionCallbacks callbacks = {0, &calculateShadingValues, NULL};
return CGFunctionCreate((void *) numComponents, 1, input_value_range, numComponents, output_value_range, &callbacks);
}
static void calculateShadingValues(void *info, const CGFloat *in, CGFloat *out) {
size_t k, compoents;
double frequency[] = {55, 220, 110, 0};
compoents = (size_t)info;
for (k = 0; k < compoents - 1; k ++)
*out++ = (1 + sin(*in * frequency[k])) / 2;
*out++ = 1;
}

使用CGShading绘制径向渐变使用CGShading绘制径向渐变


参考文章:南峰子翻译的Quartz2D编程指南