Quartz2D --> 二维绘图引擎(四-渐变、透明层)

一、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);
}

效果图:

CGGradientCreateWithColors()函数创建的线性渐变

CGGradientCreateWithColorComponents()函数创建线性渐变
径向渐变
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);
}
效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容