iOS 动画家族首页投稿(暂停使用,暂停投稿)iOS Developer

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

2017-08-01  本文已影响90人  寻形觅影

一、Gradients(渐变)

渐变是从一个颜色到另外一种颜色变化的填充。Quartz 提供了 CGShadingRef 和 CGGradientRef 两种数据类型来创建轴向或径向渐变。

1、CGShading 和 CGGradient 对象的对比

CGGradient 是 CGShading 的子集,更易于使用,而 CGShading可以定义更加复杂的渐变。这两个类型都可以绘制轴向渐变和径向渐变。

CGGradient CGShading
可以使用同一个CGGradient对象绘制轴向和径向渐变 需要为轴向渐变和径向渐变创建不同的 CGShading 对象
设置CGGradient对象的渐变的几何形状(轴向或径向)是在绘制时指定的 是在创建时指定的
Quartz来计算渐变梯度上每个点对应的颜色值(不需要过多的人为干扰) 必须提供使用 CGFunctionRef 回调函数来计算渐变梯度上每个点对应的颜色值
可以容易的定义多个定位点和颜色 需要设计自己的回调函数来定义多个定位点和颜色,所以需要我们手动处理更多的东西
2、CGGradient 的使用

CGGradient 是渐变的抽象定义,它只指定了颜色和位置,但没有指定几何形状。我们可以在轴向和径向几何形状中使用它。这样使它更容易重用。

使用CGGradient对象创建和画一个渐变相当简单,需要以下步骤:

这里参数options是一个枚举类型:

typedef CF_OPTIONS (uint32_t, CGGradientDrawingOptions) {
  kCGGradientDrawsBeforeStartLocation = (1 << 0), // 渐变可以波及起点之前
  kCGGradientDrawsAfterEndLocation = (1 << 1) // 渐变可以波及终点之后
};

示例:

- (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的使用。

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。
}

  示例:

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 = 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:

示例:

- (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);
}
效果图
上一篇 下一篇

猜你喜欢

热点阅读