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

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 (44) hide show
  1. package/.npmignore +16 -0
  2. package/README.md +103 -186
  3. package/android/CMakeLists.txt +29 -0
  4. package/android/build.gradle +26 -10
  5. package/android/fix-prefab.gradle +4 -0
  6. package/android/src/main/AndroidManifest.xml +1 -1
  7. package/android/src/main/cpp/CMakeLists.txt +18 -86
  8. package/android/src/main/cpp/FaceAntiSpoofJSI.cpp +80 -6
  9. package/android/src/main/cpp/cpp-adapter.cpp +9 -0
  10. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofModule.kt +55 -103
  11. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofPackage.kt +15 -8
  12. package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorFactory.kt +15 -0
  13. package/android/src/main/java/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorOutput.kt +111 -0
  14. package/index.js +22 -43
  15. package/nitro.json +20 -0
  16. package/nitrogen/generated/.gitattributes +1 -0
  17. package/nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.cmake +81 -0
  18. package/nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.gradle +27 -0
  19. package/nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.cpp +58 -0
  20. package/nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.hpp +34 -0
  21. package/nitrogen/generated/android/c++/JAntiSpoofingResult.hpp +77 -0
  22. package/nitrogen/generated/android/c++/JFunc_void_AntiSpoofingResult.hpp +78 -0
  23. package/nitrogen/generated/android/c++/JFunc_void_std__exception_ptr.hpp +76 -0
  24. package/nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.cpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.hpp +63 -0
  26. package/nitrogen/generated/android/c++/JSize.hpp +61 -0
  27. package/nitrogen/generated/android/c++/JSpoofDetectorOutputOptions.hpp +91 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/AntiSpoofingResult.kt +76 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Func_void_AntiSpoofingResult.kt +78 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Func_void_std__exception_ptr.kt +78 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/HybridSpoofDetectorFactorySpec.kt +57 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/Size.kt +56 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/SpoofDetectorOutputOptions.kt +65 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/spoofdetector/VisionCameraSpoofDetectorOnLoad.kt +35 -0
  35. package/nitrogen/generated/shared/c++/AntiSpoofingResult.hpp +103 -0
  36. package/nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.cpp +21 -0
  37. package/nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.hpp +67 -0
  38. package/nitrogen/generated/shared/c++/Size.hpp +87 -0
  39. package/nitrogen/generated/shared/c++/SpoofDetectorOutputOptions.hpp +98 -0
  40. package/package.json +10 -8
  41. package/src/specs/SpoofDetectorFactory.nitro.ts +23 -0
  42. package/android/src/main/assets/spoof_model_scale_2_7.tflite +0 -0
  43. package/android/src/main/assets/spoof_model_scale_4_0.tflite +0 -0
  44. /package/{android/src/main → lib}/assets/FaceAntiSpoofing.tflite +0 -0
@@ -1,6 +1,8 @@
1
1
  #include <jni.h>
2
2
  #include <jsi/jsi.h>
3
3
  #include <string>
4
+ #include <vector>
5
+ #include <algorithm>
4
6
  #include <android/log.h>
5
7
 
6
8
  using namespace facebook;
@@ -8,18 +10,90 @@ using namespace facebook;
8
10
  // Logging macro
9
11
  #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "FaceAntiSpoofJSI", __VA_ARGS__))
10
12
 
