Skip to content

Commit 7da6b1e

Browse files
authored
Merge pull request #127 from karolbielski/bugfix/issue-124-113-89
[#89] [#113] [#124] Focus improvements on dual and tripple cameras
2 parents bf5d708 + a9dc96a commit 7da6b1e

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ You can provide a variety of extra customization options to `CodeScannerView` in
3535
- `showViewfinder` determines whether to show a box-like viewfinder over the UI. Default: false.
3636
- `simulatedData` allows you to provide some test data to use in Simulator, when real scanning isn’t available. Default: an empty string.
3737
- `shouldVibrateOnSuccess` allows you to determine whether device should vibrate when a code is found. Default: true.
38+
- `videoCaptureDevice` allows you to choose different capture device that is most suitable for code to scan.
3839

3940
If you want to add UI customization, such as a dedicated Cancel button, you should wrap your `CodeScannerView` instance in a `NavigationView` and use a `toolbar()` modifier to add whatever buttons you want.
4041

@@ -87,6 +88,24 @@ struct QRCodeScannerExampleView: View {
8788
}
8889
```
8990

91+
## Scanning small QR codes
92+
93+
Scanning small QR code on devices with dual or tripple cameras has to be adjusted because of minimum focus distance built in these cameras.
94+
To have the best possible focus on the code we scan it is needed to choose the most suitable camera and apply recommended zoom factor.
95+
96+
Example for scanning 20x20mm QR codes.
97+
98+
```swift
99+
CodeScannerView(codeTypes: [.qr], videoCaptureDevice: AVCaptureDevice.zoomedCameraForQRCode(withMinimumCodeSize: 20)) { response in
100+
switch response {
101+
case .success(let result):
102+
print("Found code: \(result.string)")
103+
case .failure(let error):
104+
print(error.localizedDescription)
105+
}
106+
}
107+
```
108+
90109

91110
## Credits
92111

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// AVCaptureDevice+bestForBuiltInCamera.swift
3+
// https://github.com/twostraws/CodeScanner
4+
//
5+
// Created by Karol Bielski on 10/02/2024.
6+
// Copyright © 2024 Paul Hudson. All rights reserved.
7+
//
8+
9+
import AVFoundation
10+
11+
@available(macCatalyst 14.0, *)
12+
extension AVCaptureDevice {
13+
14+
/// Returns best built in back camera for scanning QR codes zoomed for a given minimum code size.
15+
static func zoomedCameraForQRCode(withMinimumCodeSize minimumCodeSize: Float = 20) -> AVCaptureDevice? {
16+
let captureDevice = AVCaptureDevice.DiscoverySession(
17+
deviceTypes: [.builtInWideAngleCamera],
18+
mediaType: .video,
19+
position: .back
20+
).devices.first ?? AVCaptureDevice.default(for: .video)
21+
22+
if #available(iOS 15.0, *) {
23+
captureDevice?.setRecommendedZoomFactor(forMinimumCodeSize: minimumCodeSize)
24+
}
25+
26+
return captureDevice
27+
}
28+
29+
/// Sets recommended zoom factor for a given minimum code size.
30+
@available(iOS 15.0, *)
31+
func setRecommendedZoomFactor(forMinimumCodeSize minimumCodeSize: Float) {
32+
/*
33+
Optimize the user experience for scanning QR codes down to given size.
34+
When scanning a QR code of that size, the user may need to get closer than
35+
the camera's minimum focus distance to fill the rect of interest.
36+
To have the QR code both fill the rect and still be in focus, we may need to apply some zoom.
37+
*/
38+
let deviceMinimumFocusDistance = Float(minimumFocusDistance)
39+
guard deviceMinimumFocusDistance != -1 else { return }
40+
41+
let deviceFieldOfView = activeFormat.videoFieldOfView
42+
let formatDimensions = CMVideoFormatDescriptionGetDimensions(activeFormat.formatDescription)
43+
let rectOfInterestWidth = Double(formatDimensions.height) / Double(formatDimensions.width)
44+
let minimumSubjectDistanceForCode = minimumSubjectDistanceForCode(
45+
fieldOfView: deviceFieldOfView,
46+
minimumCodeSize: minimumCodeSize,
47+
previewFillPercentage: Float(rectOfInterestWidth)
48+
)
49+
50+
guard minimumSubjectDistanceForCode < deviceMinimumFocusDistance else { return }
51+
52+
let zoomFactor = deviceMinimumFocusDistance / minimumSubjectDistanceForCode
53+
do {
54+
try lockForConfiguration()
55+
videoZoomFactor = CGFloat(zoomFactor)
56+
unlockForConfiguration()
57+
} catch {
58+
print("Could not lock for configuration: \(error)")
59+
}
60+
}
61+
62+
private func minimumSubjectDistanceForCode(
63+
fieldOfView: Float,
64+
minimumCodeSize: Float,
65+
previewFillPercentage: Float
66+
) -> Float {
67+
/*
68+
Given the camera horizontal field of view, we can compute the distance (mm) to make a code
69+
of minimumCodeSize (mm) fill the previewFillPercentage.
70+
*/
71+
let radians = (fieldOfView / 2).radians
72+
let filledCodeSize = minimumCodeSize / previewFillPercentage
73+
return filledCodeSize / tan(radians)
74+
}
75+
}
76+
77+
private extension Float {
78+
var radians: Float {
79+
self * Float.pi / 180
80+
}
81+
}
82+
83+
/*
84+
Part of this code is copied from Apple sample project "AVCamBarcode: Using AVFoundation to capture barcodes".
85+
86+
IMPORTANT: This Apple software is supplied to you by Apple
87+
Inc. ("Apple") in consideration of your agreement to the following
88+
terms, and your use, installation, modification or redistribution of
89+
this Apple software constitutes acceptance of these terms. If you do
90+
not agree with these terms, please do not use, install, modify or
91+
redistribute this Apple software.
92+
93+
In consideration of your agreement to abide by the following terms, and
94+
subject to these terms, Apple grants you a personal, non-exclusive
95+
license, under Apple's copyrights in this original Apple software (the
96+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
97+
Software, with or without modifications, in source and/or binary forms;
98+
provided that if you redistribute the Apple Software in its entirety and
99+
without modifications, you must retain this notice and the following
100+
text and disclaimers in all such redistributions of the Apple Software.
101+
Neither the name, trademarks, service marks or logos of Apple Inc. may
102+
be used to endorse or promote products derived from the Apple Software
103+
without specific prior written permission from Apple. Except as
104+
expressly stated in this notice, no other rights or licenses, express or
105+
implied, are granted by Apple herein, including but not limited to any
106+
patent rights that may be infringed by your derivative works or by other
107+
works in which the Apple Software may be incorporated.
108+
109+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
110+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
111+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
112+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
113+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
114+
115+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
116+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
117+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
118+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
119+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
120+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
121+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
122+
POSSIBILITY OF SUCH DAMAGE.
123+
124+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
125+
*/

0 commit comments

Comments
 (0)