欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

低仿扫描全能王的选择区域功能

程序员文章站 2022-03-23 22:00:08
扫描全能王 CS, Cam Scanner 很是强大,本文简单仿一下他的选择区域功能。端点可拖动1, 识别点本文例子比较简单,只有四个端点,用于拖拽给定四个坐标,先识别到开始点击的位置,距离哪个点近,就认为要拖动那个点 // 四个点,按方位划分 enum SketchPointOption: Int{ case leftTop = 0, rightTop = 1, leftBottom = 2 case rightBottom = 3...

扫描全能王 CS, Cam Scanner 很是强大,

本文简单仿一下他的选择区域功能。

端点可拖动

低仿扫描全能王的选择区域功能

1, 识别点

本文例子比较简单,只有四个端点,用于拖拽

给定四个坐标,

先识别到开始点击的位置,距离哪个点近,

就认为要拖动那个点


    // 四个点,按方位划分
    enum SketchPointOption: Int{
        case leftTop = 0, rightTop = 1, leftBottom = 2
        case rightBottom = 3
    }



//	先识别到开始点击的位置,距离哪个点,在一定范围内
 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        super.touchesBegan(touches, with: event)
        
        guard let touch = touches.first else{
            return
        }
        
        
        let currentPoint = touch.location(in: self)
        
        // 判定选中的最大距离
        let maxDistance: CGFloat = 20
        let points = [defaultPoints.leftTop, defaultPoints.rightTop, defaultPoints.leftBottom,
                      defaultPoints.rightBottom]
        for pt in points{
            let distance = abs(pt.x - currentPoint.x) + abs(pt.y - currentPoint.y)
            if distance <= maxDistance, let pointIndex = points.firstIndex(of: pt){
                currentControlPointType = SketchPointOption(rawValue: pointIndex)
                break
            }
        }
    }
    


    // 如果上一步识别到了,就更新选择点的位置,并重新连线
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        if currentControlPointType != nil, let touch = touches.first{
            
            let current = touch.location(in: self)
            guard bounds.contains(current) else{
                return
            }
            // 就更新选择点的位置
            prepare(point: current)
            // 并重新连线
            reloadData()
        }
        
    }

2,更新 UI

本文中,图上四个点,没有用四个控件,

四个点用的是 CALayer, 事件处理采用上面的触摸检测,


    var lineLayer: CAShapeLayer = {
        let l = CAShapeLayer()
        l.lineWidth = 1
        l.fillColor = UIColor.clear.cgColor
        l.strokeColor = SketchColor.normal
        return l
    }()
    
    var pointsLayer: CAShapeLayer = {
        let l = CAShapeLayer()
        l.fillColor = UIColor.white.cgColor
        l.lineWidth = 2
        l.strokeColor = SketchColor.normal
        return l
    }()
    
    var linePath: UIBezierPath = {
        let l = UIBezierPath()
        l.lineWidth = 1
        return l
    }()
    
    var pointPath: UIBezierPath = {
        let l = UIBezierPath()
        l.lineWidth = 2
        return l
    }()