11
- // Dummy frame processing function
13
+ static JavaVM *javaVm = nullptr;
14
+
12
15
  jsi::Value faceAntiSpoof(jsi::Runtime &runtime, const jsi::Value &frame) {
13
- LOGI("FaceAntiSpoofJSI: frame received");
16
+ if (!frame.isObject()) {
17
+ throw jsi::JSError(runtime, "FaceAntiSpoof expected a Frame object");
18
+ }
14
19
 
15
- jsi::Object result(runtime);
16
- result.setProperty(runtime, "isLive", true);
17
- result.setProperty(runtime, "label", "Live");
18
- result.setProperty(runtime, "confidence", 0.99);
20
+ auto frameObject = frame.asObject(runtime);
21
+ auto planesValue = frameObject.getProperty(runtime, "getPlanes").asObject(runtime)
22
+ .asFunction(runtime).call(runtime, frameObject);
23
+ auto planes = planesValue.asObject(runtime);
24
+ auto length = static_cast<size_t>(planes.getProperty(runtime, "length").asNumber());
25
+ if (length < 3) {
26
+ throw jsi::JSError(runtime, "FaceAntiSpoof expected Y, U and V planes");
27
+ }
19
28
 
29
+ std::vector<std::vector<uint8_t>> planeData(3);
30
+ jint rowStrides[3] = {};
31
+ jint pixelStrides[3] = {};
32
+ for (size_t index = 0; index < 3; index++) {
33
+ auto plane = planes.getProperty(runtime, jsi::Value(static_cast<double>(index))).asObject(runtime);
34
+ auto buffer = plane.getProperty(runtime, "getPixelBuffer").asObject(runtime)
35
+ .asFunction(runtime).call(runtime, plane).asObject(runtime);
36
+ auto arrayBuffer = std::move(buffer).getArrayBuffer(runtime);
37
+ auto *data = static_cast<const uint8_t *>(arrayBuffer.data(runtime));
38
+ planeData[index].assign(data, data + arrayBuffer.size(runtime));
39
+ rowStrides[index] = static_cast<jint>(plane.getProperty(runtime, "bytesPerRow").asNumber());
40
+ auto planeWidth = plane.getProperty(runtime, "width").asNumber();
41
+ pixelStrides[index] = std::max(1, static_cast<jint>(rowStrides[index] / planeWidth));
42
+ }
43
+
44
+ JNIEnv *env = nullptr;
45
+ bool attached = false;
46
+ if (javaVm == nullptr) {
47
+ throw jsi::JSError(runtime, "FaceAntiSpoof Android runtime is unavailable");
48
+ }
49
+ if (javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) == JNI_EDETACHED) {
50
+ if (javaVm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
51
+ throw jsi::JSError(runtime, "FaceAntiSpoof could not attach to Android runtime");
52
+ }
53
+ attached = true;
54
+ }
55
+ auto moduleClass = env->FindClass("com/faceantispoof/FaceAntiSpoofModule");
56
+ auto process = env->GetStaticMethodID(moduleClass, "processFrameFromJSI", "([[BII[I[I)[D");
57
+ auto javaPlanes = env->NewObjectArray(3, env->FindClass("[B"), nullptr);
58
+ auto javaRowStrides = env->NewIntArray(3);
59
+ auto javaPixelStrides = env->NewIntArray(3);
60
+ env->SetIntArrayRegion(javaRowStrides, 0, 3, rowStrides);
61
+ env->SetIntArrayRegion(javaPixelStrides, 0, 3, pixelStrides);
62
+ for (int index = 0; index < 3; index++) {
63
+ auto bytes = env->NewByteArray(static_cast<jsize>(planeData[index].size()));
64
+ env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(planeData[index].size()),
65
+ reinterpret_cast<const jbyte *>(planeData[index].data()));
66
+ env->SetObjectArrayElement(javaPlanes, index, bytes);
67
+ env->DeleteLocalRef(bytes);
68
+ }
69
+ auto values = static_cast<jdoubleArray>(env->CallStaticObjectMethod(
70
+ moduleClass, process, javaPlanes, static_cast<jint>(frameObject.getProperty(runtime, "width").asNumber()),
71
+ static_cast<jint>(frameObject.getProperty(runtime, "height").asNumber()),
72
+ javaRowStrides, javaPixelStrides));
73
+ jdouble output[5] = {};
74
+ if (values != nullptr) env->GetDoubleArrayRegion(values, 0, 5, output);
75
+ env->DeleteLocalRef(values);
76
+ env->DeleteLocalRef(javaPixelStrides);
77
+ env->DeleteLocalRef(javaRowStrides);
78
+ env->DeleteLocalRef(javaPlanes);
79
+ env->DeleteLocalRef(moduleClass);
80
+ if (attached) javaVm->DetachCurrentThread();
81
+
82
+ jsi::Object result(runtime);
83
+ result.setProperty(runtime, "isLive", output[0] != 0.0);
84
+ result.setProperty(runtime, "label", output[0] != 0.0 ? "Live Face" : "Spoof Face");
85
+ result.setProperty(runtime, "neuralNetworkScore", output[1]);
86
+ result.setProperty(runtime, "laplacianScore", output[2]);
87
+ result.setProperty(runtime, "combinedScore", output[3]);
88
+ result.setProperty(runtime, "confidence", output[4]);
20
89
  return result;
