Swift中的人工制品绘图 [英] Artefact drawing in Swift

查看:75
本文介绍了Swift中的人工制品绘图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面的代码通过覆盖触摸来绘制线条,但是在绘制时仍然存在伪像,如下图所示。



当在屏幕上进行锯齿形绘制时改变方向时,有时该线变为平直角而不是保持圆形。当在小圆圈上当场绘图时,也会出现伪影,当手指离开屏幕时,绘图点会闪烁半圈,有时会留下半圈和部分圆圈残留。



伪影是断断续续的,并不是完全一致或可预测的,这使得很难在代码中找到问题。它同时存在于模拟器和iOS7-iOS9的设备中。



一个zip文件,其中包含两个绘制点和线的视频屏幕截图以及Xcode项目会以名为Archive.zip(23MB)

  class SmoothCurvedLinesView:UIView {
var strokeColor = UIColor.blueColor()
var lineWidth:CGFloat = 20
var snapshotImage:UIImage?

私有变量路径:UIBezierPath?
private var临时路径:UIBezierPath?
私有变量点= [CGPoint]()
私有变量totalPointCount = 0

覆盖func drawRect(rect:CGRect){
snapshotImage?.drawInRect(rect)

strokeColor.setStroke()

路径?.stroke()
临时路径?.stroke()
}

覆盖func touchesBegan(touches:Set< UITouch>,withEvent事件:UIEvent?){
让touch:AnyObject? = touches.first
点= [touch!.locationInView(self)]
totalPointCount = totalPointCount +1
}

覆盖func touchesMoved(touches:Set< UITouch> ;,withEvent event:UIEvent?){
让我们触摸:AnyObject? = touches.first
让点= touch!.locationInView(self)

points.append(point)
totalPointCount = totalPointCount + 1

updatePaths ()

,如果totalPointCount> 50 {
ConstructIncrementalImage(includeTemporaryPath:false)
路径=零
totalPointCount = 0
}

setNeedsDisplay()
}

private func updatePaths(){
//更新主路径

而points.count> 4 {
points [3] = CGPointMake((points [2] .x + points [4] .x)/2.0,(points [2] .y + points [4] .y)/2.0)

如果path == nil {
path = createPathStartingAtPoint(points [0])
}

path?.addCurveToPoint(points [3],controlPoint1 :points [1],controlPoint2:points [2])

points.removeFirst(3)
}

//建立直到最后一个接触点的临时路径

让pointCount = points.count

如果pointCount == 2 {
临时路径= createPathStartingAtPoint(点[0])
临时路径?.addLineToPoint( points [1])$ ​​b $ b}否则,如果pointCount == 3 {
临时路径= createPathStartingAtPoint(点[0])
临时路径?.addQuadCurveToPoint(点[2],控制点:点[1] )
}否则,如果pointCount == 4 {
临时路径= createPathStartingAtPoint(points [0])
临时路径?。addCurveToPoint(points [3],controlPoint1:points [1],controlPoint2:points [2])
}
}

覆盖func touchesEnded(touches:Set< UITouch>,withEvent事件:UIEvent?){
ConstructIncrementalImage()
path = nil
setNeedsDisplay()
}

覆盖func touchesCancelled(touches:设置< UITouch>?,withEvent事件:UIEvent?){
touchesEnded(touches !, withEvent:事件)
}

私有函数createPathStartingAtPoint(point:CGPoint)-> UIBezierPath {
let localPath = UIBezierPath()

localPath.moveToPoint(point)

localPath.lineWidth = lineWidth
localPath.lineCapStyle = .Round
localPath.lineJoinStyle = .Round

return localPath
}

private func ConstructIncrementalImage(includeTemporaryPath includeTemporaryPath:Bool = true){
UIGraphicsBeginImageContextWithOptions( bounds.size,false,0.0)
strokeColor.setStroke()
snapshotImage?.drawAtPoint(CGPointZero)
path?.stroke()
if(includeTemporaryPath){临时路径? stroke()}
snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}


解决方案

这似乎是 addQuadCurveToPoint addCurveToPoint中一个令人着迷的错误其中,如果控制点(s)与两个端点位于同一行,因此不接受 lineJoinStyle 。因此,您可以对此进行测试(通过查看各个点的 atan2 并确保它们不相同),如果是的话,只需执行 addLineToPoint 代替:



我发现此修改后的代码删除了这些工件:

  private func updatePaths(){
//更新主路径

而points.count> 4 {
points [3] = CGPointMake((points [2] .x + points [4] .x)/2.0,(points [2] .y + points [4] .y)/2.0)

如果path == nil {
path = createPathStartingAtPoint(points [0])
}

addCubicCurveToPath(path)

points.removeFirst(3)
}

//建立直到最后一个接触点

的临时路径let pointCount = points.count

if pointCount == 2 {
临时路径= createPathStartingAtPoint(points [0])
临时路径?.addLineToPoint(points [1])$ ​​b $ b}否则,如果pointCount == 3 {
临时路径= createPathStartingAtPoint(点[0])
addQuadCurveToPath(temporaryPath)
}否则,如果pointCount == 4 {

}
}

///将三次曲线添加到路径
///
///由于贝塞尔曲线折叠了b的错误确认自己没有荣誉`lineJoinStyle`,
///检查是否发生这种情况,如果发生这种情况,只需添加线条而不是三次贝塞尔曲线。

私人函数addCubicCurveToPath(somePath:UIBezierPath?){
let m01 = atan2(points [0] .x-points [1] .x,points [0] .y-points [ 1] .y)
let m23 = atan2(points [2] .x-points [3] .x,points [2] .y-points [3] .y)
let m03 = atan2 (points [0] .x-points [3] .x,points [0] .y-points [3] .y)
如果m01 == m03 || m23 == m03 || points [0] == points [3] {
somePath?.addLineToPoint(points [1])$ ​​b $ b somePath?.addLineToPoint(points [2])
somePath?.addLineToPoint(points [ 3])
}否则{
somePath?.addCurveToPoint(points [3],controlPoint1:points [1],controlPoint2:points [2])
}
}

///将二次曲线添加到路径
///
///由于贝塞尔曲线向后折叠的错误,所以不兑现`lineJoinStyle`,
///检查是否发生这种情况,如果发生这种情况,只需添加线条而不是二次贝塞尔曲线即可。

私有函数addQuadCurveToPath(somePath:UIBezierPath?){
let m01 = atan2(points [0] .x-points [1] .x,points [0] .y-points [ 1] .y)
let m12 = atan2(points [1] .x-points [2] .x,points [1] .y-points [2] .y)
let m02 = atan2 (points [0] .x-points [2] .x,points [0] .y-points [2] .y)
如果m01 == m02 || m12 == m02 || points [0] == points [2] {
somePath?.addLineToPoint(points [1])$ ​​b $ b somePath?.addLineToPoint(points [2])
}其他{
somePath?.addQuadCurveToPoint(points [2],controlPoint:points [1])$ ​​b $ b}
}

此外,这可能过分谨慎,但是使用 guard 语句确保两个连续的点永远不会相同是很谨慎的:

 重写功能touchesMoved(touches:Set< UITouch>,withEvent事件:UIEvent?){
让我们触摸:AnyObject? = touches.first
让点= touch!.locationInView(self)

保护点!= points.last else {return}

points.append(point )
totalPointCount = totalPointCount + 1

updatePaths()

如果totalPointCount> 50 {
ConstructIncrementalImage(includeTemporaryPath:false)
路径=零
totalPointCount = 0
}

setNeedsDisplay()
}






如果发现其他有问题的情况,可以重复我刚刚进行的调试练习。即,运行代码直到出现问题,然后立即停止并查看 points 数组的日志以查看导致问题的原因,然后创建 init?(coder:)始终100%地重现该问题,例如:

 是否需要初始化?(编码器aDecoder:NSCoder){
super.init(编码器:aDecoder)

points.append(CGPoint(x:239.33332824707,y:419.0))
points.append(CGPoint(x:239.33332824707,y:420.0))
points.append(CGPoint(x:239.33332824707,y:419.3))

updatePaths()
}

然后,有了一个可重复生成的问题,调试很容易。因此,在诊断出问题之后,我随后修改了 updatePaths ,直到问题解决为止。然后我注释掉 init?并重复整个练习。


The code below draws lines by overriding touches, however there is an artefact that persists when drawing, seen in the images below.

When changing direction while zig zagging drawing across the screen, sometimes the line turns into a flat straight corner instead of remaining circular. The artefact is also experienced when drawing on the spot in small circles, the drawing point flashes half circles sometimes leaving half circles and partial circle residue when the finger leave the screen.

The artefacts are intermittent and not in an entirely consistent or predictable pattern making it difficult to find the issue in the code. It is present both in the simulator and on device in iOS7 - iOS9.

A zip containing two video screen captures of drawing dots and lines along with the Xcode project are uploaded to DropBox in a file called Archive.zip (23MB) https://www.dropbox.com/s/hm39rdiuk0mf578/Archive.zip?dl=0

Questions:

1 - In code, what is causing this dot/half circle artefact and how can it be corrected?

class SmoothCurvedLinesView: UIView {
    var strokeColor = UIColor.blueColor()
    var lineWidth: CGFloat = 20
    var snapshotImage: UIImage?

    private var path: UIBezierPath?
    private var temporaryPath: UIBezierPath?
    private var points = [CGPoint]()
    private var totalPointCount = 0

    override func drawRect(rect: CGRect) {
        snapshotImage?.drawInRect(rect)

        strokeColor.setStroke()

        path?.stroke()
        temporaryPath?.stroke()
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        points = [touch!.locationInView(self)]
        totalPointCount = totalPointCount + 1
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let touch: AnyObject? = touches.first
        let point = touch!.locationInView(self)

        points.append(point)
        totalPointCount = totalPointCount + 1

        updatePaths()

        if totalPointCount > 50 {
            constructIncrementalImage(includeTemporaryPath: false)
            path = nil
            totalPointCount = 0
        }

        setNeedsDisplay()
    }

    private func updatePaths() {
        // update main path

        while points.count > 4 {
            points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0)

            if path == nil {
                path = createPathStartingAtPoint(points[0])
            }

            path?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])

            points.removeFirst(3)
        }

        // build temporary path up to last touch point

        let pointCount = points.count

        if pointCount == 2 {
            temporaryPath = createPathStartingAtPoint(points[0])
            temporaryPath?.addLineToPoint(points[1])
        } else if pointCount == 3 {
            temporaryPath = createPathStartingAtPoint(points[0])
            temporaryPath?.addQuadCurveToPoint(points[2], controlPoint: points[1])
        } else if pointCount == 4 {
            temporaryPath = createPathStartingAtPoint(points[0])
            temporaryPath?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
        }
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        constructIncrementalImage()
        path = nil
        setNeedsDisplay()
    }

    override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
        touchesEnded(touches!, withEvent: event)
    }

    private func createPathStartingAtPoint(point: CGPoint) -> UIBezierPath {
        let localPath = UIBezierPath()

        localPath.moveToPoint(point)

        localPath.lineWidth = lineWidth
        localPath.lineCapStyle = .Round
        localPath.lineJoinStyle = .Round

        return localPath
    }

    private func constructIncrementalImage(includeTemporaryPath includeTemporaryPath: Bool = true) {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
        strokeColor.setStroke()
        snapshotImage?.drawAtPoint(CGPointZero)
        path?.stroke()
        if (includeTemporaryPath) { temporaryPath?.stroke() }
        snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    }
}

