From 255195d1b4d51027f9b7f4fae4c7960919204e4a Mon Sep 17 00:00:00 2001 From: Haik Aslanyan Date: Wed, 6 May 2020 18:56:59 +0400 Subject: [PATCH] modified scanning area logic to support non centered images --- Sources/Internal/RCConstants.swift | 2 +- Sources/Internal/RCImageDecoder.swift | 127 ++++++++++---------- Sources/Public/RCCameraViewController.swift | 8 -- Sources/Public/RCCoder.swift | 19 ++- 4 files changed, 76 insertions(+), 80 deletions(-) diff --git a/Sources/Internal/RCConstants.swift b/Sources/Internal/RCConstants.swift index 18a8271..524f8ad 100644 --- a/Sources/Internal/RCConstants.swift +++ b/Sources/Internal/RCConstants.swift @@ -28,5 +28,5 @@ struct RCConstants { static let dotSizeScale: CGFloat = 0.08 static let dotPatterns: [CGFloat] = [6, 4, 2] static let dotPointRange = (Float(1.3)...Float(2.5)) - static let pixelThreshold = (0...180) + static let pixelThreshold = (UInt8(0)...UInt8(180)) } diff --git a/Sources/Internal/RCImageDecoder.swift b/Sources/Internal/RCImageDecoder.swift index 5a743f7..31ae3c5 100644 --- a/Sources/Internal/RCImageDecoder.swift +++ b/Sources/Internal/RCImageDecoder.swift @@ -27,37 +27,17 @@ struct RCImageDecoder { internal let configuration: RCCoderConfiguration internal var size = 720 internal var bytesPerRow = 720 - internal var padding = 0 - private var sectionSize: Int { - self.size / 5 - } } extension RCImageDecoder { func process(pointer: UnsafeMutablePointer) throws -> [RCBit] { let bufferData = UnsafeMutableBufferPointer(start: pointer, count: size * bytesPerRow) let data = PixelContainer(rows: bytesPerRow, items: bufferData) - var points = [CGPoint]() - for side in Side.allCases { - switch side { - case .left: - let point = try scanControlPoint(for: data, region: (padding, (size - sectionSize) / 2), side: side) - points.append(point) - case .top: - let point = try scanControlPoint(for: data, region: ((size - sectionSize) / 2, padding), side: side) - points.append(point) - case .right: - let point = try scanControlPoint(for: data, region: (size - sectionSize - padding, (size - sectionSize) / 2), side: side) - points.append(point) - case .bottom: - let point = try scanControlPoint(for: data, region: ((size - sectionSize) / 2, size - sectionSize - padding), side: side) - points.append(point) - } - } + let points = try scanControlPoints(for: data) let transform = calculateTransform(from: points) let mapper = RCPointMapper(transform: transform, size: size) let locations = mapper.map(points: calculateBitLocations()) - let bits = locations.map { RCConstants.pixelThreshold.contains(Int(data[Int($0.x), Int($0.y)])) ? RCBit.one : RCBit.zero } + let bits = locations.map { RCConstants.pixelThreshold.contains(data[Int($0.x), Int($0.y)]) ? RCBit.one : RCBit.zero } return bits } @@ -72,47 +52,67 @@ extension RCImageDecoder { } extension RCImageDecoder { - private func scanControlPoint(for data: PixelContainer, region: (x: Int, y: Int), side: Side) throws -> CGPoint { - - func scan(region: (x: Int, y: Int, size: Int), data: PixelContainer, coordinate: (Int) -> (x: Int, y: Int), comparison: (PixelPattern, (x: Int, y: Int)) -> Bool) -> [PixelPattern] { - var lastPattern = PixelPattern(bit: RCConstants.pixelThreshold.contains(Int(data[region.x, region.y])) ? RCBit.one : RCBit.zero, x: region.x, y: region.y, count: 0) - var pixelPatterns = [lastPattern] - var count = 0 - let maxSize = region.size * region.size - while count < maxSize { - let coordinate = coordinate(count) - let bit = RCConstants.pixelThreshold.contains(Int(data[coordinate.x, coordinate.y])) ? RCBit.one : RCBit.zero - if comparison(lastPattern, coordinate), lastPattern.bit == bit { - lastPattern.count += 1 - pixelPatterns[pixelPatterns.count - 1] = lastPattern - } else { - lastPattern = PixelPattern(bit: bit, x: coordinate.x, y: coordinate.y, count: 1) - pixelPatterns.append(lastPattern) - } - count += 1 + private func scanControlPoints(for data: PixelContainer) throws -> [CGPoint] { + var horizontalPatterns = [PixelPattern]() + var verticalPatterns = [PixelPattern]() + var points = [CGPoint]() + for side in Side.allCases { + switch side { + case .left: + horizontalPatterns = scanPixelPattern(for: .horizontal, data: data) + points.append(try controlPoint(for: horizontalPatterns, side: side)) + case .right: + points.append(try controlPoint(for: horizontalPatterns, side: side)) + case .top: + verticalPatterns = scanPixelPattern(for: .vertical, data: data) + points.append(try controlPoint(for: verticalPatterns, side: side)) + case .bottom: + points.append(try controlPoint(for: verticalPatterns, side: side)) } - return pixelPatterns } - - let pixelPatterns: [PixelPattern] - switch side { - case .left, .right: - pixelPatterns = scan(region: (region.x, region.y, sectionSize), data: data, coordinate: { count in - let x = count % sectionSize + region.x - let y = count / sectionSize + region.y - return (x, y) - }, comparison: { (pattern, coordinate) in - return pattern.y == coordinate.y - }) - case .top, .bottom: - pixelPatterns = scan(region: (region.x, region.y, sectionSize), data: data, coordinate: { count in - let x = count / sectionSize + region.x - let y = count % sectionSize + region.y - return (x, y) - }, comparison: { (pattern, coordinate) in - return pattern.x == coordinate.x - }) + return points + } + + + private func scanPixelPattern(for mode: ScanMode, data: PixelContainer) -> [PixelPattern] { + var lastPattern = PixelPattern.init(bit: RCConstants.pixelThreshold.contains((data[0, 0])) ? RCBit.one : RCBit.zero, x: 0, y: 0, count: 0) + var pixelPatterns = [lastPattern] + var count = 0 + let maxSize = size * size + switch mode { + case .horizontal: + while count < maxSize { + let x = count % size + let y = count / size + let bit = RCConstants.pixelThreshold.contains(data[x, y]) ? RCBit.one : RCBit.zero + if lastPattern.y == y, lastPattern.bit == bit { + lastPattern.count += 1 + pixelPatterns[pixelPatterns.count - 1] = lastPattern + } else { + lastPattern = PixelPattern(bit: bit, x: x, y: y, count: 1) + pixelPatterns.append(lastPattern) + } + count += 1 + } + case .vertical: + while count < maxSize { + let x = count / size + let y = count % size + let bit = RCConstants.pixelThreshold.contains(data[x, y]) ? RCBit.one : RCBit.zero + if lastPattern.x == x, lastPattern.bit == bit { + lastPattern.count += 1 + pixelPatterns[pixelPatterns.count - 1] = lastPattern + } else { + lastPattern = PixelPattern(bit: bit, x: x, y: y, count: 1) + pixelPatterns.append(lastPattern) + } + count += 1 + } } + return pixelPatterns + } + + private func controlPoint(for pixelPatterns: [PixelPattern], side: Side) throws -> CGPoint { let controlPoints = try pixelPatterns.withUnsafeBufferPointer { (pixelPatternsBuffer) -> [CGPoint] in var points = [CGPoint]() guard pixelPatternsBuffer.count >= 5 else { throw RCError.decoding } @@ -164,7 +164,7 @@ extension RCImageDecoder { } }.first! } - + private func calculateTransform(from points: [CGPoint]) -> CATransform3D { let perspective = RCTransformation(points) let middleRect = CGRect(origin: .zero, size: CGSize(width: CGFloat(size), height: CGFloat(size))) @@ -215,6 +215,11 @@ extension RCImageDecoder { case right } + enum ScanMode { + case horizontal + case vertical + } + final class PixelContainer { let rows: Int var columns: Int { data.count / rows } diff --git a/Sources/Public/RCCameraViewController.swift b/Sources/Public/RCCameraViewController.swift index c485af1..ab6a57f 100644 --- a/Sources/Public/RCCameraViewController.swift +++ b/Sources/Public/RCCameraViewController.swift @@ -113,7 +113,6 @@ public extension RCCameraViewController { super.viewDidLayoutSubviews() configureMaskLayer() videoPreviewLayer?.frame = view.bounds - calculateScanArea() } } @@ -157,7 +156,6 @@ extension RCCameraViewController { guard let captureDevice = AVCaptureDevice.default(for: .video) else { return } do { captureSession.sessionPreset = .hd1280x720 - calculateScanArea() let input = try AVCaptureDeviceInput(device: captureDevice) input.device.activeVideoMaxFrameDuration = CMTimeMake(value: 1, timescale: 30) captureSession.addInput(input) @@ -170,12 +168,6 @@ extension RCCameraViewController { } } - private func calculateScanArea() { - let actualWidth = view.frame.height / 16 * 9 - let sideArea = (actualWidth - view.frame.width * 0.9) / 2 - coder.imageDecoder.padding = Int(sideArea / actualWidth * 720) - } - private func configureVideoPreview(orientation: AVCaptureVideoOrientation = .portrait) { videoPreviewLayer?.removeFromSuperlayer() videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) diff --git a/Sources/Public/RCCoder.swift b/Sources/Public/RCCoder.swift index 6579d3f..6eaeb70 100644 --- a/Sources/Public/RCCoder.swift +++ b/Sources/Public/RCCoder.swift @@ -44,7 +44,6 @@ public extension RCCoder { func decode(_ image: UIImage) throws -> String { guard image.size.width == image.size.height else { throw RCError.wrongImageSize } imageDecoder.size = image.cgImage!.height - imageDecoder.padding = 0 imageDecoder.bytesPerRow = image.cgImage!.height let bits = try imageDecoder.decode(image) let message = try bitCoder.decode(bits) @@ -55,15 +54,15 @@ public extension RCCoder { configuration.validate(text) } - func validateForBlackBackground(colors: [UIColor]) -> Bool { - colors.allSatisfy { color in - var white: CGFloat = 0 - var alpha: CGFloat = 0 - guard color.getWhite(&white, alpha: &alpha) else { return false } - guard alpha == 1.0 else { return false } - return RCConstants.pixelThreshold.contains(Int(white * 255)) - } - } +// func validateForBlackBackground(colors: [UIColor]) -> Bool { +// colors.allSatisfy { color in +// var white: CGFloat = 0 +// var alpha: CGFloat = 0 +// guard color.getWhite(&white, alpha: &alpha) else { return false } +// guard alpha == 1.0 else { return false } +// return RCConstants.pixelThreshold.contains(Int(white * 255)) +// } +// } } extension RCCoder {