我不知道您要解决的具体问题,但一个可爱(而且非常简单)的解决方案是只渲染贝塞尔曲线的轮廓,例如:
这很容易使用 Core Graphics 完成(在这种情况下,是drawRect
一个UIView
子类):
- (void)drawRect:(CGRect)rect {
CGPathRef path = [self newBezierPath];
CGPathRef outlinePath = CGPathCreateCopyByStrokingPath(path, NULL, 10, kCGLineCapButt, kCGLineJoinBevel, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 3.0);
CGContextAddPath(context, outlinePath);
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextDrawPath(context, kCGPathStroke);
CGPathRelease(path);
CGPathRelease(outlinePath);
}
- (CGPathRef)newBezierPath {
CGPoint point1 = CGPointMake(10.0, 50.0);
CGPoint point2 = CGPointMake(self.bounds.size.width - 10.0, point1.y + 150.0);
CGPoint controlPoint1 = CGPointMake(point1.x + 400.0, point1.y);
CGPoint controlPoint2 = CGPointMake(point2.x - 400.0, point2.y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, point1.x, point1.y);
CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, point2.x, point2.y);
return path;
}
或者在 Swift 3 中:
override func draw(_ rect: CGRect) {
let path = bezierPath().cgPath
let outlinePath = path.copy(strokingWithWidth: 10, lineCap: .butt, lineJoin: .bevel, miterLimit: 0)
let context = UIGraphicsGetCurrentContext()!
context.setLineWidth(3)
context.addPath(outlinePath)
context.setStrokeColor(UIColor.red.cgColor)
context.strokePath()
}
private func bezierPath() -> UIBezierPath {
let point1 = CGPoint(x: 10.0, y: 50.0)
let point2 = CGPoint(x: bounds.size.width - 10.0, y: point1.y + 150.0)
let controlPoint1 = CGPoint(x: point1.x + 400.0, y: point1.y)
let controlPoint2 = CGPoint(x: point2.x - 400.0, y: point2.y)
let path = UIBezierPath()
path.move(to: point1)
path.addCurve(to: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return path
}
如果你真的想画一条平行的路径,那就更复杂了。但是你可以渲染这样的东西(红色的原始贝塞尔路径,蓝色的“平行”线)。
我不完全确定您确定的算法,但我通过
- 自己计算贝塞尔点(红色路径),将其分解得足够精细以使效果平滑;
- 计算每个点与下一个点之间的角度;
- 通过获取贝塞尔路径上的点并计算与我刚刚确定的角度垂直的新点来计算偏移路径的坐标(蓝色);和
- 使用这些偏移点坐标,绘制一系列新的线段以将平行线渲染到贝塞尔曲线。
因此,在 Objective-C 中,这可能看起来像:
- (void)drawRect:(CGRect)rect {
CGPoint point1 = CGPointMake(10.0, 50.0);
CGPoint point2 = CGPointMake(self.bounds.size.width - 10.0, point1.y + 150.0);
CGPoint controlPoint1 = CGPointMake(point1.x + 400.0, point1.y);
CGPoint controlPoint2 = CGPointMake(point2.x - 400.0, point2.y);
// draw original bezier path in red
[[UIColor redColor] setStroke];
[[self bezierPathFromPoint1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2] stroke];
// calculate and draw offset bezier curve in blue
[[UIColor blueColor] setStroke];
[[self offsetBezierPathBy:10.0
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2] stroke];
}
- (UIBezierPath *)bezierPathFromPoint1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point1];
[path addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2];
return path;
}
- (UIBezierPath *)offsetBezierPathBy:(CGFloat)offset
point1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
UIBezierPath *path = [UIBezierPath bezierPath];
static NSInteger numberOfPoints = 100;
CGPoint previousPoint = [self cubicBezierAtTime:0.0
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2];
CGPoint point;
double angle;
for (NSInteger i = 1; i <= numberOfPoints; i++) {
double t = (double) i / numberOfPoints;
point = [self cubicBezierAtTime:t
point1:point1
point2:point2
controlPoint1:controlPoint1
controlPoint2:controlPoint2];
// calculate the angle to the offset point
// this is the angle between the two points, plus 90 degrees (pi / 2.0)
angle = atan2(point.y - previousPoint.y, point.x - previousPoint.x) + M_PI_2;
if (i == 1)
[path moveToPoint:[self offsetPoint:previousPoint by:offset angle:angle]];
previousPoint = point;
[path addLineToPoint:[self offsetPoint:previousPoint by:offset angle:angle]];
}
return path;
}
// return point offset by particular distance and particular angle
- (CGPoint)offsetPoint:(CGPoint)point by:(CGFloat)offset angle:(double)angle {
return CGPointMake(point.x + cos(angle) * offset, point.y + sin(angle) * offset);
}
// Manually calculate cubic bezier curve
//
// B(t) = (1-t)^3 * point1 + 3 * (1-t)^2 * t controlPoint1 + 3 * (1-t) * t^2 * pointPoint2 + t^3 * point2
- (CGPoint)cubicBezierAtTime:(double)t
point1:(CGPoint)point1
point2:(CGPoint)point2
controlPoint1:(CGPoint)controlPoint1
controlPoint2:(CGPoint)controlPoint2 {
double oneMinusT = 1.0 - t;
double oneMinusTSquared = oneMinusT * oneMinusT;
double oneMinusTCubed = oneMinusTSquared * oneMinusT;
double tSquared = t * t;
double tCubed = tSquared * t;
CGFloat x = point1.x * oneMinusTCubed +
3.0 * oneMinusTSquared * t * controlPoint1.x +
3.0 * oneMinusT * tSquared * controlPoint2.x +
tCubed * point2.x;
CGFloat y = point1.y * oneMinusTCubed +
3.0 * oneMinusTSquared * t * controlPoint1.y +
3.0 * oneMinusT * tSquared * controlPoint2.y +
tCubed * point2.y;
return CGPointMake(x, y);
}
或者,在 Swift 3 中:
override func draw(_ rect: CGRect) {
let point1 = CGPoint(x: 10.0, y: 50.0)
let point2 = CGPoint(x: bounds.size.width - 10.0, y: point1.y + 150.0)
let controlPoint1 = CGPoint(x: point1.x + 400.0, y: point1.y)
let controlPoint2 = CGPoint(x: point2.x - 400.0, y: point2.y)
UIColor.red.setStroke()
bezierPath(from: point1, to: point2, withControl: controlPoint1, and: controlPoint2).stroke()
UIColor.blue.setStroke()
offSetBezierPath(by: 5, from: point1, to: point2, withControl: controlPoint1, and: controlPoint2).stroke()
}
private func bezierPath(from point1: CGPoint, to point2: CGPoint, withControl controlPoint1: CGPoint, and controlPoint2:CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: point1)
path.addCurve(to: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return path
}
private func offSetBezierPath(by offset: CGFloat, from point1: CGPoint, to point2: CGPoint, withControl controlPoint1: CGPoint, and controlPoint2:CGPoint) -> UIBezierPath {
let path = UIBezierPath()
let numberOfPoints = 100
var previousPoint = cubicBezier(at: 0, point1: point1, point2: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
for i in 1 ... numberOfPoints {
let time = CGFloat(i) / CGFloat(numberOfPoints)
let point = cubicBezier(at: time, point1: point1, point2: point2, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
// calculate the angle to the offset point
// this is the angle between the two points, plus 90 degrees (pi / 2.0)
let angle = atan2(point.y - previousPoint.y, point.x - previousPoint.x) + .pi / 2;
if i == 1 {
path.move(to: calculateOffset(of: previousPoint, by: offset, angle: angle))
}
previousPoint = point
path.addLine(to: calculateOffset(of: previousPoint, by: offset, angle: angle))
}
return path
}
/// Return point offset by particular distance and particular angle
///
/// - Parameters:
/// - point: Point to offset.
/// - offset: How much to offset by.
/// - angle: At what angle.
///
/// - Returns: New `CGPoint`.
private func calculateOffset(of point: CGPoint, by offset: CGFloat, angle: CGFloat) -> CGPoint {
return CGPoint(x: point.x + cos(angle) * offset, y: point.y + sin(angle) * offset)
}
/// Manually calculate cubic bezier curve
///
/// B(t) = (1-t)^3 * point1 + 3 * (1-t)^2 * t controlPoint1 + 3 * (1-t) * t^2 * pointPoint2 + t^3 * point2
///
/// - Parameters:
/// - time: Time, a value between zero and one.
/// - point1: Starting point.
/// - point2: Ending point.
/// - controlPoint1: First control point.
/// - controlPoint2: Second control point.
///
/// - Returns: Point on bezier curve.
private func cubicBezier(at time: CGFloat, point1: CGPoint, point2: CGPoint, controlPoint1: CGPoint, controlPoint2: CGPoint) -> CGPoint {
let oneMinusT = 1.0 - time
let oneMinusTSquared = oneMinusT * oneMinusT
let oneMinusTCubed = oneMinusTSquared * oneMinusT
let tSquared = time * time
let tCubed = tSquared * time
var x = point1.x * oneMinusTCubed
x += 3.0 * oneMinusTSquared * time * controlPoint1.x
x += 3.0 * oneMinusT * tSquared * controlPoint2.x
x += tCubed * point2.x
var y = point1.y * oneMinusTCubed
y += 3.0 * oneMinusTSquared * time * controlPoint1.y
y += 3.0 * oneMinusT * tSquared * controlPoint2.y
y += tCubed * point2.y
return CGPoint(x: x, y: y)
}