解决方案

This would appear to be a fascinating bug in addQuadCurveToPoint and addCurveToPoint where, if the control point(s) are on the same line as the two end points, it doesn't honor the lineJoinStyle. So you can test for this (by looking at the atan2 of the various points and make sure there are not the same), and if so, just do addLineToPoint instead:

I found that this revised code removed those artifacts:

private func updatePaths() {
    // update main path

    while points.count > 4 {
        points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0)

        if path == nil {
            path = createPathStartingAtPoint(points[0])
        }

        addCubicCurveToPath(path)

        points.removeFirst(3)
    }

    // build temporary path up to last touch point

    let pointCount = points.count

    if pointCount == 2 {
        temporaryPath = createPathStartingAtPoint(points[0])
        temporaryPath?.addLineToPoint(points[1])
    } else if pointCount == 3 {
        temporaryPath = createPathStartingAtPoint(points[0])
        addQuadCurveToPath(temporaryPath)
    } else if pointCount == 4 {
        temporaryPath = createPathStartingAtPoint(points[0])
        addCubicCurveToPath(temporaryPath)
    }
}

/// Add cubic curve to path
///
/// Because of bug with bezier curves that fold back on themselves do no honor `lineJoinStyle`,
/// check to see if this occurs, and if so, just add lines rather than cubic bezier path.

private func addCubicCurveToPath(somePath: UIBezierPath?) {
    let m01 = atan2(points[0].x - points[1].x, points[0].y - points[1].y)
    let m23 = atan2(points[2].x - points[3].x, points[2].y - points[3].y)
    let m03 = atan2(points[0].x - points[3].x, points[0].y - points[3].y)
    if m01 == m03 || m23 == m03 || points[0] == points[3] {
        somePath?.addLineToPoint(points[1])
        somePath?.addLineToPoint(points[2])
        somePath?.addLineToPoint(points[3])
    } else {
        somePath?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
    }
}

/// Add quadratic curve to path
///
/// Because of bug with bezier curves that fold back on themselves do no honor `lineJoinStyle`,
/// check to see if this occurs, and if so, just add lines rather than quadratic bezier path.

private func addQuadCurveToPath(somePath: UIBezierPath?) {
    let m01 = atan2(points[0].x - points[1].x, points[0].y - points[1].y)
    let m12 = atan2(points[1].x - points[2].x, points[1].y - points[2].y)
    let m02 = atan2(points[0].x - points[2].x, points[0].y - points[2].y)
    if m01 == m02 || m12 == m02 || points[0] == points[2] {
        somePath?.addLineToPoint(points[1])
        somePath?.addLineToPoint(points[2])
    } else {
        somePath?.addQuadCurveToPoint(points[2], controlPoint: points[1])
    }
}

Also, this may be overly cautious, but it might be prudent to ensure that two successive points are never the same with a guard statements:

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch: AnyObject? = touches.first
    let point = touch!.locationInView(self)

    guard point != points.last else { return }

    points.append(point)
    totalPointCount = totalPointCount + 1

    updatePaths()

    if totalPointCount > 50 {
        constructIncrementalImage(includeTemporaryPath: false)
        path = nil
        totalPointCount = 0
    }

    setNeedsDisplay()
}


If you find other situations where there are problems, you can repeat the debugging exercise that I just did. Namely, run the code until a problem manifested itself, but stop immediately and look at the log of points array to see what points caused a problem, and then create a init?(coder:) that consistently reproduced the problem 100% of the time, e.g.:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    points.append(CGPoint(x: 239.33332824707, y: 419.0))
    points.append(CGPoint(x: 239.33332824707, y: 420.0))
    points.append(CGPoint(x: 239.33332824707, y: 419.3))

    updatePaths()
}

Then, with a consistently reproducible problem, the debugging was easy. So having diagnosed the problem, I then revised updatePaths until the problem was resolved. I then commented out init? and repeated the whole exercise.

这篇关于Swift中的人工制品绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