Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Copied package
Browse files Browse the repository at this point in the history
  • Loading branch information
felstell committed Jun 17, 2022
0 parents commit a0791ae
Show file tree
Hide file tree
Showing 15 changed files with 966 additions and 0 deletions.
17 changes: 17 additions & 0 deletions CreditCardScanner.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Pod::Spec.new do |s|
s.name = 'CreditCardScanner'
s.version = '0.1.0'
s.summary = 'A library to scan credit card info using the device camera and on-device machine learning'
s.homepage = 'https://github.com/yhkaplan/credit-card-scanner'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' TODO:
s.license = { :type => 'MIT', :file => 'LICENSE.md' }
s.author = { 'Joshua Kaplan' => '[email protected]' }
s.source = { :git => 'https://github.com/yhkaplan/credit-card-scanner.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/yhkaplan'
s.ios.deployment_target = '13.0'
s.swift_versions = ['5.1', '5.2', '5.3']
s.dependency = 'Reg', '~> 0.3.0' # TODO: Actually implement cococapods support
s.dependency = 'Sukar', '~> 0.1.0' # TODO: actually implement Cocoapods support
s.static_framework = true
s.source_files = 'Sources/**/*.{swift,h,m}'
end
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build:
swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios13.0-simulator"

# Injects xcconfig file to get past code-signing requirements
build_example_project:
XCODE_XCCONFIG_FILE="Example/NoCodeSign.xcconfig" xcodebuild -scheme Example
25 changes: 25 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"object": {
"pins": [
{
"package": "Reg",
"repositoryURL": "https://github.com/yhkaplan/Reg.git",
"state": {
"branch": null,
"revision": "c8f1f51bc088708b3b3ecf04523b5bedd6914222",
"version": "0.3.0"
}
},
{
"package": "Sukar",
"repositoryURL": "https://github.com/yhkaplan/Sukar.git",
"state": {
"branch": null,
"revision": "ce0d8cad4df8a5050be8c1ad3e42378eb3b80950",
"version": "0.1.0"
}
}
]
},
"version": 1
}
28 changes: 28 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.2

import PackageDescription

