Skip to content

Commit

Permalink
Release version 3.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Team Mobile Schorsch committed Oct 23, 2023
1 parent 92fe96e commit ff41227
Show file tree
Hide file tree
Showing 30 changed files with 903 additions and 109 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ On *iPad* we support portrait and landscape orientations.

## Documentation

Further documentation with installation, integration or customization guides can be found in our [website](https://developer.gini.net/gini-mobile-ios/GiniCaptureSDK).
Further documentation with installation, integration or customization guides can be found in our [website](https://gini.atlassian.net/wiki/spaces/ICSV/overview).

## Requirements

Expand All @@ -40,6 +40,6 @@ Gini GmbH, [email protected]

## License

The Gini Capture SDK for iOS is licensed under a Private License. See [the license](https://developer.gini.net/gini-mobile-ios/GiniCaptureSDK/license.html) for more info.
The Gini Capture SDK for iOS is licensed under a Private License. See [the license](https://gini.atlassian.net/wiki/spaces/ICSV/pages/13074449/License) for more info.

**Important:** Always make sure to ship all license notices and permissions with your application.
18 changes: 18 additions & 0 deletions Sources/GiniCaptureSDK/Core/Extensions/NSRegularExpression.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// NSRegularExpression.swift
//
//
// Copyright © 2023 Gini GmbH. All rights reserved.
//

import Foundation

extension NSRegularExpression {
convenience init(_ pattern: String, options: NSRegularExpression.Options = []) {
do {
try self.init(pattern: pattern, options: options)
} catch {
preconditionFailure("Illegal regular expression pattern: \(pattern).")
}
}
}
8 changes: 8 additions & 0 deletions Sources/GiniCaptureSDK/Core/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ extension String {
return String(format: resource.localizedFormat, arguments: args)
}
}

func split(every length: Int, by separator: String = " ") -> String {
guard length > 0 && length < count else { return self }

return (0 ... (count - 1) / length).map {
dropFirst($0 * length).prefix(length)
}.joined(separator: separator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// UIActivityIndicatorView.swift
//
//
// Created by Valentina Iancu on 19.10.23.
//

import UIKit

extension UIActivityIndicatorView {
func applyLargeStyle() {
if #available(iOS 13.0, *) {
self.style = .large
} else {
self.style = .whiteLarge
}
}
}
136 changes: 129 additions & 7 deletions Sources/GiniCaptureSDK/Core/Screens/Camera/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import UIKit
import AVFoundation
import Photos
import Vision

protocol CameraProtocol: AnyObject {
var session: AVCaptureSession { get }
var videoDeviceInput: AVCaptureDeviceInput? { get }
var didDetectQR: ((GiniQRCodeDocument) -> Void)? { get set }
var didDetectInvalidQR: ((GiniQRCodeDocument) -> Void)? { get set }
var didDetectIBANs: (([String]) -> Void)? { get set }
var isFlashSupported: Bool { get }
var isFlashOn: Bool { get set }

Expand All @@ -28,6 +30,7 @@ protocol CameraProtocol: AnyObject {
func setupQRScanningOutput(completion: @escaping ((CameraError?) -> Void))
func start()
func stop()
func startOCR()
}

final class Camera: NSObject, CameraProtocol {
Expand All @@ -36,13 +39,17 @@ final class Camera: NSObject, CameraProtocol {
var didDetectQR: ((GiniQRCodeDocument) -> Void)?
var didDetectInvalidQR: ((GiniQRCodeDocument) -> Void)?
var didCaptureImageHandler: ((Data?, CameraError?) -> Void)?
var didDetectIBANs: (([String]) -> Void)?

// Session management
var giniConfiguration: GiniConfiguration
var isFlashOn: Bool
var photoOutput: AVCapturePhotoOutput?
var session: AVCaptureSession = AVCaptureSession()
let sessionQueue = DispatchQueue(label: "session queue")
var videoDeviceInput: AVCaptureDeviceInput?
var videoDataOutput = AVCaptureVideoDataOutput()
let videoDataOutputQueue = DispatchQueue(label: "ocr queue")

lazy var isFlashSupported: Bool = {
#if targetEnvironment(simulator)
Expand All @@ -56,7 +63,10 @@ final class Camera: NSObject, CameraProtocol {
}()

fileprivate let application: UIApplication
fileprivate let sessionQueue = DispatchQueue(label: "session queue")

private var request: VNImageBasedRequest?

private var textOrientation = CGImagePropertyOrientation.up

init(application: UIApplication = UIApplication.shared,
giniConfiguration: GiniConfiguration) {
Expand All @@ -67,19 +77,28 @@ final class Camera: NSObject, CameraProtocol {
}

fileprivate func configureSession(completion: @escaping ((CameraError?) -> Void)) {
self.session.beginConfiguration()
self.setupInput()
self.setupPhotoCaptureOutput()
self.session.commitConfiguration()
session.beginConfiguration()
setupInput()
setupPhotoCaptureOutput()
configureVideoDataOutput()
session.commitConfiguration()
if giniConfiguration.qrCodeScanningEnabled {
self.setupQRScanningOutput(completion: completion)
setupQRScanningOutput(completion: completion)
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}

func startOCR() {
if #available(iOS 13.0, *) {
request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
} else {
Log(message: "IBAN detection is not supported for iOS 12 or older", event: .warning)
}
}

func switchTo(newVideoDevice: AVCaptureDevice) {
guard let videoInput = videoDeviceInput else { return }

Expand Down Expand Up @@ -108,16 +127,41 @@ final class Camera: NSObject, CameraProtocol {
}
}

func setup(completion: @escaping ((CameraError?) -> Void)) {
private func applyZoomForSmallText(captureDevice: AVCaptureDevice) {
// Set zoom and autofocus to help focus on very small text.
do {
try captureDevice.lockForConfiguration()
captureDevice.videoZoomFactor = 2
captureDevice.autoFocusRangeRestriction = .near
captureDevice.unlockForConfiguration()
} catch {
Log(message: "Could not lock device for configuration", event: .error)
return
}
}

func setup(completion: @escaping ((CameraError?) -> Void)) {
// Set up vision request before letting ViewController set up the camera
// so that it exists when the first buffer is received.
setupCaptureDevice { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let cameraError):
completion(cameraError)
case .success(let captureDevice):
// NOTE:
// Requesting 4k buffers allows recognition of smaller text but will
// consume more power. Use the smallest buffer size necessary to keep
// down battery usage.
if captureDevice.supportsSessionPreset(.hd4K3840x2160) {
self.session.sessionPreset = AVCaptureSession.Preset.hd4K3840x2160
} else {
self.session.sessionPreset = AVCaptureSession.Preset.hd1920x1080
}

do {
self.videoDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
self.applyZoomForSmallText(captureDevice: captureDevice)
} catch {
completion(.notAuthorizedToUseDevice) // shouldn't happen
}
Expand Down Expand Up @@ -294,6 +338,55 @@ fileprivate extension Camera {
session.addOutput(output)
photoOutput = output
}

func configureVideoDataOutput() {
// Configure video data output.
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
if !session.canAddOutput(videoDataOutput) {
for output in session.outputs {
session.removeOutput(output)
}
}
session.addOutput(videoDataOutput)
}

// MARK: - Text recognition

// Vision recognition handler.
func recognizeTextHandler(request: VNRequest, error: Error?) {

if #available(iOS 13.0, *) {
guard let results = request.results as? [VNRecognizedTextObservation] else {
return
}

var ibans = Set<String>()
let maximumCandidates = 1

for visionResult in results {
// topCandidates return no more than N but can be less than N candidates. The maximum number of candidates returned cannot exceed 10 candidates.
guard let candidate = visionResult.topCandidates(maximumCandidates).first else { continue }

for result in extractIBANS(string: candidate.string) {
ibans.insert(result)
}
}

// Found a definite number.
// Stop the camera synchronously to ensure that no further buffers are
// received. Then update the number view asynchronously.
sessionQueue.sync {
DispatchQueue.main.async {
self.didDetectIBANs!(Array(ibans))
}
}

} else {
Log(message: "IBAN detection is not supported for iOS 12 or older", event: .warning)
}
}
}

// MARK: - AVCaptureMetadataOutputObjectsDelegate
Expand Down Expand Up @@ -339,3 +432,32 @@ extension Camera: AVCapturePhotoCaptureDelegate {
}

}

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate

extension Camera: AVCaptureVideoDataOutputSampleBufferDelegate {

func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
if #available(iOS 13.0, *) {
guard let request = request as? VNRecognizeTextRequest else { return }
if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
// Configure for running in real-time.
request.recognitionLevel = .accurate
request.usesLanguageCorrection = false
let requestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
orientation: textOrientation,
options: [:])
do {
try requestHandler.perform([request])
} catch {
Log(message: "Could not perform ocr request", event: .error)
return
}
}
} else {
Log(message: "IBAN detection is not supported for iOS 12 or older", event: .warning)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit

// MARK: - Toggle UI elements

extension Camera2ViewController {
extension CameraViewController {

/**
Show the capture button. Should be called when onboarding is dismissed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import UIKit

// MARK: - Image Cropping

extension Camera2ViewController {
extension CameraViewController {
// swiftlint:disable line_length
func crop(image: UIImage) -> UIImage {
let standardImageAspectRatio: CGFloat = 0.75 // Standard aspect ratio of a 3/4 image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit

// MARK: - Document import

extension Camera2ViewController {
extension CameraViewController {

@objc func showImportFileSheet() {
let alertViewController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
Expand Down
Loading

0 comments on commit ff41227

Please sign in to comment.