react-native-vision-camera-spoof-detector 1.1.11 → 1.1.15

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.
Files changed (30) hide show
  1. package/VisionCameraSpoofDetector.podspec +41 -0
  2. package/ios/FaceAntiSpoofManager.swift +106 -4
  3. package/ios/FaceAntiSpoofModule.m +38 -51
  4. package/ios/HybridSpoofDetectorFactory.swift +10 -0
  5. package/ios/HybridSpoofDetectorOutput.swift +164 -0
  6. package/nitro.json +4 -0
  7. package/nitrogen/generated/ios/VisionCameraSpoofDetector+autolinking.rb +62 -0
  8. package/nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Bridge.cpp +60 -0
  9. package/nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Bridge.hpp +151 -0
  10. package/nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Umbrella.hpp +60 -0
  11. package/nitrogen/generated/ios/VisionCameraSpoofDetectorAutolinking.mm +33 -0
  12. package/nitrogen/generated/ios/VisionCameraSpoofDetectorAutolinking.swift +26 -0
  13. package/nitrogen/generated/ios/c++/HybridSpoofDetectorFactorySpecSwift.cpp +11 -0
  14. package/nitrogen/generated/ios/c++/HybridSpoofDetectorFactorySpecSwift.hpp +97 -0
  15. package/nitrogen/generated/ios/swift/AntiSpoofingResult.swift +54 -0
  16. package/nitrogen/generated/ios/swift/Func_void_AntiSpoofingResult.swift +46 -0
  17. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  18. package/nitrogen/generated/ios/swift/HybridSpoofDetectorFactorySpec.swift +56 -0
  19. package/nitrogen/generated/ios/swift/HybridSpoofDetectorFactorySpec_cxx.swift +142 -0
  20. package/nitrogen/generated/ios/swift/Size.swift +34 -0
  21. package/nitrogen/generated/ios/swift/SpoofDetectorOutputOptions.swift +74 -0
  22. package/package.json +8 -3
  23. package/src/specs/SpoofDetectorFactory.nitro.ts +1 -0
  24. package/ios/FaceAntiSpoofFrameProcessor.swift +0 -301
  25. package/ios/FaceAntiSpoofJSI.h +0 -12
  26. package/ios/FaceAntiSpoofJSI.mm +0 -92
  27. package/ios/FaceAntiSpoofPluginRegister.m +0 -84
  28. package/ios/models/FaceAntiSpoofing.tflite +0 -0
  29. package/react-native-vision-camera-spoof-detector.podspec +0 -22
  30. /package/{ios/models → lib/assets}/model_spec.json +0 -0
@@ -0,0 +1,41 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'VisionCameraSpoofDetector'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.homepage = package['homepage']
10
+ s.license = package['license']
11
+ s.authors = package['author']
12
+ s.platform = :ios, '15.5'
13
+ s.source = { :path => '.' }
14
+ s.swift_version = '5.0'
15
+ s.pod_target_xcconfig = {
16
+ 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64',
17
+ 'HEADER_SEARCH_PATHS' => '$(inherited) ${PODS_TARGET_SRCROOT}/nitrogen/generated/shared/c++ ${PODS_TARGET_SRCROOT}/nitrogen/generated/ios ${PODS_TARGET_SRCROOT}/nitrogen/generated/ios/c++'
18
+ }
19
+
20
+ load 'nitrogen/generated/ios/VisionCameraSpoofDetector+autolinking.rb'
21
+ add_nitrogen_files(s)
22
+
23
+ s.source_files = [
24
+ 'ios/**/*.{h,m,mm,swift,cpp,c,cc}',
25
+ 'nitrogen/generated/shared/**/*.cpp',
26
+ 'nitrogen/generated/ios/**/*.{cpp,mm,swift}',
27
+ 'nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Bridge.hpp',
28
+ ]
29
+ s.public_header_files = 'nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Bridge.hpp'
30
+ s.private_header_files = [
31
+ 'nitrogen/generated/shared/**/*.hpp',
32
+ 'nitrogen/generated/ios/c++/**/*.{h,hpp}',
33
+ ]
34
+ s.resources = ['lib/assets/*']
35
+
36
+ s.dependency 'TensorFlowLiteSwift'
37
+ s.dependency 'VisionCamera'
38
+ s.dependency 'React-jsi'
39
+ s.dependency 'React-callinvoker'
40
+ install_modules_dependencies(s)
41
+ end
@@ -6,8 +6,17 @@ import TensorFlowLite
6
6
 