let package = Package(
name: "CreditCardScanner",
platforms: [.iOS(.v11)],
products: [
.library(
name: "CreditCardScanner",
targets: ["CreditCardScanner"]
),
],
dependencies: [
.package(url: "https://github.com/yhkaplan/Reg.git", from: "0.3.0"),
.package(url: "https://github.com/yhkaplan/Sukar.git", from: "0.1.0"),
],
targets: [
.target(
name: "CreditCardScanner",
dependencies: ["Reg", "Sukar"]
),
.testTarget(
name: "CreditCardScannerTests",
dependencies: ["CreditCardScanner"]
),
]
)
6 changes: 6 additions & 0 deletions Package.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Make static
MACH_O_TYPE = staticlib
// Static libs require debug info to be dwarf
DEBUG_INFORMATION_FORMAT = dwarf
// Prob not necessary for this proj, but for finding transitive deps
FRAMEWORK_SEARCH_PATHS = $(inherited) ./Carthage/Build/iOS/**
229 changes: 229 additions & 0 deletions Sources/CreditCardScanner/CameraView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//
// CameraView.swift
// CreditCardScannerPackageDescription
//
// Created by josh on 2020/07/23.
//
#if canImport(UIKit)
#if canImport(AVFoundation)

import AVFoundation
import UIKit
import VideoToolbox

protocol CameraViewDelegate: AnyObject {
func didCapture(image: CGImage)
func didError(with: CreditCardScannerError)
}

@available(iOS 13, *)
final class CameraView: UIView {
weak var delegate: CameraViewDelegate?
private let creditCardFrameStrokeColor: UIColor
private let maskLayerColor: UIColor
private let maskLayerAlpha: CGFloat

// MARK: - Capture related

private let captureSessionQueue = DispatchQueue(
label: "com.yhkaplan.credit-card-scanner.captureSessionQueue"
)

// MARK: - Capture related

private let sampleBufferQueue = DispatchQueue(
label: "com.yhkaplan.credit-card-scanner.sampleBufferQueue"
)

init(
delegate: CameraViewDelegate,
creditCardFrameStrokeColor: UIColor,
maskLayerColor: UIColor,
maskLayerAlpha: CGFloat
) {
self.delegate = delegate
self.creditCardFrameStrokeColor = creditCardFrameStrokeColor
self.maskLayerColor = maskLayerColor
self.maskLayerAlpha = maskLayerAlpha
super.init(frame: .zero)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private let imageRatio: ImageRatio = .vga640x480

// MARK: - Region of interest and text orientation

/// Region of video data output buffer that recognition should be run on.
/// Gets recalculated once the bounds of the preview layer are known.
private var regionOfInterest: CGRect?

var videoPreviewLayer: AVCaptureVideoPreviewLayer {
guard let layer = layer as? AVCaptureVideoPreviewLayer else {
fatalError("Expected `AVCaptureVideoPreviewLayer` type for layer. Check PreviewView.layerClass implementation.")
}

return layer
}

private var videoSession: AVCaptureSession? {
get {
videoPreviewLayer.session
}
set {
videoPreviewLayer.session = newValue
}
}

let semaphore = DispatchSemaphore(value: 1)

override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}

func stopSession() {
videoSession?.stopRunning()
}

func startSession() {
videoSession?.startRunning()
}

func setupCamera() {
captureSessionQueue.async { [weak self] in
self?._setupCamera()
}
}

private func _setupCamera() {
let session = AVCaptureSession()
session.beginConfiguration()
session.sessionPreset = imageRatio.preset

guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .back) else {
delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup))
return
}

do {
let deviceInput = try AVCaptureDeviceInput(device: videoDevice)
session.canAddInput(deviceInput)
session.addInput(deviceInput)
} catch {
delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup, underlyingError: error))
}

let videoOutput = AVCaptureVideoDataOutput()
videoOutput.alwaysDiscardsLateVideoFrames = true
videoOutput.setSampleBufferDelegate(self, queue: sampleBufferQueue)

guard session.canAddOutput(videoOutput) else {
delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup))
return
}

session.addOutput(videoOutput)
session.connections.forEach {
$0.videoOrientation = .portrait
}
session.commitConfiguration()

DispatchQueue.main.async { [weak self] in
self?.videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self?.videoSession = session
self?.startSession()
}
}

func setupRegionOfInterest() {
guard regionOfInterest == nil else { return }
/// Mask layer that covering area around camera view
let backLayer = CALayer()
backLayer.frame = bounds
backLayer.backgroundColor = maskLayerColor.withAlphaComponent(maskLayerAlpha).cgColor

// culcurate cutoutted frame
let cuttedWidth: CGFloat = bounds.width - 40.0
let cuttedHeight: CGFloat = cuttedWidth * CreditCard.heightRatioAgainstWidth

let centerVertical = (bounds.height / 2.0)
let cuttedY: CGFloat = centerVertical - (cuttedHeight / 2.0)
let cuttedX: CGFloat = 20.0

let cuttedRect = CGRect(x: cuttedX,
y: cuttedY,
width: cuttedWidth,
height: cuttedHeight)

let maskLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: cuttedRect, cornerRadius: 10.0)

path.append(UIBezierPath(rect: bounds))
maskLayer.path = path.cgPath
maskLayer.fillRule = .evenOdd
backLayer.mask = maskLayer
layer.addSublayer(backLayer)

let strokeLayer = CAShapeLayer()
strokeLayer.lineWidth = 3.0
strokeLayer.strokeColor = creditCardFrameStrokeColor.cgColor
strokeLayer.path = UIBezierPath(roundedRect: cuttedRect, cornerRadius: 10.0).cgPath
strokeLayer.fillColor = nil
layer.addSublayer(strokeLayer)

let imageHeight: CGFloat = imageRatio.imageHeight
let imageWidth: CGFloat = imageRatio.imageWidth

let acutualImageRatioAgainstVisibleSize = imageWidth / bounds.width
let interestX = cuttedRect.origin.x * acutualImageRatioAgainstVisibleSize
let interestWidth = cuttedRect.width * acutualImageRatioAgainstVisibleSize
let interestHeight = interestWidth * CreditCard.heightRatioAgainstWidth
let interestY = (imageHeight / 2.0) - (interestHeight / 2.0)
regionOfInterest = CGRect(x: interestX,
y: interestY,
width: interestWidth,
height: interestHeight)
}
}

@available(iOS 13, *)
extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
semaphore.wait()
defer { semaphore.signal() }

guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
delegate?.didError(with: CreditCardScannerError(kind: .capture))
delegate = nil
return
}

var cgImage: CGImage?
VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage)

guard let regionOfInterest = regionOfInterest else {
return
}

guard let fullCameraImage = cgImage,
let croppedImage = fullCameraImage.cropping(to: regionOfInterest) else {
delegate?.didError(with: CreditCardScannerError(kind: .capture))
delegate = nil
return
}

delegate?.didCapture(image: croppedImage)
}
}
#endif
#endif

extension CreditCard {
// The aspect ratio of credit-card is Golden-ratio
static let heightRatioAgainstWidth: CGFloat = 0.6180469716
}
18 changes: 18 additions & 0 deletions Sources/CreditCardScanner/CreditCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// CreditCard.swift
//
//
// Created by josh on 2020/07/26.
//

import Foundation

///
public struct CreditCard {
///
public var number: String?
///
public var name: String?
///
public var expireDate: DateComponents?
}
15 changes: 15 additions & 0 deletions Sources/CreditCardScanner/CreditCardScannerError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// CreditCardScannerError.swift
//
//
// Created by josh on 2020/07/26.
//

import Foundation

public struct CreditCardScannerError: LocalizedError {
public enum Kind { case cameraSetup, photoProcessing, authorizationDenied, capture }
public var kind: Kind
public var underlyingError: Error?
public var errorDescription: String? { (underlyingError as? LocalizedError)?.errorDescription }
}
Loading

0 comments on commit a0791ae

Please sign in to comment.