21
90
  }
22
91
 
92
+ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
93
+ javaVm = vm;
94
+ return JNI_VERSION_1_6;
95
+ }
96
+
23
97
  // JSI registration function
24
98
  extern "C"
25
99
  JNIEXPORT void JNICALL
@@ -0,0 +1,9 @@
1
+ #include "VisionCameraSpoofDetectorOnLoad.hpp"
2
+ #include <fbjni/fbjni.h>
3
+ #include <jni.h>
4
+
5
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
6
+ return facebook::jni::initialize(vm, []() {
7
+ margelo::nitro::camera::spoofdetector::registerAllNatives();
8
+ });
9
+ }
@@ -13,17 +13,27 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
13
13
 
14
14
  companion object {
15
15
  const val NAME = "FaceAntiSpoof"
16
+ private var activeModule: FaceAntiSpoofModule? = null
17
+
18
+ @JvmStatic
19
+ fun processFrameFromJSI(
20
+ planes: Array<ByteArray>,
21
+ width: Int,
22
+ height: Int,
23
+ rowStrides: IntArray,
24
+ pixelStrides: IntArray
25
+ ): DoubleArray {
26
+ val module = activeModule
27
+ ?: return doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
28
+ return module.processNativeFrame(planes, width, height, rowStrides, pixelStrides)
29
+ }
16
30
  }
17
31
 
18
32
  private var faceAntiSpoofing: FaceAntiSpoofingAdvanced? = null
19
33
  private var isModuleInitialized = false
20
34
 
21
35
  init {
22
- try {
23
- System.loadLibrary("faceantispoof")
24
- } catch (e: Exception) {
25
- android.util.Log.e("FaceAntiSpoof", "Error loading native library", e)
26
- }
36
+ activeModule = this
27
37
 
28
38
  try {
29
39
  // Initialize face anti-spoofing with app assets
@@ -35,57 +45,6 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
35
45
  isModuleInitialized = false
36
46
  }
37
47
 
38
- try {
39
- tryRegisterJSI()
40
- } catch (e: Exception) {
41
- android.util.Log.e("FaceAntiSpoof", "Error starting JSI registration", e)
42
- }
43
- }
44
-
45
- private fun tryRegisterJSI() {
46
- try {
47
- Thread {
48
- try {
49
- Thread.sleep(500)
50
- val holder = reactApplicationContext.javaScriptContextHolder ?: return@Thread
51
-
52
- // Try nativePointer
53
- try {
54
- val field = holder.javaClass.getDeclaredField("nativePointer")
55
- field.isAccessible = true
56
- val jsContextPtr = field.getLong(holder)
57
- if (jsContextPtr != 0L) {
58
- try {
59
- registerJSIFunction(jsContextPtr)
60
- } catch (e: Exception) {
61
- android.util.Log.d("FaceAntiSpoof", "JSI registration not available: ${e.message}")
62
- }
63
- return@Thread
64
- }
65
- } catch (_: Exception) { }
66
-
67
- // Try mContext
68
- try {
69
- val field = holder.javaClass.getDeclaredField("mContext")
70
- field.isAccessible = true
71
- val jsContextPtr = field.getLong(holder)
72
- if (jsContextPtr != 0L) {
73
- try {
74
- registerJSIFunction(jsContextPtr)
75
- } catch (e: Exception) {
76
- android.util.Log.d("FaceAntiSpoof", "JSI registration not available: ${e.message}")
77
- }
78
- return@Thread
79
- }
80
- } catch (_: Exception) { }
81
-
82
- } catch (e: Exception) {
83
- android.util.Log.d("FaceAntiSpoof", "Error in JSI registration thread: ${e.message}")
84
- }
85
- }.start()
86
- } catch (e: Exception) {
87
- android.util.Log.d("FaceAntiSpoof", "Failed to start thread for JSI registration: ${e.message}")
88
- }
89
48
  }
90
49
 
91
50
  override fun getName(): String = NAME
@@ -181,6 +140,41 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
181
140
  }
182
141
  }
183
142
 
