大體思路是,先獲取到貝塞爾曲線上全部的點,而後在計算每一個點的t值,而後根據t值來計算每一個點的顏色。這種方式會在頂點的位置計算會有一些問題,總體來講只是一種思路,具體效果有待考驗。git
如何獲取貝塞爾曲線上全部的點?這個實際上是比較簡單的,能夠利用UIBezierPath
畫一條曲線,渲染到CAShapeLayer (fillColor:clearColor,strokeColor:redColor)
上,而後遍歷CAShapeLayer
上的像素,只要像素的有色值那就是須要的點。同時因爲這樣渲染出的線條已經處理好了鋸齒問題(即像素透明度), 因此爲後面的處理省下了不少的事情。github
如今已經獲得了須要的點,剩下的就是計算每一個點的t值了。計算t值也就是一個解方程的過程,這裏說的是二次貝塞爾曲線,涉及到的就是一元二次方程。可是在像素點的座標值都是整數型的,不是全部的點都是在曲線上的,因此解出來的 t 值多少會有些偏差,不過效果仍是能夠的,對總體的漸變影響不大。spa
// 根據 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爲參數的方程解一次,若是尚未解,那這個點就認爲是不在線上的了。code
在計算的過程當中還有一個問題:若是以x 爲參數計算,那麼 X 方向上頂點附近的點(若是有頂點)計算出來的t值偏差會比較大。因此在計算的時候作了一些判斷,若是是頂點附近的點,以y爲參數計算cdn
- (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值blog
// 篩選結果
- (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];
}
複製代碼
能夠先看下效果。總體來講效果仍是理想的,而且也支持了線寬的問題。get
Demo 地址it