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

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

端点可拖动

1, 识别点

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

给定四个坐标,

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

就认为要拖动那个点

// 四个点,按方位划分enum SketchPointOption: Int{case leftTop = 0, rightTop = 1, leftBottom = 2case 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 = 20let 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 = 1l.fillColor = UIColor.clear.cgColorl.strokeColor = SketchColor.normalreturn l}()var pointsLayer: CAShapeLayer = {let l = CAShapeLayer()l.fillColor = UIColor.white.cgColorl.lineWidth = 2l.strokeColor = SketchColor.normalreturn l}()var linePath: UIBezierPath = {let l = UIBezierPath()l.lineWidth = 1return l}()var pointPath: UIBezierPath = {let l = UIBezierPath()l.lineWidth = 2return l}()/**刷新数据*/func reloadData(){linePath.removeAllPoints()pointPath.removeAllPoints()draw(sketch: defaultPoints)lineLayer.path = linePath.cgPathpointsLayer.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 = 8pointPath.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.countfor 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 个 ptif distance < 40{ggTouch = truebreak}}}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 = 1layer.borderColor = UIColor.lightGray.cgColorisHidden = truelayer.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 = 3ctx.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.ylet x3 = twicePt.x, y3 = twicePt.ylet x4 = thirdPt.x, y4 = thirdPt.ylet 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: CGPointvar rightTop: CGPointvar leftBottom: CGPointvar rightBottom: CGPointvar restPoints = [CGPoint]()var pts: [CGPoint]{[leftTop, rightTop, leftBottom, rightBottom]}mutatingfunc sortPointClockwise() -> Bool{// 按左上,右上,右下,左下排序var result = [CGPoint](repeating: CGPoint.zero, count: 4)var minDistance: CGFloat = -1for p in pts{let distance = p.x * p.x + p.y * p.yif minDistance == -1 || distance < minDistance{result[0] = pminDistance = distance}}var leftPts = pts.filter { (pp) -> Bool inpp != 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 inpt != 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

低仿扫描全能王的选择区域功能相关推荐

  1. 合合信息扫描全能王“照片高清修复”功能上线,3秒还原老照片

    穿越时光的"美颜"!合合信息智能图像处理技术让老照片"焕新" "春运"已经开始,团聚时刻即将到来.和亲人们一起围炉话家常,翻开旧日的相册,品 ...

  2. 扫描全能王文件上传不了服务器,扫描全能王如何备份JPG 文件备份JPG办法

    软件安装:手机应用宝 随着科技的发展,手机在我们的生活中扮演了很重要的角色,手机在便利我们生活的同时,也会对我们造成很重要的影响.手机已经成为了我们的必需品,不仅是通讯工具,而且很大程度上都是娱乐功能 ...

  3. pdf导入ps颜色太浅_PDF 文件编辑转换难?或许你需要一个扫描全能王!

    手机上存了各种资料,想把所有 PDF 文件统一存储.管理? 手机打开 PDF 文件时,阅读难?批注难?分享难? 想编辑/调整 PDF 页面,装了一堆杂七杂八的 APP 效果仍然不理想? PDF,素来以 ...

  4. pdf增强锐化软件_安卓软件—CamScanner 扫描全能王

    上期回顾 1 CamScanner扫描全能王,将智能手机变成随身携带的扫描仪.方便快捷地记录管理您的各种文档,收据,笔记和白板讨论等.并通过智慧精准的图像裁剪和图像增强演算法,保证您扫描的内容清晰可读 ...

  5. 复现扫描全能王的增强锐化

    扫描全能王的增强锐化其实是自适应二值化的变体. 直接用 OpenCV 的函数会让背景变花,因为背景是渐变的,直接拿均值当阈值的话,总有一些背景像素在阈值下面.所以需要将阈值乘以一个系数,比如 0.9, ...

  6. OpenCV实现“全能扫描王”的图像矫正功能

    前言: 相信很多人手机里都装了个"扫描全能王"APP,平时可以用它来可以扫描一些证件.文本,确实很好用,第一次用的时候确实感觉功能很强大啊算法很牛逼啊.但是仔细一想,其实这些实现起 ...

  7. 【免费资源必备】LingoDeer,扫描全能王直装版等五款APP让你享受上千个免费资源

    用软件最怕收费限会员使用这样的字眼,但是就算我们穷,我们也不能怂.在论坛混了这么久,今天就来给大家送福利啦!六款破解版软件,免费看书,免费学习等等等.收集软件不容易,大家要是喜欢,希望可以我点赞和收藏 ...

  8. Android 资源全能王 v1.1.4 (资源全能王搜影视、音乐、磁力、网盘、实用工具)

    特点描述 搜影视:软件聚合多路优质资源路线,如爱美剧.爱迪等等:想看什么,直接搜索.简单易操作. 搜软件:很都朋友都喜欢搜集各类好用的软件,碍于渠道比较单一,找不到自己满意的.那么资源全能王的搜软件功 ...

  9. 扫描枪设置虚拟串口linux,顶码扫描枪全能王TP20Y怎么设置USB虚拟串口模式?

    顶码全能王TP20Y是一款极具性价比的二维影像手持扫描枪,可以读取所有一维.二维条码,外形小巧,价格实惠,卓越的工业保护设计,坚固耐用,适用于超市.便利店的零售收银.自动化办公与文件管理.邮件与包裹的 ...

  10. android 名片识别软件,手机名片识别工具Android名片全能王评测

    人与人之间的关系是需要交流来建立的,所以难免的在职场上,官场上等各种场上会碰到这样那样的朋友,当然也会结实到新的朋友,当然也会收到很多名片!难道还要手动来一个一个存到手机的通讯录中吗?不.我们不要!接 ...