143
+ @Synchronized
144
+ private fun processNativeFrame(
145
+ planes: Array<ByteArray>,
146
+ width: Int,
147
+ height: Int,
148
+ rowStrides: IntArray,
149
+ pixelStrides: IntArray
150
+ ): DoubleArray {
151
+ if (!isModuleInitialized || faceAntiSpoofing == null || planes.size < 3) {
152
+ return doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
153
+ }
154
+
155
+ return try {
156
+ val result = faceAntiSpoofing!!.advancedAntiSpoofing(
157
+ planes,
158
+ width,
159
+ height,
160
+ rowStrides,
161
+ pixelStrides
162
+ )
163
+ val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
164
+ result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
165
+ doubleArrayOf(
166
+ if (isLive) 1.0 else 0.0,
167
+ result.neuralNetworkScore.toDouble(),
168
+ result.laplacianScore.toDouble(),
169
+ result.combinedScore.toDouble(),
170
+ result.confidence.toDouble()
171
+ )
172
+ } catch (error: Exception) {
173
+ android.util.Log.e("FaceAntiSpoof", "Native frame processing failed", error)
174
+ doubleArrayOf(0.0, 0.0, 0.0, 0.0, 0.0)
175
+ }
176
+ }
177
+
184
178
  @ReactMethod
185
179
  fun isAvailable(promise: Promise) {
186
180
  try {
@@ -218,51 +212,9 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
218
212
 
219
213
  @ReactMethod
220
214
  fun install(promise: Promise) {
221
- try {
222
- val holder = reactApplicationContext.javaScriptContextHolder
223
-
224
- if (holder != null) {
225
- // Try nativePointer
226
- try {
227
- val f = holder.javaClass.getDeclaredField("nativePointer")
228
- f.isAccessible = true
229
- val ptr = f.getLong(holder)
230
- if (ptr != 0L) {
231
- try {
232
- registerJSIFunction(ptr)
233
- promise.resolve(true)
234
- return
235
- } catch (e: Exception) {
236
- android.util.Log.d("FaceAntiSpoof", "JSI not available: ${e.message}")
237
- }
238
- }
239
- } catch (_: Exception) {}
240
-
241
- // Try mContext
242
- try {
243
- val f = holder.javaClass.getDeclaredField("mContext")
244
- f.isAccessible = true
245
- val ptr = f.getLong(holder)
246
- if (ptr != 0L) {
247
- try {
248
- registerJSIFunction(ptr)
249
- promise.resolve(true)
250
- return
251
- } catch (e: Exception) {
252
- android.util.Log.d("FaceAntiSpoof", "JSI not available: ${e.message}")
253
- }
254
- }
255
- } catch (_: Exception) {}
256
-
257
- promise.resolve(false)
258
- } else {
259
- promise.resolve(false)
260
- }
261
-
262
- } catch (e: Exception) {
263
- android.util.Log.e("FaceAntiSpoof", "install() error", e)
264
- promise.reject("INSTALL_ERROR", "Failed to install JSI function", e)
265
- }
215
+ // VisionCamera 5 uses the Nitro CameraOutput. The old reflective JSI
216
+ // installer targets a native library that is no longer part of this module.
217
+ promise.resolve(false)
266
218
  }
267
219
 
268
220
  @ReactMethod
@@ -279,6 +231,8 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
279
231
  }
280
232
  }
281
233
 
234
+ @Suppress("DEPRECATION")
235
+ @Deprecated("React Native deprecated this lifecycle callback; retained for compatibility.")
282
236
  override fun onCatalystInstanceDestroy() {
283
237
  // Cleanup when React Native is shutting down
284
238
  try {
@@ -292,6 +246,4 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
292
246
  super.onCatalystInstanceDestroy()
293
247
  }
294
248
 
295
- // Native JSI registration - optional, may not be available
296
- private external fun registerJSIFunction(jsContext: Long)
297
249
  }
@@ -1,15 +1,22 @@
1
- package com.faceantispoof
1
+ package com.margelo.nitro.camera.spoofdetector
2
2
 
3
- import com.facebook.react.ReactPackage
3
+ import com.facebook.react.BaseReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.uimanager.ViewManager
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+ import com.faceantispoof.FaceAntiSpoofModule
7
8
 
8
- class FaceAntiSpoofPackage : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
- listOf(FaceAntiSpoofModule(reactContext))
9
+ class FaceAntiSpoofPackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
11
+ if (name == FaceAntiSpoofModule.NAME) FaceAntiSpoofModule(reactContext) else null
11
12
 
