iOS 沿曲线线性渐变的贝塞尔曲线(改进版)

对于渐变曲线的画法,经过一些的思量之后,想到了了另外一种思路。以前的思路是,通过t值来获取点,为了保证点的个数足够的多,所以就大致取了一个可以覆盖整条曲线的点数,但是这样盲目的去点会存在点数过多和去点重复的问题。同时这样也不支持线宽的设置。所以就有了另外一只思路来实现渐变曲线。

对于贝塞尔曲线的一下东西,如果需要了解的可以看看这个

这种方式的大致思路是,先获取到贝塞尔曲线上所有的点,然后在计算每个点的t值,然后根据t值来计算每个点的颜色。

1、获取贝塞尔曲线上所有的点

如何获取贝塞尔曲线上所有的点?这个其实是比较简单的,可以利用UIBezierPath画一条曲线,渲染到CAShapeLayer (fillColor:clearColor,strokeColor:redColor)上,然后遍历CAShapeLayer上的像素,只要像素的有色值那就是需要的点。同时由于这样渲染出的线条已经处理好了锯齿问题(即像素透明度), 所以为后面的处理省下了很多的事情。

2、计算每个点的 t 值

现在已经得到了需要的点,剩下的就是计算每个点的t值了。计算t值也就是一个解方程的过程,这里说的是二次贝塞尔曲线,涉及到的就是一元二次方程。但是在像素点的坐标值都是整数型的,不是所有的点都是在曲线上的,所以解出来的 t 值多少会有些误差,不过效果还是可以的,对整体的渐变影响不大。

// 根据 x 计算 t
- (float)baseOnXWithPoint:(CGPoint)point {
    float a = _startPoint.x - 2 * _controlPoint.x + _endPoint.x;
    float b = 2 * _controlPoint.x - 2 * _startPoint.x;
    float c = _startPoint.x - point.x;
    float condition = pow(b, 2) - 4 * a * c;
    if (a != 0 ) {
        if (condition >= 0) {
            NSArray *r = [self quadraticEquationWithA:a b:b c:c];
            if (r && r.count > 0) {
                float t = [self betterRWithRs:r targetPoint:point];
                return t;
            }
        }
    } else {
        // 一元一次方程求解
        float t = (-c)/b;
        return t;
    }
    return -1;
}

// 根据 y 计算 t
- (float)baseOnYWithPoint:(CGPoint)point {
    float a = _startPoint.y - 2 * _controlPoint.y + _endPoint.y;
    float b = 2 * _controlPoint.y - 2 * _startPoint.y;
    float c = _startPoint.y - point.y;
    float condition = pow(b, 2) - 4 * a * c;
    if ( a != 0) {
        if (condition >= 0) {
            NSArray *r = [self quadraticEquationWithA:a b:b c:c];
            if (r && r.count > 0) {
                float t = [self betterRWithRs:r targetPoint:point];
                return t;
            }
        }
    } else {
        // 一元一次方程求解
        float t = (-c)/b;
        return t;
    }
    
    return -1;
}

这里会有两个方程,一个是以x为参数,一个以y为参数。这两个方程都会用到。为什么要用两个方程?因为有的点通过x或者y 并不能解得结果,比如说顶点附近的点,通过点做 x 轴的 垂线,可能与曲线并不会交点,也就意味着不会有解。所以如果以x为参数无解,那就再用y为参数的方程解一次,如果还没有解,那这个点就认为是不在线上的了。

在计算的过程中还有一个问题:如果以x 为参数计算,那么 X 方向上顶点附近的点(如果有顶点)计算出来的t值误差会比较大。所以在计算的时候做了一些判断,如果是顶点附近的点,以y为参数计算

- (float)quadraticEquationWithPoint:(CGPoint)point  {
    float t = [self baseOnXWithPoint:point];
    // 如果没有结果 即 t = -1,则依据Y从新计算
    // 如果计算的结果为 X 方向上的顶点,由于顶点位置计算不准确,所以根据Y从新计算
    if (t == -1 || fabs([self tForXAtVertexPoint] - t) < 0.1) {
        float otherT = [self baseOnYWithPoint:point];
        if (otherT == -1) {
            return t;
        }
        t = otherT;
    }
    return t;
}

对于一元二次方程,是会有两个根的情况的,所以对于解出来的结果需要进行比对,找到与目标点最接近的t值

// 筛选结果
- (float)betterRWithRs:(NSArray *)rs targetPoint:(CGPoint)point{
    CGFloat distance = NSNotFound;
    NSInteger betterIndex = 0;
    for (NSInteger i = 0; i < rs.count; i ++) {
        float t = [[rs objectAtIndex:i] floatValue];
        CGFloat x = [self xAtT:t];
        CGFloat y = [self yAtT:t];
        if (distance == NSNotFound) {
            distance = [self distanceWithPoint:CGPointMake(x, y) point1:point];
            betterIndex = i;

        } else {
            if (distance > [self distanceWithPoint:CGPointMake(x, y) point1:point]) {
                distance = [self distanceWithPoint:CGPointMake(x, y) point1:point];
                betterIndex = i;
            }
        }
        
    }
    float t = [rs[betterIndex] floatValue];
    if (t >= 1) {
        if ([self isNearbyTargetPoint:_endPoint x:point.x y:point.y]) {
            return 1;
        } else {
            return -1;
        }
    }
    
    if (t <= 0) {
        if ([self isNearbyTargetPoint:_startPoint x:point.x y:point.y]) {
            return 0;
        } else {
            return -1;
        }
    }
    return [rs[betterIndex] floatValue];
}

可以先看下效果。整体来说效果还是理想的,并且也支持了线宽的问题。

渐变曲线

Demo 地址

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

推荐阅读更多精彩内容