一、Gradients(渐变)
渐变是从一个颜色到另外一种颜色变化的填充。Quartz 提供了 CGShadingRef 和 CGGradientRef 两种数据类型来创建轴向或径向渐变。
- axial gradient --- 轴向渐变(也称为线性渐变)沿着由两个端点连接的轴线渐变。所有位于垂直于轴线的某条线上的点都具有相同的颜色值。
- radial gradient --- 径向渐变是沿着两个端点连接的轴线放射状渐变,不过路径通常由两个圆来定义,所有位于以轴线上点为圆心的圆的周长上点都具有相同的颜色值。
1、CGShading 和 CGGradient 对象的对比
CGGradient 是 CGShading 的子集,更易于使用,而 CGShading可以定义更加复杂的渐变。这两个类型都可以绘制轴向渐变和径向渐变。
CGGradient | CGShading |
---|---|
可以使用同一个CGGradient对象绘制轴向和径向渐变 | 需要为轴向渐变和径向渐变创建不同的 CGShading 对象 |
设置CGGradient对象的渐变的几何形状(轴向或径向)是在绘制时指定的 | 是在创建时指定的 |
Quartz来计算渐变梯度上每个点对应的颜色值(不需要过多的人为干扰) | 必须提供使用 CGFunctionRef 回调函数来计算渐变梯度上每个点对应的颜色值 |
可以容易的定义多个定位点和颜色 | 需要设计自己的回调函数来定义多个定位点和颜色,所以需要我们手动处理更多的东西 |
2、CGGradient 的使用
CGGradient 是渐变的抽象定义,它只指定了颜色和位置,但没有指定几何形状。我们可以在轴向和径向几何形状中使用它。这样使它更容易重用。
使用CGGradient对象创建和画一个渐变相当简单,需要以下步骤:
- 创建CGGradient对象:
- 使用函数
CGGradientRef __nullable CGGradientCreateWithColorComponents( CGColorSpaceRef cg_nullable space, const CGFloat * cg_nullable components, const CGFloat * __nullable locations, size_t count)
创建。参数含义:space --- 颜色空间,components --- 颜色组件, locations --- 位置组件(如果“ locations”为NULL,第一个颜色在“颜色”将在位置0,最后的颜色在“颜色”将在位置1,其余的将在中间均匀分布。否则每个位置的值都应该是0-1的CGFloat类型。如果没有提供0和1的位置,渐变将会使用接近0和1位置的那些值。), count --- 想使用的颜色组件数量。 - 使用函数
CGGradientRef __nullable CGGradientCreateWithColors( CGColorSpaceRef __nullable space, CFArrayRef cg_nullable colors, const CGFloat * __nullable locations)
创建。这里如果space不为NULL ,则所有颜色数组中的颜色都将转换到改颜色空间,若为NULL,则将会使用通用RGB颜色空间,但是iOS只允许使用设备空间颜色,所以iOS应用这里不能为NULL !!!。这里colors是CFArrayRef类型的,这里必须是包含CGColor类型对象的非空数组。
- 使用函数
- 绘制渐变:
- 使用函数
CGContextDrawLinearGradient(CGContextRef cg_nullable c, CGGradientRef cg_nullable gradient, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions options)
绘制线性渐变。 - 使用函数
CGContextDrawRadialGradient(CGContextRef cg_nullable c, CGGradientRef cg_nullable gradient, CGPoint startCenter, CGFloat startRadius, CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options)
创建径向渐变。
- 使用函数
这里参数options是一个枚举类型:
typedef CF_OPTIONS (uint32_t, CGGradientDrawingOptions) {
kCGGradientDrawsBeforeStartLocation = (1 << 0), // 渐变可以波及起点之前
kCGGradientDrawsAfterEndLocation = (1 << 1) // 渐变可以波及终点之后
};
- 当不再需要时释放CGGradient对象。
示例:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = {0.0, 0.1, 0.5, 0.6, 0.8, 1.0};
CGFloat colorComponents[] = {1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0,
1.0, 0.0, 1.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 1.0, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0};
gradient = CGGradientCreateWithColorComponents(colorSpace, colorComponents, locations, 6);
// CFArrayRef colorArray;
// NSArray * array = @[(id)[UIColor redColor].CGColor, (id)[UIColor cyanColor].CGColor, (id)[UIColor yellowColor].CGColor, (id)[UIColor blueColor].CGColor];
// colorArray = (__bridge CFArrayRef)array;
// gradient = CGGradientCreateWithColors(colorSpace, colorArray, locations);
// CGContextDrawLinearGradient(currentContext, gradient, CGPointMake(50, 50), CGPointMake(150, 150), 0); // 0 代表两端均不超范围渲染
CGContextDrawRadialGradient(currentContext, gradient, CGPointMake(50, 50), 30, CGPointMake(150, 150), 100, 0);
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
}
效果图:
3、CGShading的使用
CGShadingRef不透明的数据类型可以让你自己控制颜色在每个点的梯度计算。这就必须在创建一个CGShading对象之前,先创建一个CGFunction类型对象(CGFunctionRef),用以定义一个计算颜色渐变的函数。CGShading 使用户有更高的控制权,可以定义更加复杂的渐变。
因为创建CGFunctionRef函数中需要使用回调函数,所以下面从回调函数开始一步步去了解CGShading的使用。
- 创建CGFunctionRef函数中的回调函数:该回调函数是结构体类型:
struct CGFunctionCallbacks {
unsigned int version; // 版本数据一般使用0
CGFunctionEvaluateCallback __nullable evaluate; // 用于评估函数的回调,一般不可为NULL
CGFunctionReleaseInfoCallback __nullable releaseInfo; // 如果用于评估函数的回调中info为NULL,则这里也可以是NULL,
// 若不为NULL,该回调用于在CGFunction被销毁时release在CGFunction 创建时传给function的info参数信息。
};
typedef struct CGFunctionCallbacks CGFunctionCallbacks;
创建评估函数回调:样式必须与下面的类似,函数名可以随意,但是参数必须一致!
void (*CGFunctionEvaluateCallback)(void * __nullable info, const CGFloat * in, CGFloat * out)
(1、) info : 这是一个接受CGFunctionCreate函数传递的info信息的参数。
(1、) in:浮点数的数组,Quartz 传递 in 数组给回调,数组中的值必须在CGFunction对象定义的输入值范围内。数组的大小是由CGFunctionCreate函数中domainDimension参数传递的数据指定的。
(1、) out:浮点数的数组,回调函数传递 out 数组给 Quartz,它包含用于颜色空间中每个颜色组件的元素及一个 alpha 值。输出值应该在 CGFunction 对象定义的输出值范围内。数组的大小是由CGFunctionCreate函数中rangeDimension参数传递的数据指定的。
示例:
// 这一函数实现的是平方的功能
void evaluateSquare(void *info, const float *inData, float *outData)
{
outData[0] = inData[0] * inData[0];
}
void MyFunctionEvaluateCallback(void * __nullable 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; // 设置算法,获取想要输出的结果。
// 该结果是指输出结果为在基准颜色定义上获取与输入值相乘结果后的颜色
// 在下面的例子中输入的domainDimension参数为{0, 1},所以输出颜色会为(0,0,0)和(1,0,0.5)
}
*out++ = 1; // 将 alpha值恒为 1。
}
- 创建CGFunctionRef函数:使用函数
CGFunctionRef __nullable CGFunctionCreate(void * __nullable info, size_t domainDimension, const CGFloat *__nullable domain, size_t rangeDimension, const CGFloat * __nullable range, const CGFunctionCallbacks * cg_nullable callbacks)
。其中参数的意义:- info : 传递给回调函数的额外信息的参数,可以为NULL.
- domainDimension :输入给回调函数的值的数量。
- domain:是一个含有2M个元素的字符数组,M是输入值的数量即domainDimension。对于从0到(M-1)之间的每一个数字k来说,必须有domain[2k] <= domain[2k + 1],在该区间内in[k]将会被限制剪切,即domain[2k] <= in[k] <= domain[2k + 1]。即:给每个输入值规定一范围,输入值必须在设置的范围以内。 如果domain 为NULL,则输入值不会被剪切。但强烈建议设置domain。
- rangeDimension:从回调函数输出的值的数量。
- range:与domain一样,不过是针对的输出值!!
- callbacks:回调函数。
示例:
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, &MyFunctionEvaluateCallback, NULL};
numComponents = 1 + CGColorSpaceGetNumberOfComponents (colorspace);
return CGFunctionCreate ((void *) numComponents, 1, input_value_range, numComponents, output_value_ranges, &callbacks);
}
创建CGShading对象:一、使用函数
CGShadingRef __nullable CGShadingCreateAxial( CGColorSpaceRef cg_nullable space, CGPoint start, CGPoint end, CGFunctionRef cg_nullable function, bool extendStart, bool extendEnd)
创建线性渐变。参数:space - 颜色空间、start - 起始点、end - 结束点、function - 计算函数、extendStart - 是否渲染起始点以外的区域、extendEnd - 是否渲染j结束点以外的区域。 二、使用函数CGShadingRef __nullable CGShadingCreateRadial( CGColorSpaceRef cg_nullable space, CGPoint start, CGFloat startRadius, CGPoint end, CGFloat endRadius, CGFunctionRef cg_nullable function, bool extendStart, bool extendEnd)
创建径向渐变。参数详情这里不再赘述。Clip the Context And Drawing CGShading Object(裁剪上下文、绘制CGShading对象)
绘制渐变的工作原理不同于颜色和图案可以使用stroke和fill描绘对象。如果要出现想要的特定形状需要相应的剪切上下文。使用绘制函数CGContextDrawShading(CGContextRef cg_nullable c, cg_nullable CGShadingRef shading)
进行绘制最后将创建的都要记得释放掉。
完整示例:线性渐变
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGShadingRef shading;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFunctionRef function = MyGetFunction(colorSpace);
shading = CGShadingCreateAxial(colorSpace, CGPointMake(50, 50), CGPointMake(150, 150), function, NO, NO);
CGContextSaveGState(currentContext);
UIBezierPath * bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(100, 100) radius:50 startAngle:M_PI endAngle:M_PI * 2 clockwise:YES];
[bezierPath closePath];
CGContextAddPath(currentContext, bezierPath.CGPath);
CGContextClip(currentContext);
CGContextDrawShading(currentContext, shading);
CGColorSpaceRelease(colorSpace);
CGFunctionRelease(function);
CGShadingRelease(shading);
CGContextRestoreGState(currentContext);
}
void MyFunctionEvaluateCallback(void * __nullable 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;
}
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, &MyFunctionEvaluateCallback, NULL};
numComponents = 1 + CGColorSpaceGetNumberOfComponents (colorspace);
return CGFunctionCreate ((void *) numComponents, 1, input_value_range, numComponents, output_value_ranges, &callbacks);
}
完整示例:径向渐变
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGShadingRef shading;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFunctionRef function = MyGetFunction(colorSpace);
shading = CGShadingCreateRadial(colorSpace, CGPointMake(50, 300), 50, CGPointMake(200, 100), 100, function, NO, NO);
CGContextSaveGState(currentContext);
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 300, 400)];
CGContextAddPath(currentContext, bezierPath.CGPath);
CGContextClip(currentContext);
CGContextDrawShading(currentContext, shading);
CGColorSpaceRelease(colorSpace);
CGFunctionRelease(function);
CGShadingRelease(shading);
CGContextRestoreGState(currentContext);
}
void MyFunctionEvaluateCallback(void * __nullable 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
}
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, &MyFunctionEvaluateCallback, NULL};
numComponents = 1 + CGColorSpaceGetNumberOfComponents (colorspace);
return CGFunctionCreate ((void *) numComponents, 1, input_value_range, numComponents, output_value_ranges, &callbacks);
}
从上面两个示例代码可以看出这两个示例代码最重要的两个差异的地方就是:1、线性渐变示例代码创建shading对象使用CGShadingCreateAxial() 函数,而径向渐变示例代码使用CGShadingCreateRadial() 函数,这决定了绘制渐变的本质的不同。2、回调函数部分完全不同,回调函数其实就是决定了计算渐变梯度上每个点对应的颜色值。根据不同的计算方式可以实现各种渐变展示,当然这很需要对数学函数的理解~💀💀💀。
二、Transparency Layers(透明层)
透明层由两个或两个以上的对象组合而产生一个复合图形。复合结果被视为一个单独的对象。透明层是可以嵌套的。
绘制Transparency Layers:
-
开启透明层绘制:
- 使用函数
CGContextBeginTransparencyLayer(CGContextRef cg_nullable c, CFDictionaryRef __nullable auxiliaryInfo)
字典让你提供选项来指定关于layer的附加信息,但由于在Quartz 2D API中该字典还没有被使用,所以传递NULL!!! - 使用函数
CGContextBeginTransparencyLayerWithRect( CGContextRef cg_nullable c, CGRect rect, CFDictionaryRef __nullable auxInfo)
相对于上一函数多了一个限制边界的参数rect。
- 使用函数
在透明层中绘制需要组合的对象。
调用函数
CGContextEndTransparencyLayer(CGContextRef cg_nullable c)
结束透明层绘制。
示例:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGSize myShadowOffset = CGSizeMake (-20, 30);
CGContextSetShadow (currentContext, myShadowOffset, 10);
CGContextBeginTransparencyLayer(currentContext, NULL);
CGGradientRef gradientCop;
CGGradientRef gradientBed;
CGColorSpaceRef colorSpace;
colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat colorComponents[] = {1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0,
1.0, 0.0, 1.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 1.0, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0};
gradientCop = CGGradientCreateWithColorComponents(colorSpace, colorComponents, NULL, 6);
gradientBed = CGGradientCreateWithColorComponents(colorSpace, colorComponents, NULL, 6);
CGContextSaveGState(currentContext);
UIBezierPath * copPath = [UIBezierPath bezierPath];
[copPath moveToPoint:CGPointMake(70, 20)];
[copPath addQuadCurveToPoint:CGPointMake(50, 90) controlPoint:CGPointMake(100, 55)];
[copPath addLineToPoint:CGPointMake(150, 90)];
[copPath addQuadCurveToPoint:CGPointMake(130, 20) controlPoint:CGPointMake(100, 55)];
[copPath closePath];
CGContextAddPath(currentContext, copPath.CGPath);
CGContextClip(currentContext);
CGContextDrawLinearGradient(currentContext, gradientCop, CGPointMake(20, 55), CGPointMake(180, 55), 0);
CGGradientRelease(gradientCop);
CGContextRestoreGState(currentContext);
CGContextSaveGState(currentContext);
UIBezierPath * bedPath = [UIBezierPath bezierPath];
[bedPath moveToPoint:CGPointMake(90, 90)];
[bedPath addLineToPoint:CGPointMake(110, 90)];
[bedPath addLineToPoint:CGPointMake(110, 190)];
[bedPath addQuadCurveToPoint:CGPointMake(100, 210) controlPoint:CGPointMake(115, 195)];
[bedPath addQuadCurveToPoint:CGPointMake(90, 190) controlPoint:CGPointMake(85, 195)];
[bedPath closePath];
CGContextAddPath(currentContext, bedPath.CGPath);
CGContextClip(currentContext);
CGContextDrawRadialGradient(currentContext, gradientBed, CGPointMake(100, 210), 0, CGPointMake(100, 90), 10, 0);
CGGradientRelease(gradientBed);
CGContextRestoreGState(currentContext);
CGColorSpaceRelease(colorSpace);
CGContextEndTransparencyLayer(currentContext);
}