あなたが解決している特定の問題についてはわかりませんが、かわいい(そして非常に簡単な)解決策の1つは、ベジエ曲線のアウトラインアウトラインをレンダリングすることです。
これは、Core Graphics (この場合はサブクラスdrawRect
の a )を使用して簡単に実行できます。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)
}