7
7
  @objcMembers
8
8
  public class FaceAntiSpoofManager: NSObject {
9
+ public struct DetectionResult {
10
+ let isLive: Bool
11
+ let neuralNetworkScore: Double
12
+ let laplacianScore: Double
13
+ let combinedScore: Double
14
+ let confidence: Double
15
+ }
16
+
9
17
  #if canImport(TensorFlowLite)
10
18
  public static var sharedInterpreter: Interpreter?
19
+ private static let interpreterLock = NSLock()
11
20
  #endif
12
21
 
13
22
  @objc public class func initializeModel() -> Bool {
@@ -15,11 +24,19 @@ public class FaceAntiSpoofManager: NSObject {
15
24
  if sharedInterpreter != nil { return true }
16
25
 
17
26
  // Look in main bundle then plugin bundle
18
- var modelPath = Bundle.main.path(forResource: "FaceAntiSpoofing", ofType: "tflite")
27
+ var modelPath = Bundle.main.path(
28
+ forResource: "FaceAntiSpoofing",
29
+ ofType: "tflite",
30
+ inDirectory: "lib/assets"
31
+ ) ?? Bundle.main.path(forResource: "FaceAntiSpoofing", ofType: "tflite")
19
32
  if modelPath == nil {
20
- if let fpsClass = NSClassFromString("FaceAntiSpoofFrameProcessor") as? AnyClass {
21
- let podBundle = Bundle(for: fpsClass)
22
- modelPath = podBundle.path(forResource: "FaceAntiSpoofing", ofType: "tflite")
33
+ if let managerClass = NSClassFromString("FaceAntiSpoofManager") {
34
+ let podBundle = Bundle(for: managerClass)
35
+ modelPath = podBundle.path(
36
+ forResource: "FaceAntiSpoofing",
37
+ ofType: "tflite",
38
+ inDirectory: "lib/assets"
39
+ ) ?? podBundle.path(forResource: "FaceAntiSpoofing", ofType: "tflite")
23
40
  }
24
41
  }
25
42
 
@@ -54,6 +71,91 @@ public class FaceAntiSpoofManager: NSObject {
54
71
  #endif
55
72
  }
56
73
 
74
+ public class func process(input: [Float]) throws -> DetectionResult {
75
+ #if canImport(TensorFlowLite)
76
+ if sharedInterpreter == nil && !initializeModel() {
77
+ throw NSError(domain: "FaceAntiSpoof", code: 3, userInfo: [
78
+ NSLocalizedDescriptionKey: "Unable to initialize the TensorFlow Lite interpreter"
79
+ ])
80
+ }
81
+
82
+ interpreterLock.lock()
83
+ defer { interpreterLock.unlock() }
84
+
85
+ guard let interpreter = sharedInterpreter else {
86
+ throw NSError(domain: "FaceAntiSpoof", code: 1, userInfo: [
87
+ NSLocalizedDescriptionKey: "Interpreter not available"
88
+ ])
89
+ }
90
+
91
+ let inputData = input.withUnsafeBufferPointer { buffer in
92
+ Data(buffer: buffer)
93
+ }
94
+ try interpreter.copy(inputData, toInputAt: 0)
95
+ try interpreter.invoke()
96
+
97
+ let classOutput = try interpreter.output(at: 0).data.withUnsafeBytes { buffer in
98
+ Array(buffer.bindMemory(to: Float.self))
99
+ }
100
+ let maskOutput = try interpreter.output(at: 1).data.withUnsafeBytes { buffer in
101
+ Array(buffer.bindMemory(to: Float.self))
102
+ }
103
+ let count = min(classOutput.count, maskOutput.count)
104
+ let neuralScore = (0..<count).reduce(Float.zero) { total, index in
105
+ total + abs(classOutput[index]) * maskOutput[index]
106
+ }
107
+ let laplacianScore = calculateLaplacianScore(input)
108
+ let combinedScore = calculateCombinedScore(neuralScore, laplacianScore)
109
+ let confidence = combinedScore
110
+ let isLive = neuralScore < 0.2 && laplacianScore > 1000
111
+
112
+ return DetectionResult(
113
+ isLive: isLive,
114
+ neuralNetworkScore: Double(neuralScore),
115
+ laplacianScore: Double(laplacianScore),
116
+ combinedScore: Double(combinedScore),
117
+ confidence: Double(confidence)
118
+ )
119
+ #else
120
+ throw NSError(domain: "FaceAntiSpoof", code: 2, userInfo: [
121
+ NSLocalizedDescriptionKey: "TensorFlowLite is not available"
122
+ ])
123
+ #endif
124
+ }
125
+
126
+ private class func calculateLaplacianScore(_ input: [Float]) -> Int {
127
+ let width = 256
128
+ let height = 256
129
+ var grey = [Int](repeating: 0, count: width * height)
130
+ for index in 0..<(width * height) {
131
+ let offset = index * 3
132
+ grey[index] = Int(
133
+ (0.299 * input[offset] + 0.587 * input[offset + 1] + 0.114 * input[offset + 2]) * 255
134
+ )
135
+ }
136
+
137
+ let kernel = [[0, 1, 0], [1, -4, 1], [0, 1, 0]]
138
+ var score = 0
139
+ for y in 0..<(height - 2) {
140
+ for x in 0..<(width - 2) {
141
+ var result = 0
142
+ for ky in 0..<3 {
143
+ for kx in 0..<3 {
144
+ result += grey[(y + ky) * width + x + kx] * kernel[ky][kx]
145
+ }
146
+ }
147
+ if result > 50 { score += 1 }
148
+ }
149
+ }
150
+ return score
151
+ }
152
+
153
+ private class func calculateCombinedScore(_ neuralScore: Float, _ laplacianScore: Int) -> Float {
154
+ let neural = min(max(neuralScore, 0), 1)
155
+ let laplacian = min(max(Float(laplacianScore) / 1000, 0), 1)
156
+ return ((1 - neural) * 0.6) + (laplacian * 0.4)
157
+ }
158
+
57
159
  @objc public class func cleanupModel() {
58
160
  #if canImport(TensorFlowLite)
59
161
  sharedInterpreter = nil
@@ -13,8 +13,6 @@
13
13
  #import "RCTBridge.h"
14
14
  #endif
15
15
 
16
- extern void FaceAntiSpoofRegisterPlugin(void);
17
-
18
16
  @interface FaceAntiSpoofModule : NSObject <RCTBridgeModule>
19
17
  @end
20
18
 
@@ -35,9 +33,7 @@ RCT_REMAP_METHOD(initialize,
35
33
  initializeWithResolver:(RCTPromiseResolveBlock)resolve
36
34
  rejecter:(RCTPromiseRejectBlock)reject)
37
35
  {
38
- FaceAntiSpoofRegisterPlugin();
39
-
40
- BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
36
+ BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofManager") != Nil);
41
37
 
42
38
  // Prefer the FaceAntiSpoofManager to initialize the model and create the interpreter
43
39
  BOOL managerInitialized = NO;
@@ -52,12 +48,18 @@ RCT_REMAP_METHOD(initialize,
52
48
  }
53
49
 
54
50
  // As a fallback, check bundle resources
55
- NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
51
+ NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
52
+ if (!modelPath) {
53
+ modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
54
+ }
56
55
  if (!modelPath) {
57
- Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
58
- if (fpsClass != Nil) {
59
- NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
60
- modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
56
+ Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
57
+ if (managerClass != Nil) {
58
+ NSBundle *podBundle = [NSBundle bundleForClass:managerClass];
59
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
60
+ if (!modelPath) {
61
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
62
+ }
61
63
  }
62
64
  }
63
65
  BOOL modelLoaded = (modelPath != nil) || managerInitialized;
@@ -69,14 +71,19 @@ RCT_REMAP_METHOD(checkModelStatus,
69
71
  checkModelStatusWithResolver:(RCTPromiseResolveBlock)resolve
70
72
  rejecter:(RCTPromiseRejectBlock)reject)
71
73
  {
72
- FaceAntiSpoofRegisterPlugin();
73
- BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
74
- NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
74
+ BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofManager") != Nil);
75
+ NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
75
76
  if (!modelPath) {
76
- Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
77
- if (fpsClass != Nil) {
78
- NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
79
- modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
77
+ modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
78
+ }
79
+ if (!modelPath) {
80
+ Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
81
+ if (managerClass != Nil) {
82
+ NSBundle *podBundle = [NSBundle bundleForClass:managerClass];
83
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
84
+ if (!modelPath) {
85
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
86
+ }
80
87
  }
81
88
  }
82
89
  BOOL modelLoaded = (modelPath != nil);
@@ -88,7 +95,7 @@ RCT_REMAP_METHOD(isAvailable,
88
95
  isAvailableWithResolver:(RCTPromiseResolveBlock)resolve
89
96
  rejecter:(RCTPromiseRejectBlock)reject)
90
97
  {
91
- BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
98
+ BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofManager") != Nil);
92
99
  resolve(@(pluginAvailable));
93
100
  }
94
101
 
@@ -96,7 +103,7 @@ RCT_REMAP_METHOD(testMethod,
96
103
  testMethodWithResolver:(RCTPromiseResolveBlock)resolve
97
104
  rejecter:(RCTPromiseRejectBlock)reject)
98
105
  {
99
- resolve(@"iOS native module stub");
106
+ resolve(@"iOS native module is working!");
100
107
  }
101
108
 
102
109
  RCT_REMAP_METHOD(getModuleInfo,
@@ -111,7 +118,6 @@ RCT_REMAP_METHOD(install,
111
118
  installWithResolver:(RCTPromiseResolveBlock)resolve
112
119
  rejecter:(RCTPromiseRejectBlock)reject)
113
120
  {
114
- FaceAntiSpoofRegisterPlugin();
115
121
  // Attempt to initialize shared model resources via FaceAntiSpoofManager
116
122
  BOOL managerInitialized = NO;
117
123
  Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
@@ -123,41 +129,22 @@ RCT_REMAP_METHOD(install,
123
129
  }
124
130
  }
125
131
 
126
- BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
127
- NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
132
+ BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofManager") != Nil);
133
+ NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
128
134
  if (!modelPath) {
129
- Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
130
- if (fpsClass != Nil) {
131
- NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
132
- modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
133
- }
135
+ modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
134
136
  }
135
- BOOL modelLoaded = (modelPath != nil) || managerInitialized;
136
- // Attempt to register JSI: try common bridge keys to obtain a runtime pointer
137
- if (_faceAntiSpoofBridge != nil) {
138
- id runtimePtr = nil;
139
- @try {
140
- runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"jsContext"];
141
- } @catch (NSException *_) {}
142
- if (!runtimePtr) {
143
- @try {
144
- runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"jsContextRef"];
145
- } @catch (NSException *_) {}
146
- }
147
- if (!runtimePtr) {
148
- @try {
149
- runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"javaScriptContextRef"];
150
- } @catch (NSException *_) {}
151
- }
152
-
153
- if (runtimePtr) {
154
- // Pass the pointer to the JSI install stub
155
- void *ptr = (__bridge void *)(runtimePtr);
156
- extern void FaceAntiSpoofJSI_install(void *runtimePtr);
157
- FaceAntiSpoofJSI_install(ptr);
137
+ if (!modelPath) {
138
+ Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
139
+ if (managerClass != Nil) {
140
+ NSBundle *podBundle = [NSBundle bundleForClass:managerClass];
141
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite" inDirectory:@"lib/assets"];
142
+ if (!modelPath) {
143
+ modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
144
+ }
158
145
  }
159
146
  }
160
-
147
+ BOOL modelLoaded = (modelPath != nil) || managerInitialized;
161
148
  resolve(@(pluginAvailable && modelLoaded));
162
149
  }
163
150
 
@@ -0,0 +1,10 @@
1
+ import NitroModules
2
+ import VisionCamera
3
+
4
+ final class HybridSpoofDetectorFactory: HybridSpoofDetectorFactorySpec {
5
+ func createSpoofDetectorOutput(options: SpoofDetectorOutputOptions) throws
6
+ -> any HybridCameraOutputSpec
7
+ {
8
+ return HybridSpoofDetectorOutput(options: options)
9
+ }
10
+ }
@@ -0,0 +1,164 @@
1
+ import AVFoundation
2
+ import CoreImage
3
+ import Foundation
4
+ import NitroModules
5
+ import VisionCamera
6
+
7
+ final class HybridSpoofDetectorOutput: HybridCameraOutputSpec, NativeCameraOutput {
8
+ private let queue = DispatchQueue(label: "com.faceantispoof.output", qos: .userInitiated)
9
+ private let options: SpoofDetectorOutputOptions
10
+ private let delegate: SpoofDetectorDelegate
11
+ private let ciContext = CIContext(options: nil)
12
+ private let stateLock = NSLock()
13
+ private let targetSize = 256
14
+ private var isBusy = false
15
+ private var isDisposed = false
16
+
17
+ let output = AVCaptureVideoDataOutput()
18
+ let requiresAudioInput = false
19
+ let requiresDepthFormat = false
20
+ let mediaType: MediaType = .video
21
+ var streamType: StreamType = .video
22
+ var outputOrientation: CameraOrientation = .up
23
+
24
+ var currentResolution: margelo.nitro.camera.Size? {
25
+ guard let connection = output.connection(with: .video) else { return nil }
26
+ return connection.inputStreamResolution
27
+ }
28
+
29
+ var targetResolution: ResolutionRule {
30
+ return .closestTo(margelo.nitro.camera.Size(width: 720.0, height: 1280.0))
31
+ }
32
+
33
+ init(options: SpoofDetectorOutputOptions) {
34
+ self.options = options
35
+ self.delegate = SpoofDetectorDelegate()
36
+ super.init()
37
+
38
+ delegate.onSampleBuffer = { [weak self] sampleBuffer in
39
+ self?.process(sampleBuffer)
40
+ }
41
+ output.setSampleBufferDelegate(delegate, queue: queue)
42
+ output.alwaysDiscardsLateVideoFrames = true
43
+ }
44
+
45
+ func configure(config: OutputConfiguration) {
46
+ guard let connection = output.connection(with: .video) else { return }
47
+ connection.preferredVideoStabilizationMode = .off
48
+ }
49
+
50
+ func dispose() {
51
+ stateLock.lock()
52
+ isDisposed = true
53
+ stateLock.unlock()
54
+ output.setSampleBufferDelegate(nil, queue: nil)
55
+ }
56
+
57
+ private func process(_ sampleBuffer: CMSampleBuffer) {
58
+ stateLock.lock()
59
+ guard !isDisposed, !isBusy else {
60
+ stateLock.unlock()
61
+ return
62
+ }
63
+ isBusy = true
64
+ stateLock.unlock()
65
+
66
+ guard let input = makeRGBInput(from: sampleBuffer) else {
67
+ stateLock.lock()
68
+ isBusy = false
69
+ stateLock.unlock()
70
+ options.onError?(RuntimeError.error(withMessage: "Failed to create RGB frame input."))
71
+ return
72
+ }
73
+
74
+ queue.async { [weak self] in
75
+ guard let self else { return }
76
+ defer {
77
+ self.stateLock.lock()
78
+ self.isBusy = false
79
+ self.stateLock.unlock()
80
+ }
81
+
82
+ do {
83
+ let result = try FaceAntiSpoofManager.process(input: input)
84
+ self.options.onResult(
85
+ AntiSpoofingResult(
86
+ isLive: result.isLive,
87
+ label: result.isLive ? "Live Face" : "Spoof Face",
88
+ neuralNetworkScore: result.neuralNetworkScore,
89
+ laplacianScore: result.laplacianScore,
90
+ combinedScore: result.combinedScore,
91
+ confidence: result.confidence
92
+ )
93
+ )
94
+ } catch {
95
+ self.options.onError?(error)
96
+ }
97
+ }
98
+ }
99
+
100
+ private func makeRGBInput(from sampleBuffer: CMSampleBuffer) -> [Float]? {
101
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
102
+
103
+ let image = CIImage(cvPixelBuffer: pixelBuffer)
104
+ let oriented = image.oriented(forExifOrientation: outputOrientation.exifOrientation)
105
+ guard let cgImage = ciContext.createCGImage(oriented, from: oriented.extent) else {
106
+ return nil
107
+ }
108
+
109
+ let width = targetSize
110
+ let height = targetSize
111
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
112
+ var bytes = [UInt8](repeating: 0, count: width * height * 4)
113
+ let rendered = bytes.withUnsafeMutableBytes { rawBuffer -> Bool in
114
+ guard let baseAddress = rawBuffer.baseAddress,
115
+ let context = CGContext(
116
+ data: baseAddress,
117
+ width: width,
118
+ height: height,
119
+ bitsPerComponent: 8,
120
+ bytesPerRow: width * 4,
121
+ space: colorSpace,
122
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
123
+ ) else { return false }
124
+ context.interpolationQuality = .high
125
+ context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
126
+ return true
127
+ }
128
+ guard rendered else { return nil }
129
+
130
+ var result = [Float](repeating: 0, count: width * height * 3)
131
+ for index in 0..<(width * height) {
132
+ let source = index * 4
133
+ let destination = index * 3
134
+ result[destination] = Float(bytes[source]) / 255.0
135
+ result[destination + 1] = Float(bytes[source + 1]) / 255.0
136
+ result[destination + 2] = Float(bytes[source + 2]) / 255.0
137
+ }
138
+ return result
139
+ }
140
+ }
141
+
142
+ private final class SpoofDetectorDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
143
+ var onSampleBuffer: ((CMSampleBuffer) -> Void)?
144
+
145
+ func captureOutput(
146
+ _ output: AVCaptureOutput,
147
+ didOutput sampleBuffer: CMSampleBuffer,
148
+ from connection: AVCaptureConnection
149
+ ) {
150
+ onSampleBuffer?(sampleBuffer)
151
+ }
152
+ }
153
+
154
+ private extension CameraOrientation {
155
+ var exifOrientation: Int32 {
156
+ switch self {
157
+ case .up: return 1
158
+ case .right: return 6
159
+ case .down: return 3
160
+ case .left: return 8
161
+ @unknown default: return 1
162
+ }
163
+ }
164
+ }
package/nitro.json CHANGED
@@ -10,6 +10,10 @@
10
10
  },
11
11
  "autolinking": {
12
12
  "SpoofDetectorFactory": {
13
+ "ios": {
14
+ "language": "swift",
15
+ "implementationClassName": "HybridSpoofDetectorFactory"
16
+ },
13
17
  "android": {
14
18
  "language": "kotlin",
15
19
  "implementationClassName": "HybridSpoofDetectorFactory"
@@ -0,0 +1,62 @@
1
+ #
2
+ # VisionCameraSpoofDetector+autolinking.rb
3
+ # This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ # https://github.com/mrousavy/nitro
5
+ # Copyright © Marc Rousavy @ Margelo
6
+ #
7
+
8
+ # This is a Ruby script that adds all files generated by Nitrogen
9
+ # to the given podspec.
10
+ #
11
+ # To use it, add this to your .podspec:
12
+ # ```ruby
13
+ # Pod::Spec.new do |spec|
14
+ # # ...
15
+ #
16
+ # # Add all files generated by Nitrogen
17
+ # load 'nitrogen/generated/ios/VisionCameraSpoofDetector+autolinking.rb'
18
+ # add_nitrogen_files(spec)
19
+ # end
20
+ # ```
21
+
22
+ def add_nitrogen_files(spec)
23
+ Pod::UI.puts "[NitroModules] 🔥 VisionCameraSpoofDetector is boosted by nitro!"
24
+
25
+ spec.dependency "NitroModules"
26
+
27
+ current_source_files = Array(spec.attributes_hash['source_files'])
28
+ spec.source_files = current_source_files + [
29
+ # Generated cross-platform specs
30
+ "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}",
31
+ # Generated bridges for the cross-platform specs
32
+ "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}",
33
+ ]
34
+
35
+ current_public_header_files = Array(spec.attributes_hash['public_header_files'])
36
+ spec.public_header_files = current_public_header_files + [
37
+ # Generated specs
38
+ "nitrogen/generated/shared/**/*.{h,hpp}",
39
+ # Swift to C++ bridging helpers
40
+ "nitrogen/generated/ios/VisionCameraSpoofDetector-Swift-Cxx-Bridge.hpp"
41
+ ]
42
+
43
+ current_private_header_files = Array(spec.attributes_hash['private_header_files'])
44
+ spec.private_header_files = current_private_header_files + [
45
+ # iOS specific specs
46
+ "nitrogen/generated/ios/c++/**/*.{h,hpp}",
47
+ # Views are framework-specific and should be private
48
+ "nitrogen/generated/shared/**/views/**/*"
49
+ ]
50
+
51
+ current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {}
52
+ spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({
53
+ # Use C++ 20
54
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
55
+ # Enables C++ <-> Swift interop (by default it's only ObjC)
56
+ "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
57
+ # Enables stricter modular headers
58
+ "DEFINES_MODULE" => "YES",
59
+ # Disable auto-generated ObjC header for Swift (Static linkage on Xcode 26.4 breaks here)
60
+ "SWIFT_INSTALL_OBJC_HEADER" => "NO",
61
+ })
62
+ end
@@ -0,0 +1,60 @@
1
+ ///
2
+ /// VisionCameraSpoofDetector-Swift-Cxx-Bridge.cpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #include "VisionCameraSpoofDetector-Swift-Cxx-Bridge.hpp"
9
+
10
+ // Include C++ implementation defined types
11
+ #include "HybridSpoofDetectorFactorySpecSwift.hpp"
12
+ #include "VisionCameraSpoofDetector-Swift-Cxx-Umbrella.hpp"
13
+ #include <NitroModules/NitroDefines.hpp>
14
+ #include <VisionCamera/VisionCamera-Swift-Cxx-Bridge.hpp>
15
+
16
+ namespace margelo::nitro::camera::spoofdetector::bridge::swift {
17
+
18
+ // pragma MARK: std::shared_ptr<margelo::nitro::camera::HybridCameraOutputSpec>
19
+ std::shared_ptr<margelo::nitro::camera::HybridCameraOutputSpec> create_std__shared_ptr_margelo__nitro__camera__HybridCameraOutputSpec_(void* NON_NULL swiftUnsafePointer) noexcept {
20
+ // Implemented in VisionCamera
21
+ return margelo::nitro::camera::bridge::swift::create_std__shared_ptr_HybridCameraOutputSpec_(swiftUnsafePointer);
22
+ }
23
+ void* NON_NULL get_std__shared_ptr_margelo__nitro__camera__HybridCameraOutputSpec_(std__shared_ptr_margelo__nitro__camera__HybridCameraOutputSpec_ cppType) {
24
+ // Implemented in VisionCamera
25
+ return margelo::nitro::camera::bridge::swift::get_std__shared_ptr_HybridCameraOutputSpec_(cppType);
26
+ }
27
+
28
+ // pragma MARK: std::function<void(const AntiSpoofingResult& /* result */)>
29
+ Func_void_AntiSpoofingResult create_Func_void_AntiSpoofingResult(void* NON_NULL swiftClosureWrapper) noexcept {
30
+ auto swiftClosure = VisionCameraSpoofDetector::Func_void_AntiSpoofingResult::fromUnsafe(swiftClosureWrapper);
31
+ return [swiftClosure = std::move(swiftClosure)](const AntiSpoofingResult& result) mutable -> void {
32
+ swiftClosure.call(result);
33
+ };
34
+ }
35
+
36
+ // pragma MARK: std::function<void(const std::exception_ptr& /* error */)>
37
+ Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* NON_NULL swiftClosureWrapper) noexcept {
38
+ auto swiftClosure = VisionCameraSpoofDetector::Func_void_std__exception_ptr::fromUnsafe(swiftClosureWrapper);
39
+ return [swiftClosure = std::move(swiftClosure)](const std::exception_ptr& error) mutable -> void {
40
+ swiftClosure.call(error);
41
+ };
42
+ }
43
+
44
+ // pragma MARK: std::shared_ptr<HybridSpoofDetectorFactorySpec>
45
+ std::shared_ptr<HybridSpoofDetectorFactorySpec> create_std__shared_ptr_HybridSpoofDetectorFactorySpec_(void* NON_NULL swiftUnsafePointer) noexcept {
46
+ VisionCameraSpoofDetector::HybridSpoofDetectorFactorySpec_cxx swiftPart = VisionCameraSpoofDetector::HybridSpoofDetectorFactorySpec_cxx::fromUnsafe(swiftUnsafePointer);
47
+ return std::make_shared<margelo::nitro::camera::spoofdetector::HybridSpoofDetectorFactorySpecSwift>(swiftPart);
48
+ }
49
+ void* NON_NULL get_std__shared_ptr_HybridSpoofDetectorFactorySpec_(std__shared_ptr_HybridSpoofDetectorFactorySpec_ cppType) {
50
+ std::shared_ptr<margelo::nitro::camera::spoofdetector::HybridSpoofDetectorFactorySpecSwift> swiftWrapper = std::dynamic_pointer_cast<margelo::nitro::camera::spoofdetector::HybridSpoofDetectorFactorySpecSwift>(cppType);
51
+ #ifdef NITRO_DEBUG
52
+ if (swiftWrapper == nullptr) [[unlikely]] {
53
+ throw std::runtime_error("Class \"HybridSpoofDetectorFactorySpec\" is not implemented in Swift!");
54
+ }
55
+ #endif
56
+ VisionCameraSpoofDetector::HybridSpoofDetectorFactorySpec_cxx& swiftPart = swiftWrapper->getSwiftPart();
57
+ return swiftPart.toUnsafe();
58
+ }
59
+
60
+ } // namespace margelo::nitro::camera::spoofdetector::bridge::swift