/**
     刷新数据
     */
    func reloadData(){
        linePath.removeAllPoints()
        pointPath.removeAllPoints()
        draw(sketch: defaultPoints)
        
       
        lineLayer.path = linePath.cgPath
        pointsLayer.path = pointPath.cgPath
    }
    
    
    /**
     绘制单个图形
     */
    func draw(sketch model: SketchModel){
        drawLine(with: model)
        drawPoints(with: model)
    }

    
    
    
    
    /**
      绘制四条边
     */
    func drawLine(with sketch: SketchModel){
        linePath.move(to: sketch.leftTop)
        linePath.addLine(to: sketch.rightTop)
        linePath.addLine(to: sketch.rightBottom)
        linePath.addLine(to: sketch.leftBottom)
        linePath.close()
        
    }
    
    
    
    
    /**
     绘制四个顶点
     */
    func drawPoints(with sketch: SketchModel){
        let radius: CGFloat = 8
        pointPath.move(to: sketch.leftTop.advance(radius))
        pointPath.addArc(withCenter: sketch.leftTop, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
        pointPath.move(to: sketch.rightTop.advance(radius))
        pointPath.addArc(withCenter: sketch.rightTop, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
        pointPath.move(to: sketch.rightBottom.advance(radius))
        pointPath.addArc(withCenter: sketch.rightBottom, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
        pointPath.move(to: sketch.leftBottom.advance(radius))
        pointPath.addArc(withCenter: sketch.leftBottom, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
        
        ///
     
    }

与上面结合, touchesBegan 识别到事件,

touchesMoved 就是不断的 reloadData, 不停重绘

最小区域检测

有四个点,选中点为当前点,

剩余点,为剩下的三个点。

移动的时候 touchesMoved,该点靠近其他三个点到一定距离,就取消


var currentControlPointType: SketchPointOption? = nil{
        didSet{
            if let type = currentControlPointType{
                var pts = [defaultPoints.leftTop, defaultPoints.rightTop, defaultPoints.leftBottom,
                           defaultPoints.rightBottom]
                pts.remove(at: type.rawValue)
                defaultPoints.restPoints = pts
            }
            else{
                defaultPoints.restPoints = []
            }
        }
    }



override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        if currentControlPointType != nil, let touch = touches.first{
            
            
            let current = touch.location(in: self)
            guard bounds.contains(current) else{
                return
            }
            
            let points = defaultPoints.restPoints + [current]
            let ptCount = points.count
            for i in 0...(ptCount - 2){
                for j in (i + 1)...(ptCount - 1){
                    let lhs = points[i]
                    let rhs = points[j]
                    let distance = abs(lhs.x - rhs.x) + abs(lhs.y - rhs.y)
                    // 移动的时候 touchesMoved,该点靠近其他三个点到一定距离,就取消
                    // 这里的距离是 40 个 pt
                    if distance < 40{
                        ggTouch = true
                        break
                    }
                }
            }
            
            
            guard ggTouch == false else {
                
                return
            }
            
            // 更新选中点的坐标
            prepare(point: current)
            
         
            // 重新连线
            reloadData()
        }
        
    }

四个点比较好做,全能扫描王,8 个点,

端点拖,中点平移,

中点平移的时候,点的关系,稍复杂

常规的放大镜处理,

通过借助, CALayer 的 render(in:context) 方法,

再翻转一下坐标系,就好了

移动的时候,更新点,给 renderPoint ,就好了

具体的传值,见 GitHub repo

 class MagnifierView: UIView {

    
    public var renderView: UIView?
    
    public var renderPoint : CGPoint  = CGPoint.zero {
        didSet{
            self.layer.setNeedsDisplay()
        }
    }


    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        layer.borderWidth = 1
        layer.borderColor = UIColor.lightGray.cgColor
        isHidden = true
        layer.delegate = self
        // 保证和屏幕读取像素的比例一致
        layer.contentsScale = UIScreen.main.scale
    }
    

    
    override func draw(_ layer: CALayer, in ctx: CGContext) {
        super.draw(layer, in: ctx)
        guard let renderer = renderView else {
            return
        }
        // 提前位移半个长宽的坑
        ctx.translateBy(x: frame.size.width * 0.5, y: frame.size.height * 0.5)
        /// 缩放比例
        let scale : CGFloat = 3
        ctx.scaleBy(x: scale, y: scale)
        // 再次位移后就可以把触摸点移至self.center的位置
        ctx.translateBy(x: -renderPoint.x, y: -renderPoint.y)
        
        renderer.layer.render(in: ctx)
    }
    
}

凹四边形检测

通过图形学的公式

低仿扫描全能王的选择区域功能

extension CGPoint{
    func gimpTransformPolygon(isConvex firstPt: CGPoint, two twicePt: CGPoint, three thirdPt: CGPoint) -> Bool{
        
        let x2 = firstPt.x, y2 = firstPt.y
        let x3 = twicePt.x, y3 = twicePt.y
        let x4 = thirdPt.x, y4 = thirdPt.y
     
        let z1 = ((x2 - x) * (y4 - y) - (x4 - x) * (y2 - y))
        let z2 = ((x4 - x) * (y3 - y) - (x3 - x) * (y4 - y))
        let z3 = ((x4 - x2) * (y3 - y2) - (x3 - x2) * (y4 - y2))
        let z4 = ((x3 - x2) * (y - y2) - (x - x2) * (y3 - y2))
     
        return (z1 * z2 > 0) && (z3 * z4 > 0)
    }
    
    
}

交叉重连

拖动,交叉了,重新连

低仿扫描全能王的选择区域功能

出现了交叉,就更新点的连接方式

低仿扫描全能王的选择区域功能

采用图形学的方法,

先判断一个点,在一条线的左边,还是右边


extension CGPoint{
    
    func pointSideLine(left lhs: CGPoint, right rhs: CGPoint) -> CGFloat{
        
        return (x - lhs.x) * (rhs.y - lhs.y) - (y - lhs.y) * (rhs.x - lhs.x)
        
    }
    
}

再判断四个点的顺时针顺序

struct SketchModel{
    var leftTop: CGPoint
    var rightTop: CGPoint
    var leftBottom: CGPoint
    var rightBottom: CGPoint
    
    var restPoints = [CGPoint]()
    
    
    var pts: [CGPoint]{
        [leftTop, rightTop, leftBottom, rightBottom]
    }
    

    mutating
    func sortPointClockwise() -> Bool{
         // 按左上,右上,右下,左下排序
        var result = [CGPoint](repeating: CGPoint.zero, count: 4)
        var minDistance: CGFloat = -1
        for p in pts{
            let distance = p.x * p.x + p.y * p.y
            if minDistance == -1 || distance < minDistance{
                result[0] = p
                minDistance = distance
            }
        }
        var leftPts = pts.filter { (pp) -> Bool in
            pp != result[0]
        }
        if leftPts[1].pointSideLine(left: result[0], right: leftPts[0]) * leftPts[2].pointSideLine(left: result[0], right: leftPts[0]) < 0{
            result[2] = leftPts[0]
        }
        else if leftPts[0].pointSideLine(left: result[0], right: leftPts[1]) * leftPts[2].pointSideLine(left: result[0], right: leftPts[1]) < 0{
            result[2] = leftPts[1]
        }
        else if leftPts[0].pointSideLine(left: result[0], right: leftPts[2]) * leftPts[1].pointSideLine(left: result[0], right: leftPts[2]) < 0{
            result[2] = leftPts[2]
        }
        leftPts = pts.filter { (pt) -> Bool in
            pt != result[0] && pt != result[2]
        }
        if leftPts[0].pointSideLine(left: result[0], right: result[2]) > 0{
            result[1] = leftPts[0]
            result[3] = leftPts[1]
        }
        else{
            result[1] = leftPts[1]
            result[3] = leftPts[0]
        }
        

        
        if result[0].gimpTransformPolygon(isConvex: result[1], two: result[3], three: result[2]){
        // 凸四边形,才有意义
        
            leftTop = result[0]
            rightTop = result[1]
            
            rightBottom = result[2]
            leftBottom = result[3]
            return true
        }
        else{
        
        // 无效图形,凹四边形,不用管
            return false
        }
        
    }
  
}


触摸结束的时候,判断下,就好

github repo

本文地址:https://blog.csdn.net/dengjiangszhan/article/details/108726057

相关标签: 项目中来 ios