最新文章

  1. 【API知识】ElementUI一些问题的解决方案
  2. 帝国cms7.5 utf-8本地网站电脑手机模板开发同步插件即时预览修改结果
  3. android让文件按顺序列表,Java/Android 获取文件夹的文件列表(file.listFiles())并按名称排序,中文优先...
  4. 计算机在社区健康档案管理中的作用,某社区卫生中心对辖区居民的健康档案信息进行电子化。一直建档居民的年龄范围在1..._考试资料网...
  5. C# WinForm中获取当前程序运行目录的方法
  6. Vue+Vue-router+Vuex项目实战
  7. linux+PATH
  8. php导入跟引入的区别,PHP7 引入的“??” 和“?:”的区别
  9. 2025年全球5G室内无线市值将达5.09亿美元
  10. vc access mysql_VC中访问Access数据库的方法
  11. 想起一则急着争权的故事
  12. “蹲坑神器”与它背后男人们不得不说的故事
  13. Rust本地化实现 —— fluent
  14. Laravel Collect集合用pluck取多维数组中某个字段值
  15. python实现非标准正态分布下概率密度有关计算
  16. 第一二天作业-BGP MPLS + OSPF分流互备做法
  17. 【Linux认证考试分数线多少】
  18. 蓝桥杯——java(b组)省赛
  19. 干货分享 | 杭州“边缘计算” 研讨会
  20. white-space: pre-wrap 会出现首行缩进的解决办法

热门文章

  1. switch语句里面使用break,return
  2. 路由器实验要求之配置实验、直连路由验证、静态路由
  3. java中File流转Base64
  4. ios 合并图片显示
  5. 旷视6号员工范浩强:高二开始实习,“兼职”读姚班,25岁在CVPR斩获第四个世界第一...
  6. 使用IDEA工具配置和运行vue项目(详细其中的坑)
  7. linux redis玂家链接不上,Unicode编码的熟悉与研究过程(内附全部汉字编码列表)...
  8. GUI图形用户接口编写QQ登录界面
  9. 【电路】PT1000/PT100温度采集电路
  10. 结对作业 ——UI组第八组 冯富禹 齐天浩