低仿扫描全能王的选择区域功能
程序员文章站
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
上一篇: sssocket