英文:
UIBezierPath() and DragGesture: Connecting points creating corners instead of curves
问题
我正在使用UIBezierPath()和DragGesture在我的iOS应用程序中绘制画布上的线条。为了实现这一目标,我收集手势点并将其添加到路径中,该路径是UIBezierPath的一个对象。然后我设置特定的线宽并在图形上下文中绘制路径。
然而,在尝试快速绘制圆形时,我遇到了一个问题。与其获得平滑曲线,路径的连接点会形成棱角,导致角形而不是圆形的形状。这个问题特别在快速绘制圆形时出现,影响了我的整体绘画质量。
我正在寻求如何确保路径的连接点在快速绘制圆形时创建平滑曲线的指导。对于任何见解或建议,我将不胜感激。谢谢!
英文:
I'm using the UIBezierPath() and DragGesture in my iOS app to draw lines on a canvas. To achieve this, I collect the gesture points and add them to a path, which is an object of UIBezierPath. I then set a specific lineWidth and stroke the path in a graphics context.
However, I'm facing an issue when trying to draw a circle shape quickly. Instead of getting a smooth curve, the connecting points of the path create corners, resulting in an angular shape rather than a circular one. This issue specifically occurs when drawing circles rapidly, and it affects the overall quality of my drawings. For more visualisation look here:
func draw(size: CGSize, drawingImage: UIImage, lines: [Line], path: UIBezierPath) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 1.0)
drawingImage.draw(at: .zero)
let pathCount = lines.count
let pointCount = lines[pathCount-1].line.count
let point = lines[pathCount-1].line[pointCount-1]
path.addLine(to: point.toCGPoint())
let context = UIGraphicsGetCurrentContext()!
context.addPath(path.cgPath)
context.setStrokeColor(Color.white.cgColor)
path.lineWidth = 50
path.lineCapStyle = .round
path.lineJoinStyle = .round
path.stroke()
let myImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return myImage
}
I'm seeking guidance on how to ensure that the connecting points of the path create smooth curves even when drawing a circle shape quickly. Any insights or suggestions would be greatly appreciated. Thank you!
答案1
得分: 1
你需要将曲线添加到你的贝塞尔曲线中,而不是直线。为了让曲线看起来好看,你需要计算曲线上连续点之间的适当控制点。
我正在从 这个答案 中调整代码。请注意,这个示例接受一个 CGPoint 数组,而不是你提到的 Line 类。
执行它的方式如下:
let path = quadCurvedPath(points) // points 是 [CGPoint]
并像这样计算 UIBezierPath:
// 改编自 https://stackoverflow.com/a/40203583/3680644
func quadCurvedPath(_ points: [CGPoint]) -> UIBezierPath {
let path = UIBezierPath()
var p1 = points[0]
path.move(to: p1)
if (points.count == 2) {
path.addLine(to: points[1])
return path
}
var oldControlP: CGPoint?
for i in 1..<points.count {
let p2 = points[i]
var p3: CGPoint?
if i < points.count - 1 {
p3 = points[i+1]
}
let newControlP = controlPointForPoints(p1: p1, p2: p2, next: p3)
path.addCurve(to: p2, controlPoint1: oldControlP ?? p1, controlPoint2: newControlP ?? p2)
p1 = p2
oldControlP = antipodalFor(point: newControlP, center: p2)
}
return path;
}
位于中心点相对面的点:
func antipodalFor(point: CGPoint?, center: CGPoint?) -> CGPoint? {
guard let p1 = point, let center = center else {
return nil
}
let newX = 2 * center.x - p1.x
let diffY = abs(p1.y - center.y)
let newY = center.y + diffY * (p1.y < center.y ? 1 : -1)
return CGPoint(x: newX, y: newY)
}
两点的中点:
func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}
找到 addCurve 的控制点2:
func controlPointForPoints(p1: CGPoint, p2: CGPoint, next p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}
let leftMidPoint = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
var controlPoint = midPointForPoints(p1: leftMidPoint, p2: antipodalFor(point: rightMidPoint, center: p2)!)
if p1.y.between(a: p2.y, b: controlPoint.y) {
controlPoint.y = p1.y
} else if p2.y.between(a: p1.y, b: controlPoint.y) {
controlPoint.y = p2.y
}
let imaginContol = antipodalFor(point: controlPoint, center: p2)!
if p2.y.between(a: p3.y, b: imaginContol.y) {
controlPoint.y = p2.y
}
if p3.y.between(a: p2.y, b: imaginContol.y) {
let diffY = abs(p2.y - p3.y)
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
}
// 让线条更容易处理
controlPoint.x += (p2.x - p1.x) * 0.1
return controlPoint
}
CGFloat 扩展函数:
extension CGFloat {
func between(a: CGFloat, b: CGFloat) -> Bool {
return self >= Swift.min(a, b) && self <= Swift.max(a, b)
}
}
英文:
you will need to add curves to your bezier rather then lines. and in order for the curves to look good, you will need to calculate the appropriate control points between successive points on the curve.
i'm adapting code from this answer. note that this example accepts an array of CGPoints rather than the class you reference (Line).
execute it like this
let path = quadCurvedPath(points) //points is [CGPoint]
and calculate the UIBezierPath like this
//adapted from https://stackoverflow.com/a/40203583/3680644
func quadCurvedPath(_ points: [CGPoint]) -> UIBezierPath {
let path = UIBezierPath()
var p1 = points[0]
path.move(to: p1)
if (points.count == 2) {
path.addLine(to: points[1])
return path
}
var oldControlP: CGPoint?
for i in 1..<points.count {
let p2 = points[i]
var p3: CGPoint?
if i < points.count - 1 {
p3 = points[i+1]
}
let newControlP = controlPointForPoints(p1: p1, p2: p2, next: p3)
path.addCurve(to: p2, controlPoint1: oldControlP ?? p1, controlPoint2: newControlP ?? p2)
p1 = p2
oldControlP = antipodalFor(point: newControlP, center: p2)
}
return path;
}
/// located on the opposite side from the center point
func antipodalFor(point: CGPoint?, center: CGPoint?) -> CGPoint? {
guard let p1 = point, let center = center else {
return nil
}
let newX = 2 * center.x - p1.x
let diffY = abs(p1.y - center.y)
let newY = center.y + diffY * (p1.y < center.y ? 1 : -1)
return CGPoint(x: newX, y: newY)
}
/// halfway of two points
func midPointForPoints(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2);
}
/// Find controlPoint2 for addCurve
/// - Parameters:
/// - p1: first point of curve
/// - p2: second point of curve whose control point we are looking for
/// - next: predicted next point which will use antipodal control point for finded
func controlPointForPoints(p1: CGPoint, p2: CGPoint, next p3: CGPoint?) -> CGPoint? {
guard let p3 = p3 else {
return nil
}
let leftMidPoint = midPointForPoints(p1: p1, p2: p2)
let rightMidPoint = midPointForPoints(p1: p2, p2: p3)
var controlPoint = midPointForPoints(p1: leftMidPoint, p2: antipodalFor(point: rightMidPoint, center: p2)!)
if p1.y.between(a: p2.y, b: controlPoint.y) {
controlPoint.y = p1.y
} else if p2.y.between(a: p1.y, b: controlPoint.y) {
controlPoint.y = p2.y
}
let imaginContol = antipodalFor(point: controlPoint, center: p2)!
if p2.y.between(a: p3.y, b: imaginContol.y) {
controlPoint.y = p2.y
}
if p3.y.between(a: p2.y, b: imaginContol.y) {
let diffY = abs(p2.y - p3.y)
controlPoint.y = p2.y + diffY * (p3.y < p2.y ? 1 : -1)
}
// make lines easier
controlPoint.x += (p2.x - p1.x) * 0.1
return controlPoint
}
extension CGFloat {
func between(a: CGFloat, b: CGFloat) -> Bool {
return self >= Swift.min(a, b) && self <= Swift.max(a, b)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论