LBXScanWrapper.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. //
  2. // LBXScanWrapper.swift
  3. // swiftScan
  4. //
  5. // Created by lbxia on 15/12/10.
  6. // Copyright © 2015年 xialibing. All rights reserved.
  7. //
  8. import UIKit
  9. import AVFoundation
  10. public struct LBXScanResult {
  11. /// 码内容
  12. public var strScanned: String?
  13. /// 扫描图像
  14. public var imgScanned: UIImage?
  15. /// 码的类型
  16. public var strBarCodeType: String?
  17. /// 码在图像中的位置
  18. public var arrayCorner: [AnyObject]?
  19. public init(str: String?, img: UIImage?, barCodeType: String?, corner: [AnyObject]?) {
  20. strScanned = str
  21. imgScanned = img
  22. strBarCodeType = barCodeType
  23. arrayCorner = corner
  24. }
  25. }
  26. open class LBXScanWrapper: NSObject,AVCaptureMetadataOutputObjectsDelegate {
  27. let device = AVCaptureDevice.default(for: AVMediaType.video)
  28. var input: AVCaptureDeviceInput?
  29. var output: AVCaptureMetadataOutput
  30. let session = AVCaptureSession()
  31. var previewLayer: AVCaptureVideoPreviewLayer?
  32. var stillImageOutput: AVCaptureStillImageOutput
  33. // 存储返回结果
  34. var arrayResult = [LBXScanResult]()
  35. // 扫码结果返回block
  36. var successBlock: ([LBXScanResult]) -> Void
  37. // 是否需要拍照
  38. var isNeedCaptureImage: Bool
  39. // 当前扫码结果是否处理
  40. var isNeedScanResult = true
  41. /**
  42. 初始化设备
  43. - parameter videoPreView: 视频显示UIView
  44. - parameter objType: 识别码的类型,缺省值 QR二维码
  45. - parameter isCaptureImg: 识别后是否采集当前照片
  46. - parameter cropRect: 识别区域
  47. - parameter success: 返回识别信息
  48. - returns:
  49. */
  50. init(videoPreView: UIView,
  51. objType: [AVMetadataObject.ObjectType] = [(AVMetadataObject.ObjectType.qr as NSString) as AVMetadataObject.ObjectType],
  52. isCaptureImg: Bool,
  53. cropRect: CGRect = .zero,
  54. success: @escaping (([LBXScanResult]) -> Void)) {
  55. successBlock = success
  56. output = AVCaptureMetadataOutput()
  57. isNeedCaptureImage = isCaptureImg
  58. stillImageOutput = AVCaptureStillImageOutput()
  59. super.init()
  60. guard let device = device else {
  61. return
  62. }
  63. do {
  64. input = try AVCaptureDeviceInput(device: device)
  65. } catch let error as NSError {
  66. print("AVCaptureDeviceInput(): \(error)")
  67. }
  68. guard let input = input else {
  69. return
  70. }
  71. if session.canAddInput(input) {
  72. session.addInput(input)
  73. }
  74. if session.canAddOutput(output) {
  75. session.addOutput(output)
  76. }
  77. if session.canAddOutput(stillImageOutput) {
  78. session.addOutput(stillImageOutput)
  79. }
  80. stillImageOutput.outputSettings = [AVVideoCodecJPEG: AVVideoCodecKey]
  81. session.sessionPreset = AVCaptureSession.Preset.high
  82. // 参数设置
  83. output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
  84. output.metadataObjectTypes = objType
  85. // output.metadataObjectTypes = [AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
  86. if !cropRect.equalTo(CGRect.zero) {
  87. // 启动相机后,直接修改该参数无效
  88. output.rectOfInterest = cropRect
  89. }
  90. previewLayer = AVCaptureVideoPreviewLayer(session: session)
  91. previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
  92. var frame: CGRect = videoPreView.frame
  93. frame.origin = CGPoint.zero
  94. previewLayer?.frame = frame
  95. videoPreView.layer.insertSublayer(previewLayer!, at: 0)
  96. if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(.continuousAutoFocus) {
  97. do {
  98. try input.device.lockForConfiguration()
  99. input.device.focusMode = AVCaptureDevice.FocusMode.continuousAutoFocus
  100. input.device.unlockForConfiguration()
  101. } catch let error as NSError {
  102. print("device.lockForConfiguration(): \(error)")
  103. }
  104. }
  105. }
  106. public func metadataOutput(_ output: AVCaptureMetadataOutput,
  107. didOutput metadataObjects: [AVMetadataObject],
  108. from connection: AVCaptureConnection) {
  109. captureOutput(output, didOutputMetadataObjects: metadataObjects, from: connection)
  110. }
  111. func start() {
  112. if !session.isRunning {
  113. isNeedScanResult = true
  114. session.startRunning()
  115. }
  116. }
  117. func stop() {
  118. if session.isRunning {
  119. isNeedScanResult = false
  120. session.stopRunning()
  121. }
  122. }
  123. open func captureOutput(_ captureOutput: AVCaptureOutput,
  124. didOutputMetadataObjects metadataObjects: [Any],
  125. from connection: AVCaptureConnection!) {
  126. guard isNeedScanResult else {
  127. // 上一帧处理中
  128. return
  129. }
  130. isNeedScanResult = false
  131. arrayResult.removeAll()
  132. // 识别扫码类型
  133. for current in metadataObjects {
  134. guard let code = current as? AVMetadataMachineReadableCodeObject else {
  135. continue
  136. }
  137. arrayResult.append(LBXScanResult(str: code.stringValue,
  138. img: UIImage(),
  139. barCodeType: code.type.rawValue,
  140. corner: code.corners as [AnyObject]?))
  141. }
  142. if arrayResult.isEmpty {
  143. isNeedScanResult = true
  144. } else {
  145. if isNeedCaptureImage {
  146. captureImage()
  147. } else {
  148. stop()
  149. successBlock(arrayResult)
  150. }
  151. }
  152. }
  153. //MARK: ----拍照
  154. open func captureImage() {
  155. guard let stillImageConnection = connectionWithMediaType(mediaType: AVMediaType.video as AVMediaType,
  156. connections: stillImageOutput.connections as [AnyObject]) else {
  157. return
  158. }
  159. stillImageOutput.captureStillImageAsynchronously(from: stillImageConnection, completionHandler: { (imageDataSampleBuffer, _) -> Void in
  160. self.stop()
  161. if let imageDataSampleBuffer = imageDataSampleBuffer,
  162. let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
  163. let scanImg = UIImage(data: imageData)
  164. for idx in 0 ... self.arrayResult.count - 1 {
  165. self.arrayResult[idx].imgScanned = scanImg
  166. }
  167. }
  168. self.successBlock(self.arrayResult)
  169. })
  170. }
  171. open func connectionWithMediaType(mediaType: AVMediaType, connections: [AnyObject]) -> AVCaptureConnection? {
  172. for connection in connections {
  173. guard let connectionTmp = connection as? AVCaptureConnection else {
  174. continue
  175. }
  176. for port in connectionTmp.inputPorts where port.mediaType == mediaType {
  177. return connectionTmp
  178. }
  179. }
  180. return nil
  181. }
  182. //MARK: 切换识别区域
  183. open func changeScanRect(cropRect: CGRect) {
  184. // 待测试,不知道是否有效
  185. stop()
  186. output.rectOfInterest = cropRect
  187. start()
  188. }
  189. //MARK: 切换识别码的类型
  190. open func changeScanType(objType: [AVMetadataObject.ObjectType]) {
  191. // 待测试中途修改是否有效
  192. output.metadataObjectTypes = objType
  193. }
  194. open func isGetFlash() -> Bool {
  195. return device != nil && device!.hasFlash && device!.hasTorch
  196. }
  197. /**
  198. 打开或关闭闪关灯
  199. - parameter torch: true:打开闪关灯 false:关闭闪光灯
  200. */
  201. open func setTorch(torch: Bool) {
  202. guard isGetFlash() else {
  203. return
  204. }
  205. do {
  206. try input?.device.lockForConfiguration()
  207. input?.device.torchMode = torch ? AVCaptureDevice.TorchMode.on : AVCaptureDevice.TorchMode.off
  208. input?.device.unlockForConfiguration()
  209. } catch let error as NSError {
  210. print("device.lockForConfiguration(): \(error)")
  211. }
  212. }
  213. /// 闪光灯打开或关闭
  214. open func changeTorch() {
  215. let torch = input?.device.torchMode == .off
  216. setTorch(torch: torch)
  217. }
  218. //MARK: ------获取系统默认支持的码的类型
  219. static func defaultMetaDataObjectTypes() -> [AVMetadataObject.ObjectType] {
  220. var types =
  221. [
  222. AVMetadataObject.ObjectType.qr,
  223. AVMetadataObject.ObjectType.upce,
  224. AVMetadataObject.ObjectType.code39,
  225. AVMetadataObject.ObjectType.code39Mod43,
  226. AVMetadataObject.ObjectType.ean13,
  227. AVMetadataObject.ObjectType.ean8,
  228. AVMetadataObject.ObjectType.code93,
  229. AVMetadataObject.ObjectType.code128,
  230. AVMetadataObject.ObjectType.pdf417,
  231. AVMetadataObject.ObjectType.aztec,
  232. ]
  233. // if #available(iOS 8.0, *)
  234. types.append(AVMetadataObject.ObjectType.interleaved2of5)
  235. types.append(AVMetadataObject.ObjectType.itf14)
  236. types.append(AVMetadataObject.ObjectType.dataMatrix)
  237. return types
  238. }
  239. /**
  240. 识别二维码码图像
  241. - parameter image: 二维码图像
  242. - returns: 返回识别结果
  243. */
  244. public static func recognizeQRImage(image: UIImage) -> [LBXScanResult] {
  245. guard let cgImage = image.cgImage else {
  246. return []
  247. }
  248. let detector = CIDetector(ofType: CIDetectorTypeQRCode,
  249. context: nil,
  250. options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
  251. let img = CIImage(cgImage: cgImage)
  252. let features = detector.features(in: img, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
  253. return features.filter {
  254. $0.isKind(of: CIQRCodeFeature.self)
  255. }.map {
  256. $0 as! CIQRCodeFeature
  257. }.map {
  258. LBXScanResult(str: $0.messageString,
  259. img: image,
  260. barCodeType: AVMetadataObject.ObjectType.qr.rawValue,
  261. corner: nil)
  262. }
  263. }
  264. //MARK: -- - 生成二维码,背景色及二维码颜色设置
  265. public static func createCode(codeType: String, codeString: String, size: CGSize, qrColor: UIColor, bkColor: UIColor) -> UIImage? {
  266. let stringData = codeString.data(using: .utf8)
  267. // 系统自带能生成的码
  268. // CIAztecCodeGenerator
  269. // CICode128BarcodeGenerator
  270. // CIPDF417BarcodeGenerator
  271. // CIQRCodeGenerator
  272. let qrFilter = CIFilter(name: codeType)
  273. qrFilter?.setValue(stringData, forKey: "inputMessage")
  274. qrFilter?.setValue("H", forKey: "inputCorrectionLevel")
  275. // 上色
  276. let colorFilter = CIFilter(name: "CIFalseColor",
  277. parameters: [
  278. "inputImage": qrFilter!.outputImage!,
  279. "inputColor0": CIColor(cgColor: qrColor.cgColor),
  280. "inputColor1": CIColor(cgColor: bkColor.cgColor),
  281. ]
  282. )
  283. guard let qrImage = colorFilter?.outputImage,
  284. let cgImage = CIContext().createCGImage(qrImage, from: qrImage.extent) else {
  285. return nil
  286. }
  287. UIGraphicsBeginImageContext(size)
  288. let context = UIGraphicsGetCurrentContext()!
  289. context.interpolationQuality = CGInterpolationQuality.none
  290. context.scaleBy(x: 1.0, y: -1.0)
  291. context.draw(cgImage, in: context.boundingBoxOfClipPath)
  292. let codeImage = UIGraphicsGetImageFromCurrentImageContext()
  293. UIGraphicsEndImageContext()
  294. return codeImage
  295. }
  296. public static func createCode128(codeString: String, size: CGSize, qrColor: UIColor, bkColor: UIColor) -> UIImage? {
  297. let stringData = codeString.data(using: String.Encoding.utf8)
  298. // 系统自带能生成的码
  299. // CIAztecCodeGenerator 二维码
  300. // CICode128BarcodeGenerator 条形码
  301. // CIPDF417BarcodeGenerator
  302. // CIQRCodeGenerator 二维码
  303. let qrFilter = CIFilter(name: "CICode128BarcodeGenerator")
  304. qrFilter?.setDefaults()
  305. qrFilter?.setValue(stringData, forKey: "inputMessage")
  306. guard let outputImage = qrFilter?.outputImage else {
  307. return nil
  308. }
  309. let context = CIContext()
  310. guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else {
  311. return nil
  312. }
  313. let image = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up)
  314. // Resize without interpolating
  315. return resizeImage(image: image, quality: CGInterpolationQuality.none, rate: 20.0)
  316. }
  317. // 根据扫描结果,获取图像中得二维码区域图像(如果相机拍摄角度故意很倾斜,获取的图像效果很差)
  318. static func getConcreteCodeImage(srcCodeImage: UIImage, codeResult: LBXScanResult) -> UIImage? {
  319. let rect = getConcreteCodeRectFromImage(srcCodeImage: srcCodeImage, codeResult: codeResult)
  320. guard !rect.isEmpty, let img = imageByCroppingWithStyle(srcImg: srcCodeImage, rect: rect) else {
  321. return nil
  322. }
  323. return imageRotation(image: img, orientation: UIImage.Orientation.right)
  324. }
  325. // 根据二维码的区域截取二维码区域图像
  326. public static func getConcreteCodeImage(srcCodeImage: UIImage, rect: CGRect) -> UIImage? {
  327. guard !rect.isEmpty, let img = imageByCroppingWithStyle(srcImg: srcCodeImage, rect: rect) else {
  328. return nil
  329. }
  330. return imageRotation(image: img, orientation: UIImage.Orientation.right)
  331. }
  332. // 获取二维码的图像区域
  333. public static func getConcreteCodeRectFromImage(srcCodeImage: UIImage, codeResult: LBXScanResult) -> CGRect {
  334. guard let corner = codeResult.arrayCorner as? [[String: Float]], corner.count >= 4 else {
  335. return .zero
  336. }
  337. let dicTopLeft = corner[0]
  338. let dicTopRight = corner[1]
  339. let dicBottomRight = corner[2]
  340. let dicBottomLeft = corner[3]
  341. let xLeftTopRatio = dicTopLeft["X"]!
  342. let yLeftTopRatio = dicTopLeft["Y"]!
  343. let xRightTopRatio = dicTopRight["X"]!
  344. let yRightTopRatio = dicTopRight["Y"]!
  345. let xBottomRightRatio = dicBottomRight["X"]!
  346. let yBottomRightRatio = dicBottomRight["Y"]!
  347. let xLeftBottomRatio = dicBottomLeft["X"]!
  348. let yLeftBottomRatio = dicBottomLeft["Y"]!
  349. // 由于截图只能矩形,所以截图不规则四边形的最大外围
  350. let xMinLeft = CGFloat(min(xLeftTopRatio, xLeftBottomRatio))
  351. let xMaxRight = CGFloat(max(xRightTopRatio, xBottomRightRatio))
  352. let yMinTop = CGFloat(min(yLeftTopRatio, yRightTopRatio))
  353. let yMaxBottom = CGFloat(max(yLeftBottomRatio, yBottomRightRatio))
  354. let imgW = srcCodeImage.size.width
  355. let imgH = srcCodeImage.size.height
  356. // 宽高反过来计算
  357. return CGRect(x: xMinLeft * imgH,
  358. y: yMinTop * imgW,
  359. width: (xMaxRight - xMinLeft) * imgH,
  360. height: (yMaxBottom - yMinTop) * imgW)
  361. }
  362. //MARK: ----图像处理
  363. /**
  364. @brief 图像中间加logo图片
  365. @param srcImg 原图像
  366. @param LogoImage logo图像
  367. @param logoSize logo图像尺寸
  368. @return 加Logo的图像
  369. */
  370. public static func addImageLogo(srcImg: UIImage, logoImg: UIImage, logoSize: CGSize) -> UIImage {
  371. UIGraphicsBeginImageContext(srcImg.size)
  372. srcImg.draw(in: CGRect(x: 0, y: 0, width: srcImg.size.width, height: srcImg.size.height))
  373. let rect = CGRect(x: srcImg.size.width / 2 - logoSize.width / 2,
  374. y: srcImg.size.height / 2 - logoSize.height / 2,
  375. width: logoSize.width,
  376. height: logoSize.height)
  377. logoImg.draw(in: rect)
  378. let resultingImage = UIGraphicsGetImageFromCurrentImageContext()
  379. UIGraphicsEndImageContext()
  380. return resultingImage!
  381. }
  382. //图像缩放
  383. static func resizeImage(image: UIImage, quality: CGInterpolationQuality, rate: CGFloat) -> UIImage? {
  384. var resized: UIImage?
  385. let width = image.size.width * rate
  386. let height = image.size.height * rate
  387. UIGraphicsBeginImageContext(CGSize(width: width, height: height))
  388. let context = UIGraphicsGetCurrentContext()
  389. context?.interpolationQuality = quality
  390. image.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
  391. resized = UIGraphicsGetImageFromCurrentImageContext()
  392. UIGraphicsEndImageContext()
  393. return resized
  394. }
  395. // 图像裁剪
  396. static func imageByCroppingWithStyle(srcImg: UIImage, rect: CGRect) -> UIImage? {
  397. guard let imagePartRef = srcImg.cgImage?.cropping(to: rect) else {
  398. return nil
  399. }
  400. return UIImage(cgImage: imagePartRef)
  401. }
  402. // 图像旋转
  403. static func imageRotation(image: UIImage, orientation: UIImage.Orientation) -> UIImage {
  404. var rotate: Double = 0.0
  405. var rect: CGRect
  406. var translateX: CGFloat = 0.0
  407. var translateY: CGFloat = 0.0
  408. var scaleX: CGFloat = 1.0
  409. var scaleY: CGFloat = 1.0
  410. switch orientation {
  411. case .left:
  412. rotate = .pi / 2
  413. rect = CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width)
  414. translateX = 0
  415. translateY = -rect.size.width
  416. scaleY = rect.size.width / rect.size.height
  417. scaleX = rect.size.height / rect.size.width
  418. case .right:
  419. rotate = 3 * .pi / 2
  420. rect = CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width)
  421. translateX = -rect.size.height
  422. translateY = 0
  423. scaleY = rect.size.width / rect.size.height
  424. scaleX = rect.size.height / rect.size.width
  425. case .down:
  426. rotate = .pi
  427. rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
  428. translateX = -rect.size.width
  429. translateY = -rect.size.height
  430. default:
  431. rotate = 0.0
  432. rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
  433. translateX = 0
  434. translateY = 0
  435. }
  436. UIGraphicsBeginImageContext(rect.size)
  437. let context = UIGraphicsGetCurrentContext()!
  438. // 做CTM变换
  439. context.translateBy(x: 0.0, y: rect.size.height)
  440. context.scaleBy(x: 1.0, y: -1.0)
  441. context.rotate(by: CGFloat(rotate))
  442. context.translateBy(x: translateX, y: translateY)
  443. context.scaleBy(x: scaleX, y: scaleY)
  444. // 绘制图片
  445. context.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
  446. return UIGraphicsGetImageFromCurrentImageContext()!
  447. }
  448. }