capacitor-plugin-camera-forked 3.0.96 → 3.0.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -7
- package/android/src/main/java/com/tonyxlh/capacitor/camera/CameraPreviewPlugin.java +58 -0
- package/dist/docs.json +5 -5
- package/dist/esm/definitions.d.ts +3 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +65 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +65 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +65 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/CameraPreviewPlugin.swift +247 -72
- package/package.json +1 -1
|
@@ -33,9 +33,6 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
33
33
|
private let focusThrottleInterval: TimeInterval = 0.5
|
|
34
34
|
var currentCameraDevice: AVCaptureDevice?
|
|
35
35
|
|
|
36
|
-
// Simple focus management with fixed delay
|
|
37
|
-
var continuousFocusReturnTimer: Timer?
|
|
38
|
-
|
|
39
36
|
// Store the desired JPEG quality, set during initialization
|
|
40
37
|
var desiredJpegQuality: CGFloat = 0.95 // Default to high quality (0.0-1.0)
|
|
41
38
|
|
|
@@ -379,21 +376,31 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
379
376
|
|
|
380
377
|
device.unlockForConfiguration()
|
|
381
378
|
|
|
379
|
+
if device.isAdjustingFocus {
|
|
382
380
|
// Wait longer for focus to settle, especially for close objects
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
381
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
382
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
383
|
+
|
|
384
|
+
// Restore previous settings after capture
|
|
385
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
|
386
|
+
do {
|
|
387
|
+
try device.lockForConfiguration()
|
|
388
|
+
device.focusMode = previousFocusMode
|
|
389
|
+
device.exposureMode = previousExposureMode
|
|
390
|
+
device.unlockForConfiguration()
|
|
391
|
+
|
|
392
|
+
// Focus to center after settings restoration to ensure optimal focus state
|
|
393
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
394
|
+
self.focusToCenterSafely()
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
print("Could not restore camera settings: \(error)")
|
|
398
|
+
}
|
|
395
399
|
}
|
|
396
400
|
}
|
|
401
|
+
} else {
|
|
402
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
403
|
+
|
|
397
404
|
}
|
|
398
405
|
} catch {
|
|
399
406
|
// If focus configuration fails, capture anyway
|
|
@@ -425,6 +432,10 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
425
432
|
let image = UIImage(data: imageData)
|
|
426
433
|
let base64 = getBase64FromImage(image: image!, quality: desiredJpegQuality)
|
|
427
434
|
ret["base64"] = base64
|
|
435
|
+
|
|
436
|
+
// Detect blur if base64 is included
|
|
437
|
+
let blurScore = calculateBlurScore(image: image!)
|
|
438
|
+
ret["blurScore"] = blurScore
|
|
428
439
|
}
|
|
429
440
|
do {
|
|
430
441
|
|
|
@@ -435,6 +446,11 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
435
446
|
}
|
|
436
447
|
takePhotoCall.resolve(ret)
|
|
437
448
|
takePhotoCall = nil
|
|
449
|
+
|
|
450
|
+
// Focus to center after photo capture to ensure optimal focus state for next operation
|
|
451
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
452
|
+
self.focusToCenterSafely()
|
|
453
|
+
}
|
|
438
454
|
}
|
|
439
455
|
}
|
|
440
456
|
}
|
|
@@ -480,10 +496,24 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
480
496
|
let qualityValue = takeSnapshotCall.getFloat("quality") ?? 100.0
|
|
481
497
|
let quality = CGFloat(qualityValue / 100.0) // Convert percentage to 0.0-1.0 range
|
|
482
498
|
let base64 = getBase64FromImage(image: normalized, quality: quality);
|
|
499
|
+
|
|
483
500
|
var ret = PluginCallResultData()
|
|
484
501
|
ret["base64"] = base64
|
|
502
|
+
|
|
503
|
+
// Only detect blur if checkBlur option is true
|
|
504
|
+
let shouldCheckBlur = takeSnapshotCall.getBool("checkBlur", false)
|
|
505
|
+
if shouldCheckBlur {
|
|
506
|
+
let blurScore = calculateBlurScore(image: normalized)
|
|
507
|
+
ret["blurScore"] = blurScore
|
|
508
|
+
}
|
|
509
|
+
|
|
485
510
|
takeSnapshotCall.resolve(ret)
|
|
486
511
|
takeSnapshotCall = nil
|
|
512
|
+
|
|
513
|
+
// Focus to center after snapshot to ensure optimal focus state for next operation
|
|
514
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
515
|
+
self.focusToCenterSafely()
|
|
516
|
+
}
|
|
487
517
|
}
|
|
488
518
|
if saveFrameCall != nil {
|
|
489
519
|
CameraPreviewPlugin.frameTaken = normalized
|
|
@@ -491,6 +521,11 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
491
521
|
ret["success"] = true
|
|
492
522
|
saveFrameCall.resolve(ret)
|
|
493
523
|
saveFrameCall = nil
|
|
524
|
+
|
|
525
|
+
// Focus to center after frame save to ensure optimal focus state for next operation
|
|
526
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
527
|
+
self.focusToCenterSafely()
|
|
528
|
+
}
|
|
494
529
|
}
|
|
495
530
|
}
|
|
496
531
|
}
|
|
@@ -866,6 +901,12 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
866
901
|
factor = min(factor, device.maxAvailableVideoZoomFactor)
|
|
867
902
|
device.videoZoomFactor = factor
|
|
868
903
|
device.unlockForConfiguration()
|
|
904
|
+
|
|
905
|
+
// Trigger focus to center after zoom change
|
|
906
|
+
// if device.isFocusModeSupported(.autoFocus) {
|
|
907
|
+
// let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
908
|
+
// focusWithPoint(point: centerPoint)
|
|
909
|
+
// }
|
|
869
910
|
} catch {
|
|
870
911
|
print("Zoom could not be used")
|
|
871
912
|
}
|
|
@@ -877,17 +918,51 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
877
918
|
x >= 0.0 && x <= 1.0, y >= 0.0 && y <= 1.0 {
|
|
878
919
|
let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
|
879
920
|
|
|
880
|
-
//
|
|
921
|
+
// Check if focus is currently animating and reset if stuck
|
|
922
|
+
if isFocusAnimating {
|
|
923
|
+
resetFocusIfStuck()
|
|
924
|
+
}
|
|
925
|
+
|
|
881
926
|
focusWithPoint(point: point)
|
|
882
927
|
|
|
928
|
+
// Calculate the point in the preview layer's coordinate space
|
|
929
|
+
//let previewPoint = CGPoint(x: point.x * previewView.bounds.width,
|
|
930
|
+
// y: point.y * previewView.bounds.height)
|
|
931
|
+
// showFocusView(at: previewPoint)
|
|
883
932
|
call.resolve()
|
|
884
933
|
} else {
|
|
885
934
|
call.reject("Invalid coordinates. Provide normalized x,y values (0.0-1.0)")
|
|
886
935
|
}
|
|
887
936
|
}
|
|
888
937
|
|
|
938
|
+
private func resetFocusIfStuck() {
|
|
939
|
+
DispatchQueue.main.async { [weak self] in
|
|
940
|
+
guard let self = self else { return }
|
|
941
|
+
|
|
942
|
+
// Remove any existing focus indicator
|
|
943
|
+
self.focusView?.removeFromSuperview()
|
|
944
|
+
self.focusCompletionTimer?.invalidate()
|
|
945
|
+
self.isFocusAnimating = false
|
|
946
|
+
|
|
947
|
+
// Reset focus to continuous mode
|
|
948
|
+
guard let videoInput = self.videoInput else { return }
|
|
949
|
+
let device = videoInput.device
|
|
950
|
+
do {
|
|
951
|
+
try device.lockForConfiguration()
|
|
952
|
+
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
953
|
+
device.focusMode = .continuousAutoFocus
|
|
954
|
+
}
|
|
955
|
+
device.unlockForConfiguration()
|
|
956
|
+
} catch {
|
|
957
|
+
print("Could not reset focus: \(error)")
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
889
962
|
@objc func resetFocus(_ call: CAPPluginCall) {
|
|
890
|
-
|
|
963
|
+
resetFocusIfStuck()
|
|
964
|
+
|
|
965
|
+
// Reset to center focus
|
|
891
966
|
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
892
967
|
focusWithPoint(point: centerPoint)
|
|
893
968
|
|
|
@@ -899,57 +974,103 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
899
974
|
let convertedPoint = self.previewView.videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: location)
|
|
900
975
|
|
|
901
976
|
focusWithPoint(point: convertedPoint)
|
|
977
|
+
// showFocusView(at: location)
|
|
902
978
|
}
|
|
903
979
|
|
|
904
980
|
func focusWithPoint(point: CGPoint) {
|
|
905
981
|
guard let videoInput = self.videoInput else { return }
|
|
906
982
|
let device = videoInput.device
|
|
907
983
|
|
|
984
|
+
let now = Date()
|
|
985
|
+
if now.timeIntervalSince(lastFocusTime) < focusThrottleInterval {
|
|
986
|
+
return
|
|
987
|
+
}
|
|
988
|
+
lastFocusTime = now
|
|
989
|
+
|
|
908
990
|
do {
|
|
909
991
|
try device.lockForConfiguration()
|
|
910
992
|
|
|
911
|
-
|
|
912
|
-
|
|
993
|
+
focusCompletionTimer?.invalidate()
|
|
994
|
+
|
|
995
|
+
if device.isFocusPointOfInterestSupported {
|
|
913
996
|
device.focusPointOfInterest = point
|
|
914
|
-
|
|
997
|
+
|
|
998
|
+
// Use autoFocus for more aggressive focusing on specific points
|
|
999
|
+
if device.isFocusModeSupported(.autoFocus) {
|
|
1000
|
+
device.focusMode = .autoFocus
|
|
1001
|
+
} else if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
1002
|
+
device.focusMode = .continuousAutoFocus
|
|
1003
|
+
}
|
|
915
1004
|
}
|
|
916
1005
|
|
|
917
|
-
if device.
|
|
1006
|
+
if device.isExposurePointOfInterestSupported {
|
|
918
1007
|
device.exposurePointOfInterest = point
|
|
919
|
-
|
|
1008
|
+
|
|
1009
|
+
// Use autoExpose for specific point exposure
|
|
1010
|
+
if device.isExposureModeSupported(.autoExpose) {
|
|
1011
|
+
device.exposureMode = .autoExpose
|
|
1012
|
+
} else if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
1013
|
+
device.exposureMode = .continuousAutoExposure
|
|
1014
|
+
}
|
|
920
1015
|
}
|
|
921
1016
|
|
|
922
|
-
// Ensure full focus range is available
|
|
1017
|
+
// Ensure full focus range is available for close objects
|
|
923
1018
|
if device.isAutoFocusRangeRestrictionSupported {
|
|
924
1019
|
device.autoFocusRangeRestriction = .none
|
|
925
1020
|
}
|
|
926
1021
|
|
|
927
|
-
device.
|
|
1022
|
+
device.isSubjectAreaChangeMonitoringEnabled = true
|
|
928
1023
|
|
|
929
|
-
|
|
930
|
-
continuousFocusReturnTimer?.invalidate()
|
|
1024
|
+
device.unlockForConfiguration()
|
|
931
1025
|
|
|
932
|
-
//
|
|
933
|
-
|
|
934
|
-
continuousFocusReturnTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
|
|
1026
|
+
// Switch back to continuous focus after a delay to maintain automatic focus
|
|
1027
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
|
|
935
1028
|
self?.returnToContinuousFocus()
|
|
936
1029
|
}
|
|
1030
|
+
|
|
937
1031
|
|
|
938
1032
|
} catch {
|
|
939
1033
|
print("Could not focus: \(error.localizedDescription)")
|
|
940
1034
|
}
|
|
941
1035
|
}
|
|
942
1036
|
|
|
1037
|
+
// @objc private func subjectAreaDidChange(notification: NSNotification) {
|
|
1038
|
+
// DispatchQueue.main.async { [weak self] in
|
|
1039
|
+
// self?.hideFocusIndicatorWithCompletion()
|
|
1040
|
+
// }
|
|
1041
|
+
|
|
1042
|
+
// NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: notification.object)
|
|
1043
|
+
// }
|
|
1044
|
+
|
|
1045
|
+
private func focusToCenterSafely() {
|
|
1046
|
+
// Check if enough time has passed since last focus operation to avoid conflicts
|
|
1047
|
+
let now = Date()
|
|
1048
|
+
if now.timeIntervalSince(lastFocusTime) < focusThrottleInterval {
|
|
1049
|
+
return
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Only focus if not currently adjusting focus to avoid interrupting ongoing operations
|
|
1053
|
+
guard let videoInput = self.videoInput else { return }
|
|
1054
|
+
let device = videoInput.device
|
|
1055
|
+
|
|
1056
|
+
if !device.isAdjustingFocus {
|
|
1057
|
+
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
1058
|
+
focusWithPoint(point: centerPoint)
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
943
1062
|
private func returnToContinuousFocus() {
|
|
944
1063
|
guard let videoInput = self.videoInput else { return }
|
|
945
1064
|
let device = videoInput.device
|
|
946
1065
|
do {
|
|
947
1066
|
try device.lockForConfiguration()
|
|
948
1067
|
|
|
1068
|
+
// Return to continuous auto focus for automatic operation
|
|
949
1069
|
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
950
1070
|
device.focusMode = .continuousAutoFocus
|
|
951
1071
|
}
|
|
952
1072
|
|
|
1073
|
+
// Return to continuous auto exposure
|
|
953
1074
|
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
954
1075
|
device.exposureMode = .continuousAutoExposure
|
|
955
1076
|
}
|
|
@@ -960,6 +1081,22 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
960
1081
|
}
|
|
961
1082
|
}
|
|
962
1083
|
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
// private func hideFocusIndicatorWithCompletion() {
|
|
1087
|
+
// DispatchQueue.main.async { [weak self] in
|
|
1088
|
+
// guard let self = self, let focusView = self.focusView else { return }
|
|
1089
|
+
|
|
1090
|
+
// UIView.animate(withDuration: 0.2, animations: {
|
|
1091
|
+
// focusView.alpha = 0.0
|
|
1092
|
+
// focusView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
|
|
1093
|
+
// }) { _ in
|
|
1094
|
+
// focusView.removeFromSuperview()
|
|
1095
|
+
// focusView.transform = CGAffineTransform.identity
|
|
1096
|
+
// self.isFocusAnimating = false
|
|
1097
|
+
// }
|
|
1098
|
+
// }
|
|
1099
|
+
// }
|
|
963
1100
|
@objc func requestCameraPermission(_ call: CAPPluginCall) {
|
|
964
1101
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
965
1102
|
DispatchQueue.main.async {
|
|
@@ -1005,6 +1142,15 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1005
1142
|
|
|
1006
1143
|
@objc func takeSnapshot(_ call: CAPPluginCall) {
|
|
1007
1144
|
call.keepAlive = true
|
|
1145
|
+
|
|
1146
|
+
let device = videoInput.device
|
|
1147
|
+
if device.isFocusModeSupported(.autoFocus) {
|
|
1148
|
+
// Trigger focus to center before capturing
|
|
1149
|
+
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
1150
|
+
focusWithPoint(point: centerPoint)
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Set the call
|
|
1008
1154
|
takeSnapshotCall = call
|
|
1009
1155
|
}
|
|
1010
1156
|
|
|
@@ -1075,59 +1221,88 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1075
1221
|
}
|
|
1076
1222
|
|
|
1077
1223
|
@objc func takePhoto(_ call: CAPPluginCall) {
|
|
1224
|
+
call.keepAlive = true
|
|
1078
1225
|
takePhotoCall = call
|
|
1226
|
+
takePhotoWithAVFoundation()
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
deinit {
|
|
1230
|
+
NotificationCenter.default.removeObserver(self)
|
|
1231
|
+
focusCompletionTimer?.invalidate()
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// MARK: - Blur Detection
|
|
1235
|
+
private func calculateBlurScore(image: UIImage) -> Double {
|
|
1236
|
+
guard let cgImage = image.cgImage else { return 0.0 }
|
|
1079
1237
|
|
|
1080
|
-
|
|
1081
|
-
let
|
|
1082
|
-
photoSettings.isHighResolutionPhotoEnabled = true
|
|
1238
|
+
let context = CIContext()
|
|
1239
|
+
let ciImage = CIImage(cgImage: cgImage)
|
|
1083
1240
|
|
|
1084
|
-
//
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1241
|
+
// Convert to grayscale for better blur detection
|
|
1242
|
+
let grayscaleFilter = CIFilter(name: "CIColorControls")!
|
|
1243
|
+
grayscaleFilter.setValue(ciImage, forKey: kCIInputImageKey)
|
|
1244
|
+
grayscaleFilter.setValue(0.0, forKey: kCIInputSaturationKey)
|
|
1088
1245
|
|
|
1089
|
-
|
|
1090
|
-
guard let device = videoInput?.device else {
|
|
1091
|
-
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1092
|
-
return
|
|
1093
|
-
}
|
|
1246
|
+
guard let grayscaleImage = grayscaleFilter.outputImage else { return 0.0 }
|
|
1094
1247
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1248
|
+
// Apply Laplacian filter for edge detection
|
|
1249
|
+
let laplacianKernel: [Float] = [
|
|
1250
|
+
0, -1, 0,
|
|
1251
|
+
-1, 4, -1,
|
|
1252
|
+
0, -1, 0
|
|
1253
|
+
]
|
|
1254
|
+
|
|
1255
|
+
let convolutionFilter = CIFilter(name: "CIConvolution3X3")!
|
|
1256
|
+
convolutionFilter.setValue(grayscaleImage, forKey: kCIInputImageKey)
|
|
1257
|
+
convolutionFilter.setValue(CIVector(values: laplacianKernel, count: 9), forKey: "inputWeights")
|
|
1258
|
+
|
|
1259
|
+
guard let filteredImage = convolutionFilter.outputImage else { return 0.0 }
|
|
1260
|
+
|
|
1261
|
+
// Calculate variance of the Laplacian
|
|
1262
|
+
let extent = filteredImage.extent
|
|
1263
|
+
let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
|
|
1264
|
+
|
|
1265
|
+
let averageFilter = CIFilter(name: "CIAreaAverage")!
|
|
1266
|
+
averageFilter.setValue(filteredImage, forKey: kCIInputImageKey)
|
|
1267
|
+
averageFilter.setValue(inputExtent, forKey: kCIInputExtentKey)
|
|
1268
|
+
|
|
1269
|
+
guard let averageImage = averageFilter.outputImage else { return 0.0 }
|
|
1270
|
+
|
|
1271
|
+
var bitmap = [UInt8](repeating: 0, count: 4)
|
|
1272
|
+
context.render(averageImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
|
|
1273
|
+
|
|
1274
|
+
let average = Double(bitmap[0]) / 255.0
|
|
1275
|
+
|
|
1276
|
+
// Calculate variance by processing small samples of the image
|
|
1277
|
+
let sampleSize = min(100, Int(extent.width), Int(extent.height))
|
|
1278
|
+
let stepX = extent.width / Double(sampleSize)
|
|
1279
|
+
let stepY = extent.height / Double(sampleSize)
|
|
1280
|
+
|
|
1281
|
+
var variance = 0.0
|
|
1282
|
+
var sampleCount = 0
|
|
1283
|
+
|
|
1284
|
+
for i in 0..<sampleSize {
|
|
1285
|
+
for j in 0..<sampleSize {
|
|
1286
|
+
let x = extent.origin.x + Double(i) * stepX
|
|
1287
|
+
let y = extent.origin.y + Double(j) * stepY
|
|
1288
|
+
let sampleRect = CGRect(x: x, y: y, width: 1, height: 1)
|
|
1106
1289
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
}
|
|
1290
|
+
let sampleFilter = CIFilter(name: "CIAreaAverage")!
|
|
1291
|
+
sampleFilter.setValue(filteredImage, forKey: kCIInputImageKey)
|
|
1292
|
+
sampleFilter.setValue(CIVector(cgRect: sampleRect), forKey: kCIInputExtentKey)
|
|
1111
1293
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1294
|
+
if let sampleImage = sampleFilter.outputImage {
|
|
1295
|
+
var sampleBitmap = [UInt8](repeating: 0, count: 4)
|
|
1296
|
+
context.render(sampleImage, toBitmap: &sampleBitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
|
|
1297
|
+
|
|
1298
|
+
let value = Double(sampleBitmap[0]) / 255.0
|
|
1299
|
+
variance += pow(value - average, 2)
|
|
1300
|
+
sampleCount += 1
|
|
1117
1301
|
}
|
|
1118
|
-
} catch {
|
|
1119
|
-
print("Could not configure focus for capture: \(error)")
|
|
1120
|
-
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1121
1302
|
}
|
|
1122
|
-
} else {
|
|
1123
|
-
// Capture immediately if auto focus isn't supported
|
|
1124
|
-
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1125
1303
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
deinit {
|
|
1129
|
-
focusCompletionTimer?.invalidate()
|
|
1130
|
-
continuousFocusReturnTimer?.invalidate()
|
|
1304
|
+
|
|
1305
|
+
return sampleCount > 0 ? variance / Double(sampleCount) : 0.0
|
|
1131
1306
|
}
|
|
1132
1307
|
|
|
1133
1308
|
}
|
package/package.json
CHANGED