123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- //
- // LBXScanWrapper.swift
- // swiftScan
- //
- // Created by lbxia on 15/12/10.
- // Copyright © 2015年 xialibing. All rights reserved.
- //
- import UIKit
- import AVFoundation
- public struct LBXScanResult {
-
- /// 码内容
- public var strScanned: String?
-
- /// 扫描图像
- public var imgScanned: UIImage?
-
- /// 码的类型
- public var strBarCodeType: String?
- /// 码在图像中的位置
- public var arrayCorner: [AnyObject]?
- public init(str: String?, img: UIImage?, barCodeType: String?, corner: [AnyObject]?) {
- strScanned = str
- imgScanned = img
- strBarCodeType = barCodeType
- arrayCorner = corner
- }
- }
- open class LBXScanWrapper: NSObject,AVCaptureMetadataOutputObjectsDelegate {
-
- let device = AVCaptureDevice.default(for: AVMediaType.video)
- var input: AVCaptureDeviceInput?
- var output: AVCaptureMetadataOutput
- let session = AVCaptureSession()
- var previewLayer: AVCaptureVideoPreviewLayer?
- var stillImageOutput: AVCaptureStillImageOutput
- // 存储返回结果
- var arrayResult = [LBXScanResult]()
- // 扫码结果返回block
- var successBlock: ([LBXScanResult]) -> Void
- // 是否需要拍照
- var isNeedCaptureImage: Bool
- // 当前扫码结果是否处理
- var isNeedScanResult = true
-
- /**
- 初始化设备
- - parameter videoPreView: 视频显示UIView
- - parameter objType: 识别码的类型,缺省值 QR二维码
- - parameter isCaptureImg: 识别后是否采集当前照片
- - parameter cropRect: 识别区域
- - parameter success: 返回识别信息
- - returns:
- */
- init(videoPreView: UIView,
- objType: [AVMetadataObject.ObjectType] = [(AVMetadataObject.ObjectType.qr as NSString) as AVMetadataObject.ObjectType],
- isCaptureImg: Bool,
- cropRect: CGRect = .zero,
- success: @escaping (([LBXScanResult]) -> Void)) {
-
- successBlock = success
- output = AVCaptureMetadataOutput()
- isNeedCaptureImage = isCaptureImg
- stillImageOutput = AVCaptureStillImageOutput()
- super.init()
-
- guard let device = device else {
- return
- }
- do {
- input = try AVCaptureDeviceInput(device: device)
- } catch let error as NSError {
- print("AVCaptureDeviceInput(): \(error)")
- }
- guard let input = input else {
- return
- }
-
- if session.canAddInput(input) {
- session.addInput(input)
- }
- if session.canAddOutput(output) {
- session.addOutput(output)
- }
- if session.canAddOutput(stillImageOutput) {
- session.addOutput(stillImageOutput)
- }
- stillImageOutput.outputSettings = [AVVideoCodecJPEG: AVVideoCodecKey]
- session.sessionPreset = AVCaptureSession.Preset.high
- // 参数设置
- output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
- output.metadataObjectTypes = objType
- // output.metadataObjectTypes = [AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
- if !cropRect.equalTo(CGRect.zero) {
- // 启动相机后,直接修改该参数无效
- output.rectOfInterest = cropRect
- }
- previewLayer = AVCaptureVideoPreviewLayer(session: session)
- previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
- var frame: CGRect = videoPreView.frame
- frame.origin = CGPoint.zero
- previewLayer?.frame = frame
- videoPreView.layer.insertSublayer(previewLayer!, at: 0)
- if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(.continuousAutoFocus) {
- do {
- try input.device.lockForConfiguration()
- input.device.focusMode = AVCaptureDevice.FocusMode.continuousAutoFocus
- input.device.unlockForConfiguration()
- } catch let error as NSError {
- print("device.lockForConfiguration(): \(error)")
- }
- }
- }
- public func metadataOutput(_ output: AVCaptureMetadataOutput,
- didOutput metadataObjects: [AVMetadataObject],
- from connection: AVCaptureConnection) {
- captureOutput(output, didOutputMetadataObjects: metadataObjects, from: connection)
- }
-
- func start() {
- if !session.isRunning {
- isNeedScanResult = true
- session.startRunning()
- }
- }
-
- func stop() {
- if session.isRunning {
- isNeedScanResult = false
- session.stopRunning()
- }
- }
-
- open func captureOutput(_ captureOutput: AVCaptureOutput,
- didOutputMetadataObjects metadataObjects: [Any],
- from connection: AVCaptureConnection!) {
- guard isNeedScanResult else {
- // 上一帧处理中
- return
- }
- isNeedScanResult = false
- arrayResult.removeAll()
- // 识别扫码类型
- for current in metadataObjects {
- guard let code = current as? AVMetadataMachineReadableCodeObject else {
- continue
- }
- arrayResult.append(LBXScanResult(str: code.stringValue,
- img: UIImage(),
- barCodeType: code.type.rawValue,
- corner: code.corners as [AnyObject]?))
- }
- if arrayResult.isEmpty {
- isNeedScanResult = true
- } else {
- if isNeedCaptureImage {
- captureImage()
- } else {
- stop()
- successBlock(arrayResult)
- }
- }
- }
-
- //MARK: ----拍照
- open func captureImage() {
- guard let stillImageConnection = connectionWithMediaType(mediaType: AVMediaType.video as AVMediaType,
- connections: stillImageOutput.connections as [AnyObject]) else {
- return
- }
- stillImageOutput.captureStillImageAsynchronously(from: stillImageConnection, completionHandler: { (imageDataSampleBuffer, _) -> Void in
- self.stop()
- if let imageDataSampleBuffer = imageDataSampleBuffer,
- let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
-
- let scanImg = UIImage(data: imageData)
- for idx in 0 ... self.arrayResult.count - 1 {
- self.arrayResult[idx].imgScanned = scanImg
- }
- }
- self.successBlock(self.arrayResult)
- })
- }
-
- open func connectionWithMediaType(mediaType: AVMediaType, connections: [AnyObject]) -> AVCaptureConnection? {
- for connection in connections {
- guard let connectionTmp = connection as? AVCaptureConnection else {
- continue
- }
- for port in connectionTmp.inputPorts where port.mediaType == mediaType {
- return connectionTmp
- }
- }
- return nil
- }
-
-
- //MARK: 切换识别区域
- open func changeScanRect(cropRect: CGRect) {
- // 待测试,不知道是否有效
- stop()
- output.rectOfInterest = cropRect
- start()
- }
- //MARK: 切换识别码的类型
- open func changeScanType(objType: [AVMetadataObject.ObjectType]) {
- // 待测试中途修改是否有效
- output.metadataObjectTypes = objType
- }
-
- open func isGetFlash() -> Bool {
- return device != nil && device!.hasFlash && device!.hasTorch
- }
-
- /**
- 打开或关闭闪关灯
- - parameter torch: true:打开闪关灯 false:关闭闪光灯
- */
- open func setTorch(torch: Bool) {
- guard isGetFlash() else {
- return
- }
- do {
- try input?.device.lockForConfiguration()
- input?.device.torchMode = torch ? AVCaptureDevice.TorchMode.on : AVCaptureDevice.TorchMode.off
- input?.device.unlockForConfiguration()
- } catch let error as NSError {
- print("device.lockForConfiguration(): \(error)")
- }
- }
-
-
- /// 闪光灯打开或关闭
- open func changeTorch() {
- let torch = input?.device.torchMode == .off
- setTorch(torch: torch)
- }
-
- //MARK: ------获取系统默认支持的码的类型
- static func defaultMetaDataObjectTypes() -> [AVMetadataObject.ObjectType] {
- var types =
- [
- AVMetadataObject.ObjectType.qr,
- AVMetadataObject.ObjectType.upce,
- AVMetadataObject.ObjectType.code39,
- AVMetadataObject.ObjectType.code39Mod43,
- AVMetadataObject.ObjectType.ean13,
- AVMetadataObject.ObjectType.ean8,
- AVMetadataObject.ObjectType.code93,
- AVMetadataObject.ObjectType.code128,
- AVMetadataObject.ObjectType.pdf417,
- AVMetadataObject.ObjectType.aztec,
- ]
- // if #available(iOS 8.0, *)
- types.append(AVMetadataObject.ObjectType.interleaved2of5)
- types.append(AVMetadataObject.ObjectType.itf14)
- types.append(AVMetadataObject.ObjectType.dataMatrix)
- return types
- }
-
- /**
- 识别二维码码图像
-
- - parameter image: 二维码图像
-
- - returns: 返回识别结果
- */
- public static func recognizeQRImage(image: UIImage) -> [LBXScanResult] {
- guard let cgImage = image.cgImage else {
- return []
- }
- let detector = CIDetector(ofType: CIDetectorTypeQRCode,
- context: nil,
- options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
- let img = CIImage(cgImage: cgImage)
- let features = detector.features(in: img, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
- return features.filter {
- $0.isKind(of: CIQRCodeFeature.self)
- }.map {
- $0 as! CIQRCodeFeature
- }.map {
- LBXScanResult(str: $0.messageString,
- img: image,
- barCodeType: AVMetadataObject.ObjectType.qr.rawValue,
- corner: nil)
- }
- }
-
-
- //MARK: -- - 生成二维码,背景色及二维码颜色设置
- public static func createCode(codeType: String, codeString: String, size: CGSize, qrColor: UIColor, bkColor: UIColor) -> UIImage? {
- let stringData = codeString.data(using: .utf8)
- // 系统自带能生成的码
- // CIAztecCodeGenerator
- // CICode128BarcodeGenerator
- // CIPDF417BarcodeGenerator
- // CIQRCodeGenerator
- let qrFilter = CIFilter(name: codeType)
- qrFilter?.setValue(stringData, forKey: "inputMessage")
- qrFilter?.setValue("H", forKey: "inputCorrectionLevel")
- // 上色
- let colorFilter = CIFilter(name: "CIFalseColor",
- parameters: [
- "inputImage": qrFilter!.outputImage!,
- "inputColor0": CIColor(cgColor: qrColor.cgColor),
- "inputColor1": CIColor(cgColor: bkColor.cgColor),
- ]
- )
- guard let qrImage = colorFilter?.outputImage,
- let cgImage = CIContext().createCGImage(qrImage, from: qrImage.extent) else {
- return nil
- }
- UIGraphicsBeginImageContext(size)
- let context = UIGraphicsGetCurrentContext()!
- context.interpolationQuality = CGInterpolationQuality.none
- context.scaleBy(x: 1.0, y: -1.0)
- context.draw(cgImage, in: context.boundingBoxOfClipPath)
- let codeImage = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return codeImage
- }
-
- public static func createCode128(codeString: String, size: CGSize, qrColor: UIColor, bkColor: UIColor) -> UIImage? {
- let stringData = codeString.data(using: String.Encoding.utf8)
- // 系统自带能生成的码
- // CIAztecCodeGenerator 二维码
- // CICode128BarcodeGenerator 条形码
- // CIPDF417BarcodeGenerator
- // CIQRCodeGenerator 二维码
- let qrFilter = CIFilter(name: "CICode128BarcodeGenerator")
- qrFilter?.setDefaults()
- qrFilter?.setValue(stringData, forKey: "inputMessage")
- guard let outputImage = qrFilter?.outputImage else {
- return nil
- }
- let context = CIContext()
- guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else {
- return nil
- }
- let image = UIImage(cgImage: cgImage, scale: 1.0, orientation: UIImage.Orientation.up)
- // Resize without interpolating
- return resizeImage(image: image, quality: CGInterpolationQuality.none, rate: 20.0)
- }
-
-
- // 根据扫描结果,获取图像中得二维码区域图像(如果相机拍摄角度故意很倾斜,获取的图像效果很差)
- static func getConcreteCodeImage(srcCodeImage: UIImage, codeResult: LBXScanResult) -> UIImage? {
- let rect = getConcreteCodeRectFromImage(srcCodeImage: srcCodeImage, codeResult: codeResult)
- guard !rect.isEmpty, let img = imageByCroppingWithStyle(srcImg: srcCodeImage, rect: rect) else {
- return nil
- }
- return imageRotation(image: img, orientation: UIImage.Orientation.right)
- }
-
- // 根据二维码的区域截取二维码区域图像
- public static func getConcreteCodeImage(srcCodeImage: UIImage, rect: CGRect) -> UIImage? {
- guard !rect.isEmpty, let img = imageByCroppingWithStyle(srcImg: srcCodeImage, rect: rect) else {
- return nil
- }
- return imageRotation(image: img, orientation: UIImage.Orientation.right)
- }
-
- // 获取二维码的图像区域
- public static func getConcreteCodeRectFromImage(srcCodeImage: UIImage, codeResult: LBXScanResult) -> CGRect {
- guard let corner = codeResult.arrayCorner as? [[String: Float]], corner.count >= 4 else {
- return .zero
- }
- let dicTopLeft = corner[0]
- let dicTopRight = corner[1]
- let dicBottomRight = corner[2]
- let dicBottomLeft = corner[3]
- let xLeftTopRatio = dicTopLeft["X"]!
- let yLeftTopRatio = dicTopLeft["Y"]!
-
- let xRightTopRatio = dicTopRight["X"]!
- let yRightTopRatio = dicTopRight["Y"]!
- let xBottomRightRatio = dicBottomRight["X"]!
- let yBottomRightRatio = dicBottomRight["Y"]!
- let xLeftBottomRatio = dicBottomLeft["X"]!
- let yLeftBottomRatio = dicBottomLeft["Y"]!
- // 由于截图只能矩形,所以截图不规则四边形的最大外围
- let xMinLeft = CGFloat(min(xLeftTopRatio, xLeftBottomRatio))
- let xMaxRight = CGFloat(max(xRightTopRatio, xBottomRightRatio))
- let yMinTop = CGFloat(min(yLeftTopRatio, yRightTopRatio))
- let yMaxBottom = CGFloat(max(yLeftBottomRatio, yBottomRightRatio))
- let imgW = srcCodeImage.size.width
- let imgH = srcCodeImage.size.height
-
- // 宽高反过来计算
- return CGRect(x: xMinLeft * imgH,
- y: yMinTop * imgW,
- width: (xMaxRight - xMinLeft) * imgH,
- height: (yMaxBottom - yMinTop) * imgW)
- }
-
- //MARK: ----图像处理
-
- /**
- @brief 图像中间加logo图片
- @param srcImg 原图像
- @param LogoImage logo图像
- @param logoSize logo图像尺寸
- @return 加Logo的图像
- */
- public static func addImageLogo(srcImg: UIImage, logoImg: UIImage, logoSize: CGSize) -> UIImage {
- UIGraphicsBeginImageContext(srcImg.size)
- srcImg.draw(in: CGRect(x: 0, y: 0, width: srcImg.size.width, height: srcImg.size.height))
- let rect = CGRect(x: srcImg.size.width / 2 - logoSize.width / 2,
- y: srcImg.size.height / 2 - logoSize.height / 2,
- width: logoSize.width,
- height: logoSize.height)
- logoImg.draw(in: rect)
- let resultingImage = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return resultingImage!
- }
-
- //图像缩放
- static func resizeImage(image: UIImage, quality: CGInterpolationQuality, rate: CGFloat) -> UIImage? {
- var resized: UIImage?
- let width = image.size.width * rate
- let height = image.size.height * rate
- UIGraphicsBeginImageContext(CGSize(width: width, height: height))
- let context = UIGraphicsGetCurrentContext()
- context?.interpolationQuality = quality
- image.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
- resized = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return resized
- }
- // 图像裁剪
- static func imageByCroppingWithStyle(srcImg: UIImage, rect: CGRect) -> UIImage? {
- guard let imagePartRef = srcImg.cgImage?.cropping(to: rect) else {
- return nil
- }
- return UIImage(cgImage: imagePartRef)
- }
-
- // 图像旋转
- static func imageRotation(image: UIImage, orientation: UIImage.Orientation) -> UIImage {
- var rotate: Double = 0.0
- var rect: CGRect
- var translateX: CGFloat = 0.0
- var translateY: CGFloat = 0.0
- var scaleX: CGFloat = 1.0
- var scaleY: CGFloat = 1.0
- switch orientation {
- case .left:
- rotate = .pi / 2
- rect = CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width)
- translateX = 0
- translateY = -rect.size.width
- scaleY = rect.size.width / rect.size.height
- scaleX = rect.size.height / rect.size.width
- case .right:
- rotate = 3 * .pi / 2
- rect = CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width)
- translateX = -rect.size.height
- translateY = 0
- scaleY = rect.size.width / rect.size.height
- scaleX = rect.size.height / rect.size.width
- case .down:
- rotate = .pi
- rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
- translateX = -rect.size.width
- translateY = -rect.size.height
- default:
- rotate = 0.0
- rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
- translateX = 0
- translateY = 0
- }
- UIGraphicsBeginImageContext(rect.size)
- let context = UIGraphicsGetCurrentContext()!
- // 做CTM变换
- context.translateBy(x: 0.0, y: rect.size.height)
- context.scaleBy(x: 1.0, y: -1.0)
- context.rotate(by: CGFloat(rotate))
- context.translateBy(x: translateX, y: translateY)
- context.scaleBy(x: scaleX, y: scaleY)
- // 绘制图片
- context.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
- return UIGraphicsGetImageFromCurrentImageContext()!
- }
-
- }
|