Drawing Smooth Curves - Methods Needed

前端 未结 12 2255
长情又很酷
长情又很酷 2020-11-28 17:13

How do you smooth a set of points in an iOS drawing app WHILE MOVING? I have tried UIBezierpaths but all I get are jagged ends where they intersect, when I just shift the po

12条回答
  •  鱼传尺愫
    2020-11-28 18:02

    Some good answers here, though I think they are either way off (user1244109's answer only supports horizontal tangents, not useful for generic curves), or overly complicated (sorry Catmull-Rom fans).

    I implemented this in a much simpler way, using Quad bezier curves. These need a start point, an end point, and a control point. The natural thing to do might be to use the touch points as the start & end points. Don't do this! There are no appropriate control points to use. Instead, try this idea: use the touch points as control points, and the midpoints as the start/end points. You're guaranteed to have proper tangents this way, and the code is stupid simple. Here's the algorithm:

    1. The "touch down" point is the start of the path, and store location in prevPoint.
    2. For every dragged location, calculate midPoint, the point between currentPoint and prevPoint.
      1. If this is the first dragged location, add currentPoint as a line segment.
      2. For all points in the future, add a quad curve that terminates at the midPoint, and use the prevPoint as the control point. This will create a segment that gently curves from the previous point to the current point.
    3. Store currentPoint in prevPoint, and repeat #2 until dragging ends.
    4. Add the final point as another straight segment, to finish up the path.

    This results in very good looking curves, because using the midPoints guarantees that the curve is a smooth tangent at the end points (see attached photo).

    Swift code looks like this:

    var bezierPath = UIBezierPath()
    var prevPoint: CGPoint?
    var isFirst = true
    
    override func touchesBegan(touchesSet: Set, withEvent event: UIEvent?) {
        let location = touchesSet.first!.locationInView(self)
        bezierPath.removeAllPoints()
        bezierPath.moveToPoint(location)
        prevPoint = location
    }
    
    override func touchesMoved(touchesSet: Set, withEvent event: UIEvent?) {
        let location = touchesSet.first!.locationInView(self)
    
        if let prevPoint = prevPoint {
            let midPoint = CGPoint(
                x: (location.x + prevPoint.x) / 2,
                y: (location.y + prevPoint.y) / 2,
            )
            if isFirst {
                bezierPath.addLineToPoint(midPoint)
            else {
                bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
            }
            isFirst = false
        }
        prevPoint = location
    }
    
    override func touchesEnded(touchesSet: Set, withEvent event: UIEvent?) {
        let location = touchesSet.first!.locationInView(self)
        bezierPath.addLineToPoint(location)
    }
    

    Or, if you have an array of points and want to construct the UIBezierPath in one shot:

    var points: [CGPoint] = [...]
    var bezierPath = UIBezierPath()
    var prevPoint: CGPoint?
    var isFirst = true
    
    // obv, there are lots of ways of doing this. let's
    // please refrain from yak shaving in the comments
    for point in points {
        if let prevPoint = prevPoint {
            let midPoint = CGPoint(
                x: (point.x + prevPoint.x) / 2,
                y: (point.y + prevPoint.y) / 2,
            )
            if isFirst {
                bezierPath.addLineToPoint(midPoint)
            }
            else {
                bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
            }
            isFirst = false
        }
        else { 
            bezierPath.moveToPoint(point)
        }
        prevPoint = point
    }
    if let prevPoint = prevPoint {
        bezierPath.addLineToPoint(prevPoint)
    }
    

    Here are my notes:

提交回复
热议问题