12
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
13
- return emptyList()
13
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
14
+ HashMap()
15
+ }
16
+
17
+ companion object {
18
+ init {
19
+ VisionCameraSpoofDetectorOnLoad.initializeNative()
20
+ }
14
21
  }
15
22
  }
@@ -0,0 +1,15 @@
1
+ package com.margelo.nitro.camera.spoofdetector
2
+
3
+ import androidx.annotation.Keep
4
+ import com.facebook.proguard.annotations.DoNotStrip
5
+ import com.margelo.nitro.camera.HybridCameraOutputSpec
6
+
7
+ @DoNotStrip
8
+ @Keep
9
+ class HybridSpoofDetectorFactory : HybridSpoofDetectorFactorySpec() {
10
+ @DoNotStrip
11
+ @Keep
12
+ override fun createSpoofDetectorOutput(options: SpoofDetectorOutputOptions): HybridCameraOutputSpec {
13
+ return HybridSpoofDetectorOutput(options)
14
+ }
15
+ }
@@ -0,0 +1,111 @@
1
+ package com.margelo.nitro.camera.spoofdetector
2
+
3
+ import androidx.camera.core.ImageAnalysis
4
+ import androidx.camera.core.ImageProxy
5
+ import androidx.camera.core.resolutionselector.ResolutionSelector
6
+ import androidx.camera.core.resolutionselector.ResolutionStrategy
7
+ import com.margelo.nitro.NitroModules
8
+ import com.margelo.nitro.camera.CameraOrientation
9
+ import com.margelo.nitro.camera.HybridCameraOutputSpec
10
+ import com.margelo.nitro.camera.MediaType
11
+ import com.margelo.nitro.camera.MirrorMode
12
+ import com.margelo.nitro.camera.extensions.setAllowDroppingLateFrames
13
+ import com.margelo.nitro.camera.extensions.surfaceRotation
14
+ import com.margelo.nitro.camera.public.NativeCameraOutput
15
+ import com.faceantispoof.FaceAntiSpoofingAdvanced
16
+ import java.util.concurrent.Executors
17
+ import java.util.concurrent.atomic.AtomicBoolean
18
+
19
+ class HybridSpoofDetectorOutput(
20
+ private val options: SpoofDetectorOutputOptions,
21
+ ) : HybridCameraOutputSpec(), ImageAnalysis.Analyzer, NativeCameraOutput {
22
+ override val mediaType: MediaType = MediaType.VIDEO
23
+ override val mirrorMode: MirrorMode = MirrorMode.AUTO
24
+ override var outputOrientation: CameraOrientation = CameraOrientation.UP
25
+ set(value) {
26
+ field = value
27
+ imageAnalysis?.targetRotation = value.surfaceRotation
28
+ }
29
+ override val currentResolution: com.margelo.nitro.camera.Size?
30
+ get() = null
31
+
32
+ private val executor = Executors.newSingleThreadExecutor()
33
+ private val busy = AtomicBoolean(false)
34
+ private val disposed = AtomicBoolean(false)
35
+ private val detector = FaceAntiSpoofingAdvanced(
36
+ NitroModules.applicationContext?.assets ?: error("FaceAntiSpoof: no application context")
37
+ )
38
+ private var imageAnalysis: ImageAnalysis? = null
39
+
40
+ override fun createUseCase(
41
+ mirrorMode: MirrorMode,
42
+ config: NativeCameraOutput.Config,
43
+ ): NativeCameraOutput.PreparedUseCase {
44
+ val target = options.targetResolution
45
+ val selector = target?.let {
46
+ ResolutionSelector.Builder()
47
+ .setResolutionStrategy(
48
+ ResolutionStrategy(
49
+ android.util.Size(it.width.toInt(), it.height.toInt()),
50
+ ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER,
51
+ )
52
+ )
53
+ .build()
54
+ }
55
+ val analysis = ImageAnalysis.Builder()
56
+ .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
57
+ .setOutputImageRotationEnabled(false)
58
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
59
+ .setAllowDroppingLateFrames(true)
60
+ .apply { if (selector != null) setResolutionSelector(selector) }
61
+ .build()
62
+ return NativeCameraOutput.PreparedUseCase(analysis) {
63
+ imageAnalysis = analysis
64
+ analysis.setAnalyzer(executor, this)
65
+ }
66
+ }
67
+
68
+ override fun analyze(image: ImageProxy) {
69
+ if (disposed.get() || !busy.compareAndSet(false, true)) {
70
+ image.close()
71
+ return
72
+ }
73
+ try {
74
+ val sourcePlanes = image.planes
75
+ val planes = Array(3) { index ->
76
+ val buffer = sourcePlanes[index].buffer.duplicate()
77
+ ByteArray(buffer.remaining()).also(buffer::get)
78
+ }
79
+ val rowStrides = IntArray(3) { sourcePlanes[it].rowStride }
80
+ val pixelStrides = IntArray(3) { sourcePlanes[it].pixelStride.coerceAtLeast(1) }
81
+ val result = detector.advancedAntiSpoofing(
82
+ planes, image.width, image.height, rowStrides, pixelStrides
83
+ )
84
+ val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
85
+ result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
86
+ options.onResult.invoke(
87
+ AntiSpoofingResult(
88
+ isLive,
89
+ if (isLive) "Live Face" else "Spoof Face",
90
+ result.neuralNetworkScore.toDouble(),
91
+ result.laplacianScore.toDouble(),
92
+ result.combinedScore.toDouble(),
93
+ result.confidence.toDouble(),
94
+ )
95
+ )
96
+ } catch (error: Throwable) {
97
+ options.onError?.invoke(error)
98
+ } finally {
99
+ image.close()
100
+ busy.set(false)
101
+ }
102
+ }
103
+
104
+ override fun dispose() {
105
+ disposed.set(true)
106
+ imageAnalysis?.clearAnalyzer()
107
+ detector.close()
108
+ executor.shutdownNow()
109
+ super.dispose()
110
+ }
111
+ }
package/index.js CHANGED
@@ -1,50 +1,29 @@
1
- import { NativeModules } from 'react-native';
2
- import { useFrameOutput } from 'react-native-vision-camera';
3
- import { scheduleOnRN } from 'react-native-worklets';
1
+ import { useMemo, useRef } from 'react';
2
+ import { NativeModules } from 'react-native';
3
+ import { NitroModules } from 'react-native-nitro-modules';
4
4
 
5
5
  const { FaceAntiSpoof } = NativeModules;
6
6
 
7
- const processFrameOnRN = (frameData, width, height, onResult) => {
8
- FaceAntiSpoof.processFrame(frameData.planes, width, height, frameData.rowStrides, frameData.pixelStrides)
9
- .then(onResult)
10
- .catch((error) => console.error('[FaceAntiSpoof] Frame processing failed:', error));
11
- };
12
-
13
7
  export const useFaceAntiSpoofFrameOutput = ({ onResult, ...options } = {}) => {
14
- const onFrame = (frame) => {
15
- 'worklet';
16
- try {
17
- const planes = frame.getPlanes();
18
- if (planes.length < 3) return;
19
-
20
- const planeData = planes.map((plane) => Array.from(new Uint8Array(plane.getPixelBuffer())));
21
- const rowStrides = planes.map((plane) => plane.bytesPerRow);
22
- const pixelStrides = planes.map((plane) => plane.bytesPerRow / plane.width);
23
- scheduleOnRN(
24
- processFrameOnRN,
25
- { planes: planeData, rowStrides, pixelStrides },
26
- frame.width,
27
- frame.height,
28
- onResult,
29
- );
30
- } finally {
31
- frame.dispose();
32
- }
33
- };
34
-
35
- return useFrameOutput({
36
- ...options,
37
- pixelFormat: 'yuv',
38
- targetResolution: { width: 256, height: 256 },
39
- onFrame,
40
- });
41
- };
42
-
43
- // Kept as a named export for source compatibility. VisionCamera 5 applications
44
- // must use useFaceAntiSpoofFrameOutput because the old plugin API was removed.
45
- export const faceAntiSpoofFrameProcessor = function () {
46
- 'worklet';
47
- return null;
8
+ const resultRef = useRef(onResult);
9
+ resultRef.current = onResult;
10
+ const errorRef = useRef(options.onError);
11
+ errorRef.current = options.onError;
12
+ const factory = useMemo(
13
+ () => NitroModules.createHybridObject('SpoofDetectorFactory'),
14
+ [],
15
+ );
16
+ return useMemo(
17
+ () => factory.createSpoofDetectorOutput({
18
+ onResult(result) {
19
+ resultRef.current?.(result);
20
+ },
21
+ onError(error) {
22
+ errorRef.current?.(error);
23
+ },
24
+ }),
25
+ [factory],
26
+ );
48
27
  };
49
28
 
50
29
  export default FaceAntiSpoof;
package/nitro.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://nitro.margelo.com/nitro.schema.json",
3
+ "cxxNamespace": ["camera", "spoofdetector"],
4
+ "ios": {
5
+ "iosModuleName": "VisionCameraSpoofDetector"
6
+ },
7
+ "android": {
8
+ "androidNamespace": ["camera", "spoofdetector"],
9
+ "androidCxxLibName": "VisionCameraSpoofDetector"
10
+ },
11
+ "autolinking": {
12
+ "SpoofDetectorFactory": {
13
+ "android": {
14
+ "language": "kotlin",
15
+ "implementationClassName": "HybridSpoofDetectorFactory"
16
+ }
17
+ }
18
+ },
19
+ "ignorePaths": ["**/node_modules"]
20
+ }
@@ -0,0 +1 @@
1
+ ** linguist-generated=true
@@ -0,0 +1,81 @@
1
+ #
2
+ # VisionCameraSpoofDetector+autolinking.cmake
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 CMake file that adds all files generated by Nitrogen
9
+ # to the current CMake project.
10
+ #
11
+ # To use it, add this to your CMakeLists.txt:
12
+ # ```cmake
13
+ # include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/VisionCameraSpoofDetector+autolinking.cmake)
14
+ # ```
15
+
16
+ # Define a flag to check if we are building properly
17
+ add_definitions(-DBUILDING_VISIONCAMERASPOOFDETECTOR_WITH_GENERATED_CMAKE_PROJECT)
18
+
19
+ # Enable Raw Props parsing in react-native (for Nitro Views)
20
+ add_definitions(-DRN_SERIALIZABLE_STATE)
21
+
22
+ # Add all headers that were generated by Nitrogen
23
+ include_directories(
24
+ "../nitrogen/generated/shared/c++"
25
+ "../nitrogen/generated/android/c++"
26
+ "../nitrogen/generated/android/"
27
+ )
28
+
29
+ # Add all .cpp sources that were generated by Nitrogen
30
+ target_sources(
31
+ # CMake project name (Android C++ library name)
32
+ VisionCameraSpoofDetector PRIVATE
33
+ # Autolinking Setup
34
+ ../nitrogen/generated/android/VisionCameraSpoofDetectorOnLoad.cpp
35
+ # Shared Nitrogen C++ sources
36
+ ../nitrogen/generated/shared/c++/HybridSpoofDetectorFactorySpec.cpp
37
+ # Android-specific Nitrogen C++ sources
38
+ ../nitrogen/generated/android/c++/JHybridSpoofDetectorFactorySpec.cpp
39
+ )
40
+
41
+ # From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
42
+ # Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
43
+ target_compile_definitions(
44
+ VisionCameraSpoofDetector PRIVATE
45
+ -DFOLLY_NO_CONFIG=1
46
+ -DFOLLY_HAVE_CLOCK_GETTIME=1
47
+ -DFOLLY_USE_LIBCPP=1
48
+ -DFOLLY_CFG_NO_COROUTINES=1
49
+ -DFOLLY_MOBILE=1
50
+ -DFOLLY_HAVE_RECVMMSG=1
51
+ -DFOLLY_HAVE_PTHREAD=1
52
+ # Once we target android-23 above, we can comment
53
+ # the following line. NDK uses GNU style stderror_r() after API 23.
54
+ -DFOLLY_HAVE_XSI_STRERROR_R=1
55
+ )
56
+
57
+ # Add all libraries required by the generated specs
58
+ find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
59
+ find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
60
+ find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
61
+
62
+ # Link all libraries together
63
+ target_link_libraries(
64
+ VisionCameraSpoofDetector
65
+ fbjni::fbjni # <-- Facebook C++ JNI helpers
66
+ ReactAndroid::jsi # <-- RN: JSI
67
+ react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
68
+ )
69
+
70
+ # Link react-native (different prefab between RN 0.75 and RN 0.76)
71
+ if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
72
+ target_link_libraries(
73
+ VisionCameraSpoofDetector
74
+ ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
75
+ )
76
+ else()
77
+ target_link_libraries(
78
+ VisionCameraSpoofDetector
79
+ ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
80
+ )
81
+ endif()