react-native-vision-camera-spoof-detector 1.0.23 → 1.0.25
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/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/9.2.0/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/reports/problems/problems-report.html +659 -0
- package/index.js +17 -1
- package/ios/FaceAntiSpoofFrameProcessor.swift +283 -0
- package/ios/FaceAntiSpoofJSI.h +12 -0
- package/ios/FaceAntiSpoofJSI.mm +92 -0
- package/ios/FaceAntiSpoofManager.swift +63 -0
- package/ios/FaceAntiSpoofModule.m +191 -0
- package/ios/FaceAntiSpoofPluginRegister.m +42 -0
- package/ios/models/FaceAntiSpoofing.tflite +0 -0
- package/ios/models/model_spec.json +16 -0
- package/package.json +4 -2
- package/react-native-vision-camera-spoof-detector.podspec +22 -0
- package/.gitignore +0 -27
package/index.js
CHANGED
|
@@ -3,7 +3,19 @@ import { VisionCameraProxy } from 'react-native-vision-camera';
|
|
|
3
3
|
|
|
4
4
|
const { FaceAntiSpoof } = NativeModules;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Lazily initialize the frame processor plugin after native initialize() succeeds.
|
|
7
|
+
let _faceAntiSpoofPlugin = null;
|
|
8
|
+
|
|
9
|
+
function ensurePluginInitialized() {
|
|
10
|
+
if (!_faceAntiSpoofPlugin) {
|
|
11
|
+
try {
|
|
12
|
+
_faceAntiSpoofPlugin = VisionCameraProxy.initFrameProcessorPlugin('faceAntiSpoof', {});
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.warn('[FaceAntiSpoof] Failed to init frame processor plugin:', e);
|
|
15
|
+
_faceAntiSpoofPlugin = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
// Worklet called by VisionCamera
|
|
9
21
|
export const faceAntiSpoofFrameProcessor = function (frame) {
|
|
@@ -67,6 +79,10 @@ export const initializeFaceAntiSpoof = async () => {
|
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
const isSuccess = result && status.pluginAvailable;
|
|
82
|
+
// If native initialization succeeded, register the frame processor plugin for worklets
|
|
83
|
+
if (isSuccess) {
|
|
84
|
+
ensurePluginInitialized();
|
|
85
|
+
}
|
|
70
86
|
console.log('[FaceAntiSpoof] Initialization result:', isSuccess);
|
|
71
87
|
return isSuccess;
|
|
72
88
|
} catch (error) {
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(VisionCamera)
|
|
4
|
+
import VisionCamera
|
|
5
|
+
import AVFoundation
|
|
6
|
+
import CoreImage
|
|
7
|
+
import UIKit
|
|
8
|
+
import Accelerate
|
|
9
|
+
|
|
10
|
+
#if canImport(TensorFlowLite)
|
|
11
|
+
import TensorFlowLite
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
typealias FaceAntiSpoofFrameType = Frame
|
|
15
|
+
typealias FaceAntiSpoofProxyType = VisionCameraProxyHolder
|
|
16
|
+
typealias FaceAntiSpoofPluginBase = FrameProcessorPlugin
|
|
17
|
+
|
|
18
|
+
@objc(FaceAntiSpoofFrameProcessor)
|
|
19
|
+
class FaceAntiSpoofFrameProcessor: FaceAntiSpoofPluginBase {
|
|
20
|
+
private weak var proxyHolder: FaceAntiSpoofProxyType?
|
|
21
|
+
|
|
22
|
+
// Processing state
|
|
23
|
+
private let processingQueue = DispatchQueue(label: "com.faceantispoof.processing", qos: .userInitiated)
|
|
24
|
+
private var isProcessing: Bool = false
|
|
25
|
+
private var latestResult: [String: Any] = ["error": "Not initialized", "isLive": false, "label": "Not Initialized"]
|
|
26
|
+
private static let INPUT_IMAGE_SIZE: Int = 256
|
|
27
|
+
|
|
28
|
+
#if canImport(TensorFlowLite)
|
|
29
|
+
// Interpreter is provided by FaceAntiSpoofManager.sharedInterpreter
|
|
30
|
+
#endif
|
|
31
|
+
|
|
32
|
+
override init(
|
|
33
|
+
proxy: FaceAntiSpoofProxyType,
|
|
34
|
+
options: [AnyHashable : Any]! = [:]
|
|
35
|
+
) {
|
|
36
|
+
#if canImport(VisionCamera)
|
|
37
|
+
super.init(proxy: proxy, options: options)
|
|
38
|
+
#else
|
|
39
|
+
super.init(proxy: proxy, options: options as NSDictionary?)
|
|
40
|
+
#endif
|
|
41
|
+
self.proxyHolder = proxy
|
|
42
|
+
|
|
43
|
+
#if canImport(TensorFlowLite)
|
|
44
|
+
let managerInited = FaceAntiSpoofManager.initializeModel()
|
|
45
|
+
if managerInited {
|
|
46
|
+
NSLog("[FaceAntiSpoof] Manager initialized model")
|
|
47
|
+
} else {
|
|
48
|
+
NSLog("[FaceAntiSpoof] Manager failed to initialize model; running stub")
|
|
49
|
+
}
|
|
50
|
+
#else
|
|
51
|
+
NSLog("[FaceAntiSpoof] TensorFlowLite not available; running stub")
|
|
52
|
+
#endif
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
deinit {
|
|
56
|
+
NSLog("[FaceAntiSpoof] deinit - releasing resources")
|
|
57
|
+
self.proxyHolder = nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override func callback(
|
|
61
|
+
_ frame: FaceAntiSpoofFrameType,
|
|
62
|
+
withArguments arguments: [AnyHashable : Any]?
|
|
63
|
+
) -> Any {
|
|
64
|
+
#if canImport(TensorFlowLite)
|
|
65
|
+
guard FaceAntiSpoofManager.isModelLoaded() else {
|
|
66
|
+
return ["error": "Model not loaded", "isLive": false, "label": "stub"]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let sampleBuffer = frame.buffer
|
|
70
|
+
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
|
|
71
|
+
return ["error": "No pixel buffer", "isLive": false, "label": "stub"]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let width = CVPixelBufferGetWidth(pixelBuffer)
|
|
75
|
+
let height = CVPixelBufferGetHeight(pixelBuffer)
|
|
76
|
+
|
|
77
|
+
// If processing already in flight, return latest cached result immediately
|
|
78
|
+
var shouldReturnLatest = false
|
|
79
|
+
objc_sync_enter(self)
|
|
80
|
+
if isProcessing {
|
|
81
|
+
shouldReturnLatest = true
|
|
82
|
+
} else {
|
|
83
|
+
isProcessing = true
|
|
84
|
+
}
|
|
85
|
+
objc_sync_exit(self)
|
|
86
|
+
|
|
87
|
+
if shouldReturnLatest {
|
|
88
|
+
return latestResult
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Capture a small, resized RGB float snapshot synchronously while the pixel buffer is valid
|
|
92
|
+
let targetSize = CGSize(width: Self.INPUT_IMAGE_SIZE, height: Self.INPUT_IMAGE_SIZE)
|
|
93
|
+
guard let rgbInput = Self.rgbFloatData(from: pixelBuffer, size: targetSize) else {
|
|
94
|
+
objc_sync_enter(self)
|
|
95
|
+
isProcessing = false
|
|
96
|
+
objc_sync_exit(self)
|
|
97
|
+
latestResult = ["error": "Failed to create RGB snapshot", "isLive": false, "label": "error"]
|
|
98
|
+
return latestResult
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Offload heavy model work to a background queue
|
|
102
|
+
processingQueue.async { [weak self] in
|
|
103
|
+
guard let self = self else { return }
|
|
104
|
+
defer {
|
|
105
|
+
objc_sync_enter(self)
|
|
106
|
+
self.isProcessing = false
|
|
107
|
+
objc_sync_exit(self)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
guard let interpreter = FaceAntiSpoofManager.sharedInterpreter else {
|
|
111
|
+
self.latestResult = ["error": "Interpreter not available", "isLive": false, "label": "error"]
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
do {
|
|
116
|
+
// Prepare input Data (Float32 little-endian)
|
|
117
|
+
var inputFloats = rgbInput
|
|
118
|
+
let inputData = Data(bytes: &inputFloats, count: inputFloats.count * MemoryLayout<Float>.size)
|
|
119
|
+
|
|
120
|
+
try interpreter.copy(inputData, toInputAt: 0)
|
|
121
|
+
try interpreter.invoke()
|
|
122
|
+
|
|
123
|
+
// Read outputs (assume indices 0 and 1 correspond to clssPred and leafNodeMask)
|
|
124
|
+
let out0 = try interpreter.output(at: 0)
|
|
125
|
+
let out1 = try interpreter.output(at: 1)
|
|
126
|
+
|
|
127
|
+
let clssPred: [Float] = out0.data.withUnsafeBytes { ptr in
|
|
128
|
+
let floatPtr = ptr.bindMemory(to: Float.self)
|
|
129
|
+
return Array(floatPtr)
|
|
130
|
+
}
|
|
131
|
+
let leafMask: [Float] = out1.data.withUnsafeBytes { ptr in
|
|
132
|
+
let floatPtr = ptr.bindMemory(to: Float.self)
|
|
133
|
+
return Array(floatPtr)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Compute neural network score similar to Android leafScore1
|
|
137
|
+
let count = min(clssPred.count, leafMask.count)
|
|
138
|
+
var nnScore: Float = 0.0
|
|
139
|
+
for i in 0..<count {
|
|
140
|
+
nnScore += abs(clssPred[i]) * leafMask[i]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Laplacian score computed from the RGB snapshot (resized)
|
|
144
|
+
let lapScore = Self.calculateLaplacianScore(from: rgbInput, width: Self.INPUT_IMAGE_SIZE, height: Self.INPUT_IMAGE_SIZE)
|
|
145
|
+
|
|
146
|
+
let combined = Self.calculateCombinedScore(neuralScore: nnScore, laplacianScore: lapScore)
|
|
147
|
+
let confidence = Self.calculateConfidence(neuralScore: nnScore, laplacianScore: lapScore)
|
|
148
|
+
|
|
149
|
+
self.latestResult = [
|
|
150
|
+
"neuralNetworkScore": Double(nnScore),
|
|
151
|
+
"laplacianScore": lapScore,
|
|
152
|
+
"combinedScore": Double(combined),
|
|
153
|
+
"confidence": Double(confidence),
|
|
154
|
+
"isLive": combined > 0.5,
|
|
155
|
+
"label": combined > 0.5 ? "live" : "spoof",
|
|
156
|
+
"width": width,
|
|
157
|
+
"height": height
|
|
158
|
+
]
|
|
159
|
+
} catch {
|
|
160
|
+
self.latestResult = ["error": "Processing error: \(error)", "isLive": false, "label": "error"]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Return latest result immediately (may be previous or initial)
|
|
165
|
+
return latestResult
|
|
166
|
+
#else
|
|
167
|
+
return ["error": "TensorFlowLite not available", "isLive": false, "label": "stub"]
|
|
168
|
+
#endif
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// MARK: - Helpers
|
|
172
|
+
#if canImport(TensorFlowLite)
|
|
173
|
+
private static func floatArray(from data: Data) -> [Float] {
|
|
174
|
+
let count = data.count / MemoryLayout<Float>.size
|
|
175
|
+
return data.withUnsafeBytes { buffer -> [Float] in
|
|
176
|
+
let ptr = buffer.bindMemory(to: Float.self)
|
|
177
|
+
return Array(UnsafeBufferPointer(start: ptr.baseAddress, count: count))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private static let sharedCIContext: CIContext = CIContext(options: nil)
|
|
182
|
+
|
|
183
|
+
private static func rgbFloatData(from pixelBuffer: CVPixelBuffer, size: CGSize) -> [Float]? {
|
|
184
|
+
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
|
|
185
|
+
defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) }
|
|
186
|
+
|
|
187
|
+
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
|
|
188
|
+
let context = Self.sharedCIContext
|
|
189
|
+
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
|
|
190
|
+
|
|
191
|
+
let width = Int(size.width)
|
|
192
|
+
let height = Int(size.height)
|
|
193
|
+
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
194
|
+
var rawData = [UInt8](repeating: 0, count: width * height * 4)
|
|
195
|
+
rawData.withUnsafeMutableBytes { ptr in
|
|
196
|
+
if let base = ptr.baseAddress {
|
|
197
|
+
let bytesPerRow = width * 4
|
|
198
|
+
if let ctx = CGContext(data: base,
|
|
199
|
+
width: width,
|
|
200
|
+
height: height,
|
|
201
|
+
bitsPerComponent: 8,
|
|
202
|
+
bytesPerRow: bytesPerRow,
|
|
203
|
+
space: colorSpace,
|
|
204
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {
|
|
205
|
+
ctx.interpolationQuality = .high
|
|
206
|
+
ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var floatData = [Float](repeating: 0, count: width * height * 3)
|
|
212
|
+
var idx = 0
|
|
213
|
+
for y in 0..<height {
|
|
214
|
+
for x in 0..<width {
|
|
215
|
+
let pixelIndex = (y * width + x) * 4
|
|
216
|
+
let r = Float(rawData[pixelIndex]) / 255.0
|
|
217
|
+
let g = Float(rawData[pixelIndex + 1]) / 255.0
|
|
218
|
+
let b = Float(rawData[pixelIndex + 2]) / 255.0
|
|
219
|
+
floatData[idx] = r
|
|
220
|
+
floatData[idx + 1] = g
|
|
221
|
+
floatData[idx + 2] = b
|
|
222
|
+
idx += 3
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return floatData
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private static func calculateLaplacianScore(from rgbData: [Float], width: Int, height: Int) -> Int {
|
|
229
|
+
var grey = [Int](repeating: 0, count: width * height)
|
|
230
|
+
var i = 0
|
|
231
|
+
for y in 0..<height {
|
|
232
|
+
for x in 0..<width {
|
|
233
|
+
let base = (y * width + x) * 3
|
|
234
|
+
let r = Int((rgbData[base] * 255).rounded())
|
|
235
|
+
let g = Int((rgbData[base + 1] * 255).rounded())
|
|
236
|
+
let b = Int((rgbData[base + 2] * 255).rounded())
|
|
237
|
+
let value = Int(0.299 * Float(r) + 0.587 * Float(g) + 0.114 * Float(b))
|
|
238
|
+
grey[i] = value
|
|
239
|
+
i += 1
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let laplace = [[0,1,0],[1,-4,1],[0,1,0]]
|
|
244
|
+
let size = 3
|
|
245
|
+
var score = 0
|
|
246
|
+
let threshold = 50
|
|
247
|
+
for y in 0..<(height - size + 1) {
|
|
248
|
+
for x in 0..<(width - size + 1) {
|
|
249
|
+
var result = 0
|
|
250
|
+
for ky in 0..<size {
|
|
251
|
+
for kx in 0..<size {
|
|
252
|
+
result += grey[(y + ky) * width + (x + kx)] * laplace[ky][kx]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if result > threshold {
|
|
256
|
+
score += 1
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return score
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private static func calculateCombinedScore(neuralScore: Float, laplacianScore: Int) -> Float {
|
|
264
|
+
let normalizedNeural = min(max(neuralScore, 0), 1)
|
|
265
|
+
let normalizedLaplacian = min(max(Float(laplacianScore) / 4000.0, 0), 1)
|
|
266
|
+
let neuralWeight: Float = 0.6
|
|
267
|
+
let laplacianWeight: Float = 0.4
|
|
268
|
+
let invertedNeural = 1.0 - normalizedNeural
|
|
269
|
+
return (invertedNeural * neuralWeight) + (normalizedLaplacian * laplacianWeight)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private static func calculateConfidence(neuralScore: Float, laplacianScore: Int) -> Float {
|
|
273
|
+
let neuralConfidence = min(max(neuralScore, 0), 1)
|
|
274
|
+
let laplacianConfidence = min(max(Float(laplacianScore) / 4000.0, 0), 1)
|
|
275
|
+
let invertedNeural = 1.0 - neuralConfidence
|
|
276
|
+
return (invertedNeural * 0.6 + laplacianConfidence * 0.4)
|
|
277
|
+
}
|
|
278
|
+
#endif
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#endif
|
|
282
|
+
|
|
283
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Objective-C++ JSI registration for FaceAntiSpoof
|
|
2
|
+
#import "FaceAntiSpoofJSI.h"
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
#import <objc/runtime.h>
|
|
5
|
+
#import <objc/message.h>
|
|
6
|
+
|
|
7
|
+
#ifdef __cplusplus
|
|
8
|
+
#include <jsi/jsi.h>
|
|
9
|
+
using namespace facebook;
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
// Helper to call +[FaceAntiSpoofManager isModelLoaded]
|
|
13
|
+
static bool FaceAntiSpoofManager_isModelLoaded() {
|
|
14
|
+
Class managerClass = objc_getClass("FaceAntiSpoofManager");
|
|
15
|
+
if (!managerClass) return false;
|
|
16
|
+
SEL sel = sel_getUid("isModelLoaded");
|
|
17
|
+
if (!sel) return false;
|
|
18
|
+
BOOL (*msgSendBool)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
19
|
+
return msgSendBool((id)managerClass, sel);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Helper to call +[FaceAntiSpoofManager initializeModel]
|
|
23
|
+
static bool FaceAntiSpoofManager_initialize() {
|
|
24
|
+
Class managerClass = objc_getClass("FaceAntiSpoofManager");
|
|
25
|
+
if (!managerClass) return false;
|
|
26
|
+
SEL sel = sel_getUid("initializeModel");
|
|
27
|
+
if (!sel) return false;
|
|
28
|
+
BOOL (*msgSendBool)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
29
|
+
return msgSendBool((id)managerClass, sel);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
extern "C" void FaceAntiSpoofJSI_install(void *runtimePtr) {
|
|
33
|
+
#ifdef __cplusplus
|
|
34
|
+
if (!runtimePtr) {
|
|
35
|
+
NSLog(@"[FaceAntiSpoof] JSI install called with NULL runtime pointer");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
jsi::Runtime *rt = reinterpret_cast<jsi::Runtime*>(runtimePtr);
|
|
40
|
+
if (!rt) {
|
|
41
|
+
NSLog(@"[FaceAntiSpoof] JSI install: invalid runtime pointer");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Register faceAntiSpoof_isModelLoaded()
|
|
47
|
+
auto isLoadedFunc = jsi::Function::createFromHostFunction(*rt,
|
|
48
|
+
jsi::PropNameID::forAscii(*rt, "faceAntiSpoof_isModelLoaded"), 0,
|
|
49
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
|
|
50
|
+
bool loaded = FaceAntiSpoofManager_isModelLoaded();
|
|
51
|
+
return jsi::Value(loaded);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
rt->global().setProperty(*rt, "faceAntiSpoof_isModelLoaded", isLoadedFunc);
|
|
55
|
+
|
|
56
|
+
// Register faceAntiSpoof_initialize()
|
|
57
|
+
auto initFunc = jsi::Function::createFromHostFunction(*rt,
|
|
58
|
+
jsi::PropNameID::forAscii(*rt, "faceAntiSpoof_initialize"), 0,
|
|
59
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
|
|
60
|
+
bool ok = FaceAntiSpoofManager_initialize();
|
|
61
|
+
return jsi::Value(ok);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
rt->global().setProperty(*rt, "faceAntiSpoof_initialize", initFunc);
|
|
65
|
+
|
|
66
|
+
// Register faceAntiSpoof_getModuleInfo()
|
|
67
|
+
auto infoFunc = jsi::Function::createFromHostFunction(*rt,
|
|
68
|
+
jsi::PropNameID::forAscii(*rt, "faceAntiSpoof_getModuleInfo"), 0,
|
|
69
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
|
|
70
|
+
jsi::Object obj(rt);
|
|
71
|
+
obj.setProperty(rt, "name", jsi::String::createFromUtf8(rt, "FaceAntiSpoof"));
|
|
72
|
+
jsi::Array methods(rt, 4);
|
|
73
|
+
methods.setValueAtIndex(rt, 0, jsi::String::createFromUtf8(rt, "initialize"));
|
|
74
|
+
methods.setValueAtIndex(rt, 1, jsi::String::createFromUtf8(rt, "checkModelStatus"));
|
|
75
|
+
methods.setValueAtIndex(rt, 2, jsi::String::createFromUtf8(rt, "isAvailable"));
|
|
76
|
+
methods.setValueAtIndex(rt, 3, jsi::String::createFromUtf8(rt, "cleanup"));
|
|
77
|
+
obj.setProperty(rt, "methods", methods);
|
|
78
|
+
return obj;
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
rt->global().setProperty(*rt, "faceAntiSpoof_getModuleInfo", infoFunc);
|
|
82
|
+
|
|
83
|
+
NSLog(@"[FaceAntiSpoof] JSI functions registered");
|
|
84
|
+
} catch (const std::exception &ex) {
|
|
85
|
+
NSLog(@"[FaceAntiSpoof] JSI registration exception: %s", ex.what());
|
|
86
|
+
} catch (...) {
|
|
87
|
+
NSLog(@"[FaceAntiSpoof] JSI registration unknown exception");
|
|
88
|
+
}
|
|
89
|
+
#else
|
|
90
|
+
NSLog(@"[FaceAntiSpoof] JSI install compiled without C++ support");
|
|
91
|
+
#endif
|
|
92
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(TensorFlowLite)
|
|
4
|
+
import TensorFlowLite
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
@objcMembers
|
|
8
|
+
public class FaceAntiSpoofManager: NSObject {
|
|
9
|
+
#if canImport(TensorFlowLite)
|
|
10
|
+
public static var sharedInterpreter: Interpreter?
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
@objc public class func initializeModel() -> Bool {
|
|
14
|
+
#if canImport(TensorFlowLite)
|
|
15
|
+
if sharedInterpreter != nil { return true }
|
|
16
|
+
|
|
17
|
+
// Look in main bundle then plugin bundle
|
|
18
|
+
var modelPath = Bundle.main.path(forResource: "FaceAntiSpoofing", ofType: "tflite")
|
|
19
|
+
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")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
guard let path = modelPath else {
|
|
27
|
+
NSLog("[FaceAntiSpoof] Manager: model not found")
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
do {
|
|
32
|
+
var options = Interpreter.Options()
|
|
33
|
+
options.threadCount = 2
|
|
34
|
+
sharedInterpreter = try Interpreter(modelPath: path, options: options)
|
|
35
|
+
try sharedInterpreter?.allocateTensors()
|
|
36
|
+
NSLog("[FaceAntiSpoof] Manager: Loaded model at \(path)")
|
|
37
|
+
return true
|
|
38
|
+
} catch {
|
|
39
|
+
NSLog("[FaceAntiSpoof] Manager: Failed to create interpreter: \(error)")
|
|
40
|
+
sharedInterpreter = nil
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
#else
|
|
44
|
+
NSLog("[FaceAntiSpoof] Manager: TensorFlowLite not available")
|
|
45
|
+
return false
|
|
46
|
+
#endif
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@objc public class func isModelLoaded() -> Bool {
|
|
50
|
+
#if canImport(TensorFlowLite)
|
|
51
|
+
return sharedInterpreter != nil
|
|
52
|
+
#else
|
|
53
|
+
return false
|
|
54
|
+
#endif
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@objc public class func cleanupModel() {
|
|
58
|
+
#if canImport(TensorFlowLite)
|
|
59
|
+
sharedInterpreter = nil
|
|
60
|
+
#endif
|
|
61
|
+
NSLog("[FaceAntiSpoof] Manager: cleaned up model")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#if __has_include(<React/RCTBridgeModule.h>)
|
|
2
|
+
#import <React/RCTBridgeModule.h>
|
|
3
|
+
#elif __has_include("RCTBridgeModule.h")
|
|
4
|
+
#import "RCTBridgeModule.h"
|
|
5
|
+
#else
|
|
6
|
+
#warning "React headers not found - FaceAntiSpoofModule will expose stub implementation"
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#import <objc/message.h>
|
|
10
|
+
#if __has_include(<React/RCTBridge.h>)
|
|
11
|
+
#import <React/RCTBridge.h>
|
|
12
|
+
#elif __has_include("RCTBridge.h")
|
|
13
|
+
#import "RCTBridge.h"
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
@interface FaceAntiSpoofModule : NSObject <RCTBridgeModule>
|
|
17
|
+
@end
|
|
18
|
+
|
|
19
|
+
@implementation FaceAntiSpoofModule
|
|
20
|
+
|
|
21
|
+
RCT_EXPORT_MODULE(FaceAntiSpoof);
|
|
22
|
+
|
|
23
|
+
// Indicate whether this module requires main thread initialization
|
|
24
|
+
+(BOOL)requiresMainQueueSetup
|
|
25
|
+
{
|
|
26
|
+
return NO;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Keep a reference to the bridge for lifecycle hooks
|
|
30
|
+
static RCTBridge *_faceAntiSpoofBridge = nil;
|
|
31
|
+
|
|
32
|
+
RCT_REMAP_METHOD(initialize,
|
|
33
|
+
initializeWithResolver:(RCTPromiseResolveBlock)resolve
|
|
34
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
35
|
+
{
|
|
36
|
+
|
|
37
|
+
BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
|
|
38
|
+
|
|
39
|
+
// Prefer the FaceAntiSpoofManager to initialize the model and create the interpreter
|
|
40
|
+
BOOL managerInitialized = NO;
|
|
41
|
+
Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
|
|
42
|
+
if (managerClass != Nil) {
|
|
43
|
+
SEL sel = NSSelectorFromString(@"initializeModel");
|
|
44
|
+
if ([managerClass respondsToSelector:sel]) {
|
|
45
|
+
// Call +initializeModel and capture BOOL result
|
|
46
|
+
BOOL (*msgSendBool)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
47
|
+
managerInitialized = msgSendBool((id)managerClass, sel);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// As a fallback, check bundle resources
|
|
52
|
+
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
53
|
+
if (!modelPath) {
|
|
54
|
+
Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
|
|
55
|
+
if (fpsClass != Nil) {
|
|
56
|
+
NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
|
|
57
|
+
modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
BOOL modelLoaded = (modelPath != nil) || managerInitialized;
|
|
61
|
+
BOOL success = pluginAvailable && modelLoaded;
|
|
62
|
+
resolve(@(success));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
RCT_REMAP_METHOD(checkModelStatus,
|
|
66
|
+
checkModelStatusWithResolver:(RCTPromiseResolveBlock)resolve
|
|
67
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
68
|
+
{
|
|
69
|
+
BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
|
|
70
|
+
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
71
|
+
if (!modelPath) {
|
|
72
|
+
Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
|
|
73
|
+
if (fpsClass != Nil) {
|
|
74
|
+
NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
|
|
75
|
+
modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
BOOL modelLoaded = (modelPath != nil);
|
|
79
|
+
NSDictionary *result = @{@"pluginAvailable": @(pluginAvailable), @"modelLoaded": @(modelLoaded), @"moduleInitialized": @(YES)};
|
|
80
|
+
resolve(result);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
RCT_REMAP_METHOD(isAvailable,
|
|
84
|
+
isAvailableWithResolver:(RCTPromiseResolveBlock)resolve
|
|
85
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
86
|
+
{
|
|
87
|
+
BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
|
|
88
|
+
resolve(@(pluginAvailable));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
RCT_REMAP_METHOD(testMethod,
|
|
92
|
+
testMethodWithResolver:(RCTPromiseResolveBlock)resolve
|
|
93
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
94
|
+
{
|
|
95
|
+
resolve(@"iOS native module stub");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
RCT_REMAP_METHOD(getModuleInfo,
|
|
99
|
+
getModuleInfoWithResolver:(RCTPromiseResolveBlock)resolve
|
|
100
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
101
|
+
{
|
|
102
|
+
NSDictionary *info = @{@"name": @"FaceAntiSpoof", @"methods": @[@"initialize", @"checkModelStatus", @"isAvailable", @"testMethod", @"getModuleInfo", @"install", @"cleanup"]};
|
|
103
|
+
resolve(info);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
RCT_REMAP_METHOD(install,
|
|
107
|
+
installWithResolver:(RCTPromiseResolveBlock)resolve
|
|
108
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
109
|
+
{
|
|
110
|
+
// Attempt to initialize shared model resources via FaceAntiSpoofManager
|
|
111
|
+
BOOL managerInitialized = NO;
|
|
112
|
+
Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
|
|
113
|
+
if (managerClass != Nil) {
|
|
114
|
+
SEL sel = NSSelectorFromString(@"initializeModel");
|
|
115
|
+
if ([managerClass respondsToSelector:sel]) {
|
|
116
|
+
BOOL (*msgSendBool)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
117
|
+
managerInitialized = msgSendBool((id)managerClass, sel);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
BOOL pluginAvailable = (NSClassFromString(@"FaceAntiSpoofFrameProcessor") != Nil);
|
|
122
|
+
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
123
|
+
if (!modelPath) {
|
|
124
|
+
Class fpsClass = NSClassFromString(@"FaceAntiSpoofFrameProcessor");
|
|
125
|
+
if (fpsClass != Nil) {
|
|
126
|
+
NSBundle *podBundle = [NSBundle bundleForClass:fpsClass];
|
|
127
|
+
modelPath = [podBundle pathForResource:@"FaceAntiSpoofing" ofType:@"tflite"];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
BOOL modelLoaded = (modelPath != nil) || managerInitialized;
|
|
131
|
+
// Attempt to register JSI: try common bridge keys to obtain a runtime pointer
|
|
132
|
+
if (_faceAntiSpoofBridge != nil) {
|
|
133
|
+
id runtimePtr = nil;
|
|
134
|
+
@try {
|
|
135
|
+
runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"jsContext"];
|
|
136
|
+
} @catch (NSException *_) {}
|
|
137
|
+
if (!runtimePtr) {
|
|
138
|
+
@try {
|
|
139
|
+
runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"jsContextRef"];
|
|
140
|
+
} @catch (NSException *_) {}
|
|
141
|
+
}
|
|
142
|
+
if (!runtimePtr) {
|
|
143
|
+
@try {
|
|
144
|
+
runtimePtr = [_faceAntiSpoofBridge valueForKeyPath:@"javaScriptContextRef"];
|
|
145
|
+
} @catch (NSException *_) {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (runtimePtr) {
|
|
149
|
+
// Pass the pointer to the JSI install stub
|
|
150
|
+
void *ptr = (__bridge void *)(runtimePtr);
|
|
151
|
+
extern void FaceAntiSpoofJSI_install(void *runtimePtr);
|
|
152
|
+
FaceAntiSpoofJSI_install(ptr);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
resolve(@(pluginAvailable && modelLoaded));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
RCT_REMAP_METHOD(cleanup,
|
|
160
|
+
cleanupWithResolver:(RCTPromiseResolveBlock)resolve
|
|
161
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
162
|
+
{
|
|
163
|
+
// Ask manager to cleanup interpreter
|
|
164
|
+
Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
|
|
165
|
+
if (managerClass != Nil) {
|
|
166
|
+
SEL sel = NSSelectorFromString(@"cleanupModel");
|
|
167
|
+
if ([managerClass respondsToSelector:sel]) {
|
|
168
|
+
void (*msgSendVoid)(id, SEL) = (void (*)(id, SEL))objc_msgSend;
|
|
169
|
+
msgSendVoid((id)managerClass, sel);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
resolve(@(YES));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
- (void)setBridge:(RCTBridge *)bridge {
|
|
176
|
+
_faceAntiSpoofBridge = bridge;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (void)invalidate {
|
|
180
|
+
Class managerClass = NSClassFromString(@"FaceAntiSpoofManager");
|
|
181
|
+
if (managerClass != Nil) {
|
|
182
|
+
SEL sel = NSSelectorFromString(@"cleanupModel");
|
|
183
|
+
if ([managerClass respondsToSelector:sel]) {
|
|
184
|
+
void (*msgSendVoid)(id, SEL) = (void (*)(id, SEL))objc_msgSend;
|
|
185
|
+
msgSendVoid((id)managerClass, sel);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
_faceAntiSpoofBridge = nil;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@end
|