美文网首页OCiOSiOS Developer
iOS 沿曲线线性渐变的贝塞尔曲线

iOS 沿曲线线性渐变的贝塞尔曲线

作者: XPorter | 来源:发表于2016-12-05 11:41 被阅读524次

    更新:换一种思路实现。iOS 沿曲线线性渐变的贝塞尔曲线(改进版)

    iOS原生的渐变只支持线性的渐变,但有的时候我们需要沿曲线进行渐变。
    先看下垂直线性渐变与沿曲线线性渐变的区别


    垂直线性渐变:颜色最亮的地方在曲线的最低点
    沿曲线线性渐变:颜色最亮的地方在起点

    那么先来分析一下这个问题:

    1. 怎样绘制曲线?
      对于贝塞尔曲线的绘制,系统提供了一系列的方法,同时我们已可以通过公式计算出一条贝塞尔曲线。
    2. 如何保证颜色渐变?
      找到曲线上的点,计算出每一个点的色值。

    只要解决上面的问题就可以画出一条沿曲线线性渐变的贝塞尔曲线,曲线画起来还是比较简单的,但是这样计算出每一点的色值是一件比较麻烦的事情。

    1、贝塞尔曲线

    这里先介绍一下贝塞尔曲线的一些东西,以二次贝塞尔曲线为例,先来动态感受一下绘制过程

    二次贝塞尔曲线

    一条二次贝塞尔曲线需要三个点,A:起点 ;B:控制点;C:终点


    然后在AB上取点E,在BC上取点F 。使AD:AB = BE:BC

    第一次取点

    在DE上取点F,使DF:DE = AD:AB = BE:BC


    第二次取点

    F点就是贝塞尔曲线上的一个点,以此类推,取点一系列的点之后在ABC之间就产生了一条贝塞尔曲线

    贝塞尔曲线

    可以看出贝塞尔曲线上的每个点是有规律的,二次贝塞尔曲线的方程为
    P0:起点;P1:控制点; P2:终点 ;t:百分比

    二次贝塞尔曲线的方程

    用OC表达的话就是这样的

    CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t) * t * _controlPoint.x + pow(t, 2) * _endPoint.x;
    CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y;
    

    2、渐变色

    既然已经一颗贝塞尔曲线的方程,那就可以操作曲线上的每一个点了,那怎么设置每一个点的颜色的。

    1、取点

    对于取点还有一个问题需要注意,由于贝塞尔曲线并不是匀速变化的,所有如果均匀分割 t 来进行取点的话,取出来的点是不均匀的。不均匀的点会造成有的地方缺失点,形成空白。所以需要对 t 进行修正,取出间隔均匀的点。

    均匀间隔的 t
    想要均匀的点,就需要线计算出曲线长度,接下来就使用辛普森积分法来计算曲线的长度。这个求的是二次贝塞尔曲线的长度,如果需更高次的曲线,可修改一下修改。
    //曲线长度
    - (CGFloat)lengthWithT:(CGFloat)t{
        NSInteger totalStep = 1000;
        
        NSInteger stepCounts = (NSInteger)(totalStep * t);
        
        if(stepCounts & 1) stepCounts++;
        
        if(stepCounts==0) return 0.0;
        
        NSInteger halfCounts = stepCounts/2;
        CGFloat sum1=0.0, sum2=0.0;
        CGFloat dStep = (t * 1.0)/stepCounts;
        for(NSInteger i=0; i<halfCounts; i++) {
            sum1 += [self speedAtT:(2*i+1)*dStep];
        }
        
        for(NSInteger i=1; i<halfCounts; i++) {
            sum2 += [self speedAtT:(2*i)*dStep ];
        }
        return ([self speedAtT:0]+[self speedAtT:1]+2*sum2+4*sum1)*dStep/3.0;
    }
    
    - (CGFloat)speedAtT:(CGFloat)t {
        CGFloat xSpeed = [self xSpeedAtT:t];
        CGFloat ySpeed = [self ySpeedAtT:t];
        CGFloat speed = sqrt(pow(xSpeed, 2) + pow(ySpeed, 2));
        return speed;
    }
    
    - (CGFloat)xSpeedAtT:(CGFloat)t {
        return 2 * (_startPoint.x + _endPoint.x - 2 * _controlPoint.x) * t + 2 * (_controlPoint.x - _startPoint.x);;
    }
    
    - (CGFloat)ySpeedAtT:(CGFloat)t {
        return 2 * (_startPoint.y + _endPoint.y - 2 * _controlPoint.y) * t + 2 * (_controlPoint.y - _startPoint.y);
    }
    
    - (CGFloat)xAtT:(CGFloat)t {
        CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t)* t * _controlPoint.x + pow(t, 2) * _endPoint.x;
        return x;
    }
    
    - (CGFloat)yAtT:(CGFloat)t {
        CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y;
        return y;
    }
    

    接下里就就开始矫正间隔了

    //矫正间隔
    - (CGFloat)uniformSpeedAtT:(CGFloat)t {
        CGFloat totalLength = [self lengthWithT:1.0];
        CGFloat len = t*totalLength; 
        CGFloat t1=t, t2;
        do {
            t2 = t1 -([self lengthWithT:t1] - len)/[self speedAtT:t1];
            if(fabs(t1-t2)<0.001) break;
            t1=t2;
        }while(true);
        return t2;
    }
    
    矫正间隔的取点

    加上颜色,就是这样了


    均匀的渐变色点

    接下来就是要去足够多的点来连成曲线了,这个取点的个数要根据具体情况来定,
    考虑到线的边界问题,处理起来太费事了,要进行更多的色值计算


    曲线的边界

    我的做法是先按线段长度取点,然后再根据每个点的速度及方向进行上下左右偏移,得到一条宽度足够的线之后在进行mask裁剪。

    偏移得到足够宽的线
    最终效果

    这个取点的方案还不是很完善,先放个demo吧至少效果是出来了,可以看一下具体的样子。


    下面的文章给了我很多的帮助,有需要的同学可以去看一下
    匀速贝塞尔曲线运动(续)
    一分钟就懂贝塞尔曲线
    Bezier - 匀速贝塞尔曲线运动的实现
    动态绘制贝塞尔曲线的在线演示

    相关文章

      网友评论

      • 退休老干部:学习了!
        问下 有办法利用 iOS 原生的UIBezierPath,计算对应百分比下,曲线端点的位置吗?
        类似于一个小球沿着曲线运动,但不是动画,中途是可以暂停的
        XPorter:有起始点的和控制点的话,就能得到一个贝塞尔曲线的方程式,可以通过百分比计算出对应的点的坐标的。你说的这个“曲线端点的位置”不就是曲线的起始点吗?
      • 南坞觉:楼主可知道纵向的颜色渐变怎么绘制?
        南坞觉:@黑白灰_ 解决了,谢谢楼主
        XPorter:http://www.cocoachina.com/ios/20161009/17704.html
        看下这个有没有你需要的
      • iOS阿能:算法很强
      • Yeeshe:我最近需要做延圆圈渐变的控件,难点就是在于中间结合的地方颜色平滑过渡,你这个可以用,但是考虑到性能的话,还是有点麻烦
        Yeeshe:@萝卜加 👌
        XPorter:@yxiang 沿圆渐变的话会简单点,毕竟圆的每个点的变化速率是一致的。https://github.com/paiv/AngleGradientLayer 你可以参考下这个。
        它在指定的区域内绘制所有的点,然后按照线宽做蒙层的。
      • Yeeshe:取线宽描点哪里,如果线宽比较宽的话,耗时会不会太多?
        Yeeshe:@萝卜加 还有没有其他更优的方案,借鉴下
        XPorter:会的,要计算出足够的点来包含所需的线宽

      本文标题:iOS 沿曲线线性渐变的贝塞尔曲线

      本文链接:https://www.haomeiwen.com/subject/qhskmttx.html