Swift. UIBezierPath形状检测

huangapple go评论49阅读模式
英文:

Swift. UIBezierPath shape detection

问题

I working with UIBezierPath and shape detection. For painting im using "UIPanGestureRecognizer".

Example of code:

My shape definder

var gesture = UIPanGestureRecognizer()
.
.
.
view.addGestureRecognizer(gesture.onChange { \[weak self\] gesture in
let point = gesture.location(in: self.view)
let shapeL = CAShapeLayer()
shapeL.strokeColor = UIColor.black.cgColor
shapeL.lineWidth = 2
shapeL.fillColor = UIColor.clear.cgColor

        switch gesture.state {
        case .began:
            //some code
        currentBezierPath = UIBezierPath()
            break
        case .changed:
            //some code
        shapeLayer.path = self.currentBezierPath.cgPath
            break
        case .ended:
            //define what user was painted(circle, rectangle, etc)
        shapeDefinder(path: currentBezierPath)
            break
        default:
            break
        })

shapeDefinder

func shapeDefinder(path: UIBezierPath) {
if(path.hasFourRightAngles()){
// square 
}

}

extension hasFourRightAngles

extension UIBezierPath {
    func hasFourRightAngles() -> Bool {
        guard self.currentPoint != .zero else {
            // empty path cannot have angles
            return false
        }
    
        let bounds = self.bounds
        let points = [
            bounds.origin,
            CGPoint(x: bounds.minX, y: bounds.minY),
            CGPoint(x: bounds.maxX, y: bounds.maxY),
            CGPoint(x: bounds.minX, y: bounds.maxY)
        ]
        let angleTolerance = 5.0 // in degrees
        var rightAngleCount = 0
        for i in 0...3 {
            let p1 = points[i]
            let p2 = points[(i+1)%4]
            let p3 = points[(i+2)%4]
            let angle = p2.angle(between: p1, and: p3)
            if abs(angle - 90) <= angleTolerance {
                rightAngleCount += 1
            }
        }
        return rightAngleCount >= 4
    }
}

and

extension CGPoint {
func angle(between p1: CGPoint, and p2: CGPoint) -> CGFloat {
let dx1 = self.x - p1.x
let dy1 = self.y - p1.y
let dx2 = p2.x - self.x
let dy2 = p2.y - self.y
let dotProduct = dx1*dx2 + dy1*dy2
let crossProduct = dx1*dy2 - dx2*dy1
return atan2(crossProduct, dotProduct) \* 180 / .pi
}
}

but my method hasFourRightAngles() doesnt work, it always has true.
Cant understand how i can detect square(the user must draw exactly a square, if the user draws a circle, then the check should not pass.)
Maybe someone know about some library which works with UIBezierPath for detect shapes?

英文:

I working with UIBezierPath and shape detection. For painting im using "UIPanGestureRecognizer".

Example of code:

My shape definder

var gesture = UIPanGestureRecognizer()
.
.
.
view.addGestureRecognizer(gesture.onChange { \[weak self\] gesture in
let point = gesture.location(in: self.view)
let shapeL = CAShapeLayer()
shapeL.strokeColor = UIColor.black.cgColor
shapeL.lineWidth = 2
shapeL.fillColor = UIColor.clear.cgColor

        switch gesture.state {
        case .began:
            //some code
        currentBezierPath = UIBezierPath()
            break
        case .changed:
            //some code
        shapeLayer.path = self.currentBezierPath.cgPath
            break
        case .ended:
            //define what user was painted(circle, rectangle, etc)
        shapeDefinder(path: currentBezierPath)
            break
        default:
            break
        })

shapeDefinder

func shapeDefinder(path: UIBezierPath) {
if(path.hasFourRightAngles()){
// square 
}

}

extension hasFourRightAngles

extension UIBezierPath {
    func hasFourRightAngles() -> Bool {
        guard self.currentPoint != .zero else {
            // empty path cannot have angles
            return false
        }
    
        let bounds = self.bounds
        let points = [
            bounds.origin,
            CGPoint(x: bounds.minX, y: bounds.minY),
            CGPoint(x: bounds.maxX, y: bounds.maxY),
            CGPoint(x: bounds.minX, y: bounds.maxY)
        ]
        let angleTolerance = 5.0 // in degrees
        var rightAngleCount = 0
        for i in 0...3 {
            let p1 = points[i]
            let p2 = points[(i+1)%4]
            let p3 = points[(i+2)%4]
            let angle = p2.angle(between: p1, and: p3)
            if abs(angle - 90) <= angleTolerance {
                rightAngleCount += 1
            }
        }
        return rightAngleCount >= 4
    }
}

and

extension CGPoint {
func angle(between p1: CGPoint, and p2: CGPoint) -\> CGFloat {
let dx1 = self.x - p1.x
let dy1 = self.y - p1.y
let dx2 = p2.x - self.x
let dy2 = p2.y - self.y
let dotProduct = dx1*dx2 + dy1*dy2
let crossProduct = dx1*dy2 - dx2*dy1
return atan2(crossProduct, dotProduct) \* 180 / .pi
}
}

but my method hasFourRightAngles() doesnt work, it always has true.
Cant understand how i can detect square(the user must draw exactly a square, if the user draws a circle, then the check should not pass.)
Maybe someone know about some library which works with UIBezierPath for detect shapes?

答案1

得分: 2

以下是翻译好的部分:

"The bounds of a path are always a rectangle, no matter the shape, so you should expect this function to always return true. From the docs:

这个路径的边界始终是一个矩形,无论形状如何,因此您应该期望此函数始终返回true。来自文档

The value in this property represents the smallest rectangle that completely encloses all points in the path, including any control points for Bézier and quadratic curves.
此属性中的值表示完全包围路径中的所有点的最小矩形,包括 Bézier 和二次曲线的任何控制点。

If you want to consider the components of the path itself, you'd need to iterate over its components using it's CGPath. See applyWithBlock for how to get the elements. That said, this probably won't work very well, since you likely don't care precisely how the shape was drawn. If you go down this road, you'll probably want to do some work to simplify the curve first, and perhaps put the stokes in a useful order.

如果您想考虑路径本身的组件,您需要使用它的 CGPath 迭代其组件。参见applyWithBlock以了解如何获取元素。话虽如此,这可能不会工作得很好,因为您可能不太关心形状是如何精确绘制的。如果您选择这条路,您可能需要先简化曲线,然后按照有用的顺序放置笔画。

If the drawing pattern itself is the important thing (i.e. the user's gesture is what matters), then I would probably keep track of whether this could be a rectangle at each point of the drawing. Either it needs to be roughly colinear to the previous line, or roughly normal. And then the final point must be close to the original point.

如果绘图模式本身很重要(即用户的手势很重要),那么我可能会在绘图的每个点上跟踪这是否可能是一个矩形。它需要与前一行大致共线,或者大致正交。然后最终点必须靠近原始点。

The better approach is possibly to consider the final image of the shape, regardless of how it was drawn, and then classify it. For various algorithms to do that, see https://stackoverflow.com/questions/9658531/how-to-identify-different-objects-in-an-image

更好的方法可能是考虑形状的最终图像,而不管它是如何绘制的,然后对其进行分类。有关执行此操作的各种算法,请参见https://stackoverflow.com/questions/9658531/how-to-identify-different-objects-in-an-image"

英文:

The bounds of a path are always a rectangle, no matter the shape, so you should expect this function to always return true. From the docs:

>The value in this property represents the smallest rectangle that completely encloses all points in the path, including any control points for Bézier and quadratic curves.

If you want to consider the components of the path itself, you'd need to iterate over its components using it's CGPath. See applyWithBlock for how to get the elements. That said, this probably won't work very well, since you likely don't care precisely how the shape was drawn. If you go down this road, you'll probably want to do some work to simplify the curve first, and perhaps put the stokes in a useful order.

If the drawing pattern itself is the important thing (i.e. the user's gesture is what matters), then I would probably keep track of whether this could be a rectangle at each point of the drawing. Either it needs to be roughly colinear to the previous line, or roughly normal. And then the final point must be close to the original point.

The better approach is possibly to consider the final image of the shape, regardless of how it was drawn, and then classify it. For various algorithms to do that, see https://stackoverflow.com/questions/9658531/how-to-identify-different-objects-in-an-image

答案2

得分: 1

你的用于获取路径边界的代码将无法确定路径内部的线是否形成直角。正如Rob在他的回答中所说,路径的边界框总是一个矩形,因此你当前的测试将始终返回true。

圆的边界框将是一个正方形,任何水平和垂直极值相等的形状的边界框也将是正方形。

你可以查询底层CGPath的内部元素,查找形成正方形的一系列线段的代码。我建议搜索解析CGPath元素的代码。

请注意,如果你要检查手绘图形,可能需要在计算中引入一些"松散"来允许形状接近但不完全是正方形,否则你可能永远找不到完美的正方形。

另外,如果路径包含一个正方形和其他形状元素,你需要决定如何处理这种情况。

英文:

Your code to get the bounds of your path will not let you tell if the lines inside the path make right angles. As Rob says in his answer, the bounding box of a path will always be a rectangle, so your current test will always return true.

The bounding box of a circle will be a square, as will the box of any shape who's horizontal and vertical maxima and minima are equal.

It is possible to interrogate the internal elements of the underlying CGPath and look for a series of lines that make a square. I suggest searching for code that parses the elements of a CGPath.

Note that if you are checking freehand drawing, you will likely need some "slop" in your calculations to allow for shapes that are close to, but not exactly squares, or you will likely never find a perfect square.

Also, what if the path contains a square plus other shape elements? You will need to decide how to handle situations like that.

huangapple
  • 本文由 发表于 2023年2月13日 23:16:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75437780.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定