iOS 沿曲線線性漸變的貝塞爾曲線

更新:換一種思路實現(xiàn)。iOS 沿曲線線性漸變的貝塞爾曲線(改進(jìn)版)

iOS原生的漸變只支持線性的漸變,但有的時候我們需要沿曲線進(jìn)行漸變。
先看下垂直線性漸變與沿曲線線性漸變的區(qū)別


垂直線性漸變:顏色最亮的地方在曲線的最低點

沿曲線線性漸變:顏色最亮的地方在起點

那么先來分析一下這個問題:

  1. 怎樣繪制曲線?
    對于貝塞爾曲線的繪制,系統(tǒng)提供了一系列的方法,同時我們已可以通過公式計算出一條貝塞爾曲線。
  2. 如何保證顏色漸變?
    找到曲線上的點,計算出每一個點的色值。

只要解決上面的問題就可以畫出一條沿曲線線性漸變的貝塞爾曲線,曲線畫起來還是比較簡單的,但是這樣計算出每一點的色值是一件比較麻煩的事情。

1、貝塞爾曲線

這里先介紹一下貝塞爾曲線的一些東西,以二次貝塞爾曲線為例,先來動態(tài)感受一下繪制過程

二次貝塞爾曲線

一條二次貝塞爾曲線需要三個點,A:起點 ;B:控制點;C:終點


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

第一次取點

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


第二次取點

F點就是貝塞爾曲線上的一個點,以此類推,取點一系列的點之后在ABC之間就產(chǎn)生了一條貝塞爾曲線

貝塞爾曲線

可以看出貝塞爾曲線上的每個點是有規(guī)律的,二次貝塞爾曲線的方程為
P0:起點;P1:控制點; P2:終點 ;t:百分比

二次貝塞爾曲線的方程

用OC表達(dá)的話就是這樣的

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、漸變色

既然已經(jīng)一顆貝塞爾曲線的方程,那就可以操作曲線上的每一個點了,那怎么設(shè)置每一個點的顏色的。

1、取點

對于取點還有一個問題需要注意,由于貝塞爾曲線并不是勻速變化的,所有如果均勻分割 t 來進(jìn)行取點的話,取出來的點是不均勻的。不均勻的點會造成有的地方缺失點,形成空白。所以需要對 t 進(jìn)行修正,取出間隔均勻的點。

均勻間隔的 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;
}
矯正間隔的取點

加上顏色,就是這樣了


均勻的漸變色點

接下來就是要去足夠多的點來連成曲線了,這個取點的個數(shù)要根據(jù)具體情況來定,
考慮到線的邊界問題,處理起來太費(fèi)事了,要進(jìn)行更多的色值計算


曲線的邊界

我的做法是先按線段長度取點,然后再根據(jù)每個點的速度及方向進(jìn)行上下左右偏移,得到一條寬度足夠的線之后在進(jìn)行mask裁剪。

偏移得到足夠?qū)挼木€

最終效果

這個取點的方案還不是很完善,先放個demo吧至少效果是出來了,可以看一下具體的樣子。


下面的文章給了我很多的幫助,有需要的同學(xué)可以去看一下
勻速貝塞爾曲線運(yùn)動(續(xù))
一分鐘就懂貝塞爾曲線
Bezier - 勻速貝塞爾曲線運(yùn)動的實現(xiàn)
動態(tài)繪制貝塞爾曲線的在線演示

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容