react-native-vision-camera-spoof-detector 1.0.6 → 1.0.8

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 (74) hide show
  1. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{codemodel-v2-6c7de80461af72f456a3.json → codemodel-v2-501c3f4eb817726556cc.json} +2 -2
  2. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{index-2025-11-24T17-41-56-0411.json → index-2025-11-25T16-55-22-0685.json} +2 -2
  3. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv-Debug-32bc4eddd343848e1c25.json → target-yuv-Debug-63fdb78450b0b16b8484.json} +2 -2
  4. package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{target-yuv_shared-Debug-75ea769bf76a12914d29.json → target-yuv_shared-Debug-5c8b5c6f50bc8769a5bd.json} +2 -2
  5. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_deps +0 -0
  6. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_log +125 -125
  7. package/android/.cxx/Debug/2a1it684/arm64-v8a/CMakeFiles/CMakeOutput.log +22 -22
  8. package/android/.cxx/Debug/2a1it684/arm64-v8a/configure_fingerprint.bin +15 -15
  9. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_deps +0 -0
  10. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_log +176 -118
  11. package/android/.cxx/Debug/2a1it684/armeabi-v7a/CMakeFiles/CMakeOutput.log +20 -20
  12. package/android/.cxx/Debug/2a1it684/armeabi-v7a/configure_fingerprint.bin +15 -15
  13. package/android/.cxx/Debug/2a1it684/x86/.ninja_deps +0 -0
  14. package/android/.cxx/Debug/2a1it684/x86/.ninja_log +164 -110
  15. package/android/.cxx/Debug/2a1it684/x86/CMakeFiles/CMakeOutput.log +20 -20
  16. package/android/.cxx/Debug/2a1it684/x86/configure_fingerprint.bin +15 -15
  17. package/android/.cxx/Debug/2a1it684/x86_64/.ninja_deps +0 -0
  18. package/android/.cxx/Debug/2a1it684/x86_64/.ninja_log +109 -109
  19. package/android/.cxx/Debug/2a1it684/x86_64/CMakeFiles/CMakeOutput.log +20 -20
  20. package/android/.cxx/Debug/2a1it684/x86_64/configure_fingerprint.bin +15 -15
  21. package/android/build/.transforms/4ebf3dd0dca4f83fe1776360e21ded81/transformed/classes/classes_dex/classes.dex +0 -0
  22. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/AcceleratorConfig.dex +0 -0
  23. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/AntiSpoofingResult.dex +0 -0
  24. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofFrameProcessor.dex +0 -0
  25. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.dex +0 -0
  26. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced.dex +0 -0
  27. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder$ImageFormat.dex +0 -0
  28. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder.dex +0 -0
  29. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  30. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/build_stdout_targets.txt +51 -51
  31. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_1041_timing.txt +9 -0
  32. package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +37 -37
  33. package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_2687_timing.txt → generate_cxx_metadata_1042_timing.txt} +1 -1
  34. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +39 -39
  35. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/{generate_cxx_metadata_2687_timing.txt → generate_cxx_metadata_1042_timing.txt} +1 -1
  36. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +40 -40
  37. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_1042_timing.txt +9 -0
  38. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  39. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/AcceleratorConfig.class +0 -0
  40. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/AntiSpoofingResult.class +0 -0
  41. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
  42. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  43. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  44. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
  45. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder.class +0 -0
  46. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  57. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AcceleratorConfig.class +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AntiSpoofingResult.class +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder.class +0 -0
  65. package/android/src/main/java/com/faceantispoof/AcceleratorConfig.kt +57 -9
  66. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +1 -0
  67. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +120 -26
  68. package/android/src/main/java/com/faceantispoof/ImageFormatEncoder.kt +151 -46
  69. package/package.json +3 -3
  70. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_2688_timing.txt +0 -9
  71. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_2687_timing.txt +0 -9
  72. /package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{index-2025-11-18T18-09-57-0510.json → index-2025-11-25T16-21-43-0410.json} +0 -0
  73. /package/android/.cxx/Debug/2a1it684/x86/.cmake/api/v1/reply/{index-2025-11-18T18-10-04-0841.json → index-2025-11-25T16-21-51-0349.json} +0 -0
  74. /package/android/.cxx/Debug/2a1it684/x86_64/.cmake/api/v1/reply/{index-2025-11-24T17-42-56-0679.json → index-2025-11-25T16-55-39-0849.json} +0 -0
@@ -3,12 +3,14 @@ package com.faceantispoof
3
3
  /**
4
4
  * Configuration class for TensorFlow Lite accelerators
5
5
  * Allows developers to control GPU/NNAPI delegate behavior
6
+ * and optimize for different device tiers (low-end, mid-range, high-end)
6
7
  */
7
8
  object AcceleratorConfig {
8
9
 
9
10
  /**
10
11
  * Enable GPU acceleration (recommended for modern devices)
11
12
  * Accelerates model 3-10x compared to CPU
13
+ * GPU is automatically skipped on low-end devices even if enabled
12
14
  */
13
15
  var enableGpu: Boolean = true
14
16
 
@@ -16,34 +18,80 @@ object AcceleratorConfig {
16
18
  * Enable NNAPI acceleration (recommended for Pixel/Samsung devices)
17
19
  * Accelerates model 3-10x compared to CPU
18
20
  * Fallback if GPU is not available
21
+ * Works better on mid-range and high-end devices
19
22
  */
20
23
  var enableNnapi: Boolean = true
21
24
 
22
25
  /**
23
26
  * Enable CPU fallback if GPU/NNAPI are not available
24
- * Default: true
27
+ * Default: true (always fallback to CPU)
25
28
  */
26
29
  var enableCpuFallback: Boolean = true
27
30
 
28
31
  /**
29
32
  * Number of threads for CPU inference
30
- * Default: 4 threads
33
+ * Default: 4 threads (optimal for most devices)
34
+ * Low-end devices will use 1-2 threads regardless
31
35
  */
32
36
  var cpuThreadCount: Int = 4
33
37
 
34
38
  /**
35
- * Print debug logs for accelerator selection
39
+ * Force CPU-only mode for all devices
40
+ * Useful for debugging or if GPU/NNAPI causes issues
41
+ * Default: false
42
+ */
43
+ var forceCpuOnly: Boolean = false
44
+
45
+ /**
46
+ * Disable GPU delegate specifically
47
+ * Keeps NNAPI enabled if available
48
+ * Default: false
49
+ */
50
+ var disableGpuDelegate: Boolean = false
51
+
52
+ /**
53
+ * Disable NNAPI delegate specifically
54
+ * GPU will still be attempted if enabled
55
+ * Default: false
56
+ */
57
+ var disableNnapiDelegate: Boolean = false
58
+
59
+ /**
60
+ * Print debug logs for accelerator selection and performance
61
+ * Default: true
36
62
  */
37
63
  var debugLogging: Boolean = true
38
64
 
65
+ /**
66
+ * Enable aggressive optimization for low-end devices
67
+ * Reduces memory usage and processes fewer frames
68
+ * Default: true
69
+ */
70
+ var optimizeForLowEndDevices: Boolean = true
71
+
72
+ /**
73
+ * Maximum memory (in MB) to allocate for the interpreter
74
+ * Low-end devices will use less aggressive settings
75
+ * Default: 0 (use system defaults)
76
+ */
77
+ var maxMemoryAllocation: Int = 0
78
+
39
79
  override fun toString(): String {
40
80
  return """
41
- AcceleratorConfig:
42
- - GPU Enabled: $enableGpu
43
- - NNAPI Enabled: $enableNnapi
44
- - CPU Fallback: $enableCpuFallback
45
- - CPU Threads: $cpuThreadCount
46
- - Debug Logging: $debugLogging
81
+ ╔════════════════════════════════════════════════╗
82
+ ║ TFLite Accelerator Configuration ║
83
+ ╠════════════════════════════════════════════════╣
84
+ GPU Acceleration: $enableGpu
85
+ NNAPI Acceleration: $enableNnapi
86
+ CPU Fallback: $enableCpuFallback
87
+ ║ CPU Threads: $cpuThreadCount
88
+ ║ Force CPU Only: $forceCpuOnly
89
+ ║ Disable GPU Delegate: $disableGpuDelegate
90
+ ║ Disable NNAPI Delegate: $disableNnapiDelegate
91
+ ║ Debug Logging: $debugLogging
92
+ ║ Optimize Low-End: $optimizeForLowEndDevices
93
+ ║ Max Memory (MB): $maxMemoryAllocation
94
+ ╚════════════════════════════════════════════════╝
47
95
  """.trimIndent()
48
96
  }
49
97
  }
@@ -153,6 +153,7 @@ class FaceAntiSpoofFrameProcessor : FrameProcessorPlugin() {
153
153
  "combinedScore" to result.combinedScore.toDouble(),
154
154
  "recognitionImageBase64" to result.recognitionImageBase64,
155
155
  // Image format metadata
156
+ // NOTE: Image is in BGR format (OpenCV compatible), encoded as Base64 JPEG
156
157
  "imageFormat" to result.imageFormat,
157
158
  "imageResolution" to result.imageResolution,
158
159
  "imageQuality" to result.imageQuality
@@ -96,22 +96,15 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
96
96
  try {
97
97
  val modelBuffer = loadModelFile(assetManager, MODEL_FILE)
98
98
  val options = Interpreter.Options()
99
- options.setNumThreads(4)
100
99
 
101
- if (tryAddGpuDelegate(options)) {
102
- currentAccelerator = ACCELERATOR_GPU
103
- Log.i(TAG, "GPU delegate enabled successfully")
104
- }
105
- else {
106
- try {
107
- options.setUseNNAPI(true)
108
- currentAccelerator = ACCELERATOR_NNAPI
109
- Log.i(TAG, "NNAPI enabled successfully")
110
- } catch (e: Exception) {
111
- currentAccelerator = ACCELERATOR_CPU
112
- Log.i(TAG, "Running on CPU (GPU/NNAPI not available): ${e.message}")
113
- }
114
- }
100
+ // FORCE CPU-ONLY MODE FOR LOW-END DEVICE COMPATIBILITY
101
+ // This bypasses all GPU/NNAPI issues and uses stable CPU inference
102
+ val optimalThreads = getOptimalThreadCount()
103
+ options.setNumThreads(optimalThreads)
104
+
105
+ currentAccelerator = ACCELERATOR_CPU
106
+ Log.i(TAG, "✓ FORCED CPU-ONLY MODE - Bypassing GPU/NNAPI")
107
+ Log.i(TAG, "Using $optimalThreads threads for CPU inference")
115
108
 
116
109
  interpreter = Interpreter(modelBuffer, options)
117
110
 
@@ -126,28 +119,46 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
126
119
  outputs[outputIndex0] = clssPred
127
120
  outputs[outputIndex1] = leafNodeMask
128
121
  isInitialized = true
129
- Log.i(TAG, "Interpreter initialized with accelerator: $currentAccelerator")
122
+ Log.i(TAG, " Face Anti-Spoof initialized | CPU-ONLY MODE | Threads: $optimalThreads")
130
123
  } catch (e: Exception) {
131
- Log.e(TAG, "Failed to load TFLite model", e)
124
+ Log.e(TAG, "Failed to load TFLite model: ${e.message}", e)
132
125
  isInitialized = false
126
+ currentAccelerator = ACCELERATOR_CPU
133
127
  }
134
128
  }
135
129
 
136
130
  private fun tryAddGpuDelegate(options: Interpreter.Options): Boolean {
137
131
  return try {
132
+ // Additional check: verify device has minimum Android version for GPU delegate
133
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
134
+ Log.w(TAG, "GPU delegate requires Android 5.0+, device running: ${android.os.Build.VERSION.SDK_INT}")
135
+ return false
136
+ }
137
+
138
138
  val compatibilityList = CompatibilityList()
139
139
  if (compatibilityList.isDelegateSupportedOnThisDevice) {
140
- gpuDelegate = GpuDelegate(compatibilityList.bestOptionsForThisDevice)
141
- options.addDelegate(gpuDelegate)
142
- Log.d(TAG, "GPU delegate added successfully")
143
- true
140
+ try {
141
+ gpuDelegate = GpuDelegate(compatibilityList.bestOptionsForThisDevice)
142
+ options.addDelegate(gpuDelegate)
143
+ Log.d(TAG, "GPU delegate added successfully")
144
+ true
145
+ } catch (e: Exception) {
146
+ Log.w(TAG, "Failed to initialize GPU delegate after compatibility check: ${e.message}")
147
+ gpuDelegate?.close()
148
+ gpuDelegate = null
149
+ false
150
+ }
144
151
  } else {
145
152
  Log.d(TAG, "GPU delegate not supported on this device")
146
153
  false
147
154
  }
148
155
  } catch (e: Exception) {
149
156
  Log.w(TAG, "Failed to add GPU delegate: ${e.message}")
150
- gpuDelegate?.close()
157
+ try {
158
+ gpuDelegate?.close()
159
+ } catch (e2: Exception) {
160
+ Log.w(TAG, "Error closing GPU delegate: ${e2.message}")
161
+ }
151
162
  gpuDelegate = null
152
163
  false
153
164
  }
@@ -412,6 +423,7 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
412
423
  Log.d(TAG, "YUV to RGB conversion completed in ${convertTime}ms. RGB data: ${convertedRgb.size} bytes")
413
424
 
414
425
  // Encode RGB to target format (JPEG, PNG, or BMP) and Base64
426
+ // NOTE: ImageFormatEncoder.rgbToBase64() automatically converts RGB→BGR for OpenCV compatibility
415
427
  val startEncode = System.currentTimeMillis()
416
428
  val format = when (outputImageFormat) {
417
429
  "PNG" -> ImageFormatEncoder.ImageFormat.PNG
@@ -430,16 +442,16 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
430
442
 
431
443
  if (base64Result != null) {
432
444
  Log.d(TAG, "$outputImageFormat encoding completed in ${encodeTime}ms. Base64 size: ${base64Result.length} bytes")
433
- Log.d(TAG, "✅ Generated ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 for recognition API (total: ${convertTime + encodeTime}ms)")
445
+ Log.d(TAG, "✅ Generated ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 for recognition API in BGR format (total: ${convertTime + encodeTime}ms)")
434
446
  val estSize = estimateImageSize()
435
- Log.d(TAG, "Format: $outputImageFormat, Quality: $outputImageQuality, Estimated: ${estSize / 1024}KB")
447
+ Log.d(TAG, "Format: $outputImageFormat (BGR), Quality: $outputImageQuality, Estimated: ${estSize / 1024}KB")
436
448
  } else {
437
- Log.w(TAG, "⚠️ ImageFormatEncoder returned null")
449
+ Log.w(TAG, "⚠️ ImageFormatEncoder returned null for BGR conversion")
438
450
  }
439
451
 
440
452
  base64Result
441
453
  } catch (e: Exception) {
442
- Log.e(TAG, "❌ Error generating ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64: ${e.message}", e)
454
+ Log.e(TAG, "❌ Error generating ${outputImageResolution}×${outputImageResolution} $outputImageFormat Base64 (BGR format): ${e.message}", e)
443
455
  e.printStackTrace()
444
456
  null
445
457
  }
@@ -771,6 +783,88 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
771
783
  Log.e(TAG, "Error closing interpreter", e)
772
784
  }
773
785
  }
786
+
787
+ /**
788
+ * Detects if the device is high-end based on RAM, processor, and Android version.
789
+ * High-end devices can use GPU/NNAPI delegates.
790
+ * Low-end devices default to optimized CPU inference.
791
+ */
792
+ private fun isHighEndDevice(): Boolean {
793
+ try {
794
+ // Check Android version
795
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
796
+ Log.d(TAG, "Device running Android ${android.os.Build.VERSION.SDK_INT} (< 5.0) - low-end")
797
+ return false
798
+ }
799
+
800
+ // Check available RAM
801
+ val runtime = Runtime.getRuntime()
802
+ val maxMemory = runtime.maxMemory() / (1024 * 1024) // Convert to MB
803
+ if (maxMemory < 256) { // Less than 256MB allocated
804
+ Log.d(TAG, "Low available RAM: ${maxMemory}MB - treating as low-end device")
805
+ return false
806
+ }
807
+
808
+ // Check processor cores (devices with 2 cores or less are typically low-end)
809
+ val processorCount = Runtime.getRuntime().availableProcessors()
810
+ if (processorCount <= 2) {
811
+ Log.d(TAG, "Limited CPU cores: $processorCount - low-end device")
812
+ return false
813
+ }
814
+
815
+ // Check device name for known low-end models
816
+ val model = android.os.Build.MODEL.lowercase()
817
+ val lowEndMarkers = listOf(
818
+ "moto g", "samsung a", "samsung j", "redmi 6", "redmi 7",
819
+ "honor 8a", "galaxy a01", "galaxy j2"
820
+ )
821
+
822
+ for (marker in lowEndMarkers) {
823
+ if (model.contains(marker)) {
824
+ Log.d(TAG, "Known low-end device model: ${android.os.Build.MODEL}")
825
+ return false
826
+ }
827
+ }
828
+
829
+ Log.d(TAG, "Device appears to be high-end (Android ${android.os.Build.VERSION.SDK_INT}, $processorCount cores, ${maxMemory}MB RAM)")
830
+ return true
831
+ } catch (e: Exception) {
832
+ Log.w(TAG, "Error detecting device tier: ${e.message}, defaulting to CPU-only")
833
+ return false
834
+ }
835
+ }
836
+
837
+ /**
838
+ * Calculates optimal thread count based on available processors and device resources.
839
+ * Low-end devices use 1-2 threads to avoid memory issues.
840
+ */
841
+ private fun getOptimalThreadCount(): Int {
842
+ return try {
843
+ val availableProcessors = Runtime.getRuntime().availableProcessors()
844
+
845
+ // For low-end devices, limit thread count
846
+ if (!isHighEndDevice()) {
847
+ Log.d(TAG, "Low-end device: limiting threads to 1-2")
848
+ return maxOf(1, minOf(2, availableProcessors - 1))
849
+ }
850
+
851
+ // For high-end devices, use configured count or optimal calculation
852
+ val configured = AcceleratorConfig.cpuThreadCount
853
+ val optimal = maxOf(1, availableProcessors - 1)
854
+
855
+ val threadCount = if (configured > 0) {
856
+ minOf(configured, availableProcessors)
857
+ } else {
858
+ optimal
859
+ }
860
+
861
+ Log.d(TAG, "Optimal thread count: $threadCount (available processors: $availableProcessors)")
862
+ threadCount
863
+ } catch (e: Exception) {
864
+ Log.w(TAG, "Error calculating optimal threads: ${e.message}, using default")
865
+ AcceleratorConfig.cpuThreadCount
866
+ }
867
+ }
774
868
  }
775
869
 
776
870
  data class AntiSpoofingResult(
@@ -8,9 +8,11 @@ import java.io.ByteArrayOutputStream
8
8
 
9
9
  /**
10
10
  * Multi-format image encoder supporting JPEG, PNG, and BMP.
11
- * Handles RGB data conversion to various image formats for compatibility
11
+ * Handles BGR data conversion (OpenCV compatible) to various image formats for compatibility
12
12
  * with face recognition systems (OpenCV cv2.imdecode compatible).
13
13
  *
14
+ * NOTE: Converts RGB input to BGR format as required by OpenCV and FastAPI server
15
+ *
14
16
  * Supported formats:
15
17
  * - JPEG: Compressed, good for network transmission (~80-100 KB at quality 85)
16
18
  * - PNG: Lossless, larger file size (~200-300 KB)
@@ -36,15 +38,17 @@ object ImageFormatEncoder {
36
38
  }
37
39
 
38
40
  /**
39
- * Convert RGB data to specified image format and return as Base64 string.
40
- * Compatible with OpenCV cv2.imdecode() for face recognition systems.
41
+ * Convert RGB data to BGR format (OpenCV compatible) and encode to specified image format.
42
+ * Returns Base64 string compatible with FastAPI server.
43
+ *
44
+ * IMPORTANT: This method performs RGB→BGR conversion required by OpenCV cv2.imdecode()
41
45
  *
42
46
  * @param rgbData RGB data as byte array (format: RGBRGBRGB..., 3 channels)
43
47
  * @param width Image width (pixels)
44
48
  * @param height Image height (pixels)
45
49
  * @param format Target image format (JPEG, PNG, BMP)
46
50
  * @param quality Compression quality for JPEG (0-100), ignored for PNG/BMP
47
- * @return Base64 encoded image string, or null if encoding fails
51
+ * @return Base64 encoded image string in BGR color space, or null if encoding fails
48
52
  */
49
53
  fun rgbToBase64(
50
54
  rgbData: ByteArray,
@@ -57,18 +61,22 @@ object ImageFormatEncoder {
57
61
  validateImageDimensions(width, height)
58
62
  validateRgbData(rgbData, width, height)
59
63
 
60
- val imageBytes = rgbToImageBytes(rgbData, width, height, format, quality)
64
+ // CRITICAL: Convert RGB to BGR for OpenCV compatibility
65
+ val bgrData = rgbToBgr(rgbData)
66
+
67
+ val imageBytes = bgrToImageBytes(bgrData, width, height, format, quality)
61
68
  ?: return null
62
69
 
63
70
  Base64.encodeToString(imageBytes, Base64.NO_WRAP)
64
71
  } catch (e: Exception) {
65
- Log.e(TAG, "Failed to convert RGB to ${format.name} Base64: ${e.message}", e)
72
+ Log.e(TAG, "Failed to convert RGB to BGR ${format.name} Base64: ${e.message}", e)
66
73
  null
67
74
  }
68
75
  }
69
76
 
70
77
  /**
71
78
  * Convert RGB float array (normalized 0-1) to image format and return as Base64.
79
+ * Performs RGB→BGR conversion for OpenCV compatibility.
72
80
  * Each pixel is stored as 3 floats (R, G, B).
73
81
  *
74
82
  * @param rgbFloats RGB data as float array (normalized to 0-1)
@@ -76,7 +84,7 @@ object ImageFormatEncoder {
76
84
  * @param height Image height
77
85
  * @param format Target image format
78
86
  * @param quality Compression quality for JPEG
79
- * @return Base64 encoded image string
87
+ * @return Base64 encoded image string in BGR format
80
88
  */
81
89
  fun rgbFloatToBase64(
82
90
  rgbFloats: FloatArray,
@@ -113,20 +121,21 @@ object ImageFormatEncoder {
113
121
 
114
122
  rgbToBase64(rgbBytes, width, height, format, quality)
115
123
  } catch (e: Exception) {
116
- Log.e(TAG, "Failed to convert RGB float to ${format.name} Base64: ${e.message}", e)
124
+ Log.e(TAG, "Failed to convert RGB float to BGR ${format.name} Base64: ${e.message}", e)
117
125
  null
118
126
  }
119
127
  }
120
128
 
121
129
  /**
122
130
  * Convert RGB data to raw image bytes (not Base64).
131
+ * Performs RGB→BGR conversion for OpenCV compatibility.
123
132
  *
124
133
  * @param rgbData RGB data as byte array
125
134
  * @param width Image width
126
135
  * @param height Image height
127
136
  * @param format Target image format
128
137
  * @param quality Compression quality for JPEG
129
- * @return Raw image bytes, or null if encoding fails
138
+ * @return Raw image bytes in BGR format, or null if encoding fails
130
139
  */
131
140
  fun rgbToImageBytes(
132
141
  rgbData: ByteArray,
@@ -139,29 +148,12 @@ object ImageFormatEncoder {
139
148
  validateImageDimensions(width, height)
140
149
  validateRgbData(rgbData, width, height)
141
150
 
142
- val bitmap = createBitmapFromRgb(rgbData, width, height)
143
- ?: return null
144
-
145
- val imageBytes = try {
146
- when (format) {
147
- ImageFormat.JPEG -> compressBitmapToBytes(bitmap, format, quality)
148
- ImageFormat.PNG -> compressBitmapToBytes(bitmap, format, 100)
149
- ImageFormat.BMP -> compressBitmapToBytes(bitmap, format, 100)
150
- }
151
- } catch (e: Exception) {
152
- Log.e(TAG, "Error compressing bitmap: ${e.message}", e)
153
- null
154
- } finally {
155
- try {
156
- bitmap.recycle()
157
- } catch (e: Exception) {
158
- Log.w(TAG, "Error recycling bitmap: ${e.message}")
159
- }
160
- }
161
-
162
- imageBytes
151
+ // CRITICAL: Convert RGB to BGR for OpenCV compatibility
152
+ val bgrData = rgbToBgr(rgbData)
153
+
154
+ bgrToImageBytes(bgrData, width, height, format, quality)
163
155
  } catch (e: Exception) {
164
- Log.e(TAG, "Failed to convert RGB to ${format.name} bytes: ${e.message}", e)
156
+ Log.e(TAG, "Failed to convert RGB to BGR ${format.name} bytes: ${e.message}", e)
165
157
  null
166
158
  }
167
159
  }
@@ -226,13 +218,93 @@ object ImageFormatEncoder {
226
218
  // ============ Private Helper Methods ============
227
219
 
228
220
  /**
229
- * Create Bitmap from RGB byte array.
230
- * RGB format: RGBRGBRGB... (3 bytes per pixel, no alpha)
221
+ * Convert RGB byte array to BGR for OpenCV compatibility.
222
+ * CRITICAL: OpenCV expects BGR format, not RGB.
223
+ *
224
+ * @param rgbData RGB data in format RGBRGBRGB...
225
+ * @return BGR data in format BGRBGRBGR...
231
226
  */
232
- private fun createBitmapFromRgb(rgbData: ByteArray, width: Int, height: Int): Bitmap? {
227
+ private fun rgbToBgr(rgbData: ByteArray): ByteArray {
228
+ val bgrData = ByteArray(rgbData.size)
229
+ var rgbIdx = 0
230
+ var bgrIdx = 0
231
+
232
+ while (rgbIdx < rgbData.size && rgbIdx + 2 < rgbData.size) {
233
+ val r = rgbData[rgbIdx++]
234
+ val g = rgbData[rgbIdx++]
235
+ val b = rgbData[rgbIdx++]
236
+
237
+ // Swap: BGR order
238
+ bgrData[bgrIdx++] = b
239
+ bgrData[bgrIdx++] = g
240
+ bgrData[bgrIdx++] = r
241
+ }
242
+
243
+ Log.d(TAG, "Converted RGB to BGR: ${rgbData.size} bytes")
244
+ return bgrData
245
+ }
246
+
247
+ /**
248
+ * Convert BGR data to raw image bytes (not Base64).
249
+ * BGR data is already in OpenCV format.
250
+ *
251
+ * @param bgrData BGR data as byte array (already in OpenCV format)
252
+ * @param width Image width
253
+ * @param height Image height
254
+ * @param format Target image format
255
+ * @param quality Compression quality for JPEG
256
+ * @return Raw image bytes, or null if encoding fails
257
+ */
258
+ private fun bgrToImageBytes(
259
+ bgrData: ByteArray,
260
+ width: Int,
261
+ height: Int,
262
+ format: ImageFormat = ImageFormat.JPEG,
263
+ quality: Int = 85
264
+ ): ByteArray? {
233
265
  return try {
234
- if (rgbData.isEmpty()) {
235
- Log.e(TAG, "RGB data is empty")
266
+ validateImageDimensions(width, height)
267
+
268
+ if (bgrData.size < width * height * 3) {
269
+ Log.e(TAG, "BGR data too small: expected ${width * height * 3}, got ${bgrData.size}")
270
+ return null
271
+ }
272
+
273
+ val bitmap = createBitmapFromBgr(bgrData, width, height)
274
+ ?: return null
275
+
276
+ val imageBytes = try {
277
+ when (format) {
278
+ ImageFormat.JPEG -> compressBitmapToBytes(bitmap, format, quality)
279
+ ImageFormat.PNG -> compressBitmapToBytes(bitmap, format, 100)
280
+ ImageFormat.BMP -> compressBitmapToBytes(bitmap, format, 100)
281
+ }
282
+ } catch (e: Exception) {
283
+ Log.e(TAG, "Error compressing bitmap: ${e.message}", e)
284
+ null
285
+ } finally {
286
+ try {
287
+ bitmap.recycle()
288
+ } catch (e: Exception) {
289
+ Log.w(TAG, "Error recycling bitmap: ${e.message}")
290
+ }
291
+ }
292
+
293
+ imageBytes
294
+ } catch (e: Exception) {
295
+ Log.e(TAG, "Failed to convert BGR to ${format.name} bytes: ${e.message}", e)
296
+ null
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Create Bitmap from BGR byte array (OpenCV format).
302
+ * BGR format: BGRBGRBGR... (3 bytes per pixel, no alpha)
303
+ */
304
+ private fun createBitmapFromBgr(bgrData: ByteArray, width: Int, height: Int): Bitmap? {
305
+ return try {
306
+ if (bgrData.isEmpty()) {
307
+ Log.e(TAG, "BGR data is empty")
236
308
  return null
237
309
  }
238
310
  if (width <= 0 || height <= 0) {
@@ -241,31 +313,64 @@ object ImageFormatEncoder {
241
313
  }
242
314
 
243
315
  val expectedSize = width * height * 3
244
- if (rgbData.size < expectedSize) {
245
- Log.e(TAG, "RGB data too small: expected at least $expectedSize, got ${rgbData.size}")
316
+ if (bgrData.size < expectedSize) {
317
+ Log.e(TAG, "BGR data too small: expected at least $expectedSize, got ${bgrData.size}")
246
318
  return null
247
319
  }
248
320
 
249
- // Convert RGB to ARGB for Bitmap (Android uses ARGB_8888)
321
+ // Convert BGR to ARGB for Bitmap (Android uses ARGB_8888)
322
+ // BGR order: B, G, R
250
323
  val pixels = IntArray(width * height)
251
- var rgbIdx = 0
324
+ var bgrIdx = 0
252
325
  for (i in pixels.indices) {
253
- if (rgbIdx + 2 >= rgbData.size) {
254
- Log.e(TAG, "RGB buffer index out of bounds at pixel $i")
326
+ if (bgrIdx + 2 >= bgrData.size) {
327
+ Log.e(TAG, "BGR buffer index out of bounds at pixel $i")
255
328
  return null
256
329
  }
257
- val r = rgbData[rgbIdx++].toInt() and 0xFF
258
- val g = rgbData[rgbIdx++].toInt() and 0xFF
259
- val b = rgbData[rgbIdx++].toInt() and 0xFF
330
+ val b = bgrData[bgrIdx++].toInt() and 0xFF
331
+ val g = bgrData[bgrIdx++].toInt() and 0xFF
332
+ val r = bgrData[bgrIdx++].toInt() and 0xFF
260
333
  pixels[i] = 0xFF000000.toInt() or (r shl 16) or (g shl 8) or b // ARGB
261
334
  }
262
335
 
263
336
  val bitmap = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
264
337
  if (bitmap == null) {
265
- Log.e(TAG, "Failed to create bitmap")
338
+ Log.e(TAG, "Failed to create bitmap from BGR")
266
339
  return null
267
340
  }
268
341
  bitmap
342
+ } catch (e: Exception) {
343
+ Log.e(TAG, "Error creating bitmap from BGR: ${e.message}", e)
344
+ null
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Create Bitmap from RGB byte array.
350
+ * RGB format: RGBRGBRGB... (3 bytes per pixel, no alpha)
351
+ * DEPRECATED: Use createBitmapFromBgr instead for OpenCV compatibility
352
+ */
353
+ @Deprecated("Use createBitmapFromBgr for OpenCV compatibility")
354
+ private fun createBitmapFromRgb(rgbData: ByteArray, width: Int, height: Int): Bitmap? {
355
+ return try {
356
+ if (rgbData.isEmpty()) {
357
+ Log.e(TAG, "RGB data is empty")
358
+ return null
359
+ }
360
+ if (width <= 0 || height <= 0) {
361
+ Log.e(TAG, "Invalid dimensions: width=$width, height=$height")
362
+ return null
363
+ }
364
+
365
+ val expectedSize = width * height * 3
366
+ if (rgbData.size < expectedSize) {
367
+ Log.e(TAG, "RGB data too small: expected at least $expectedSize, got ${rgbData.size}")
368
+ return null
369
+ }
370
+
371
+ // Convert RGB to BGR first, then to ARGB for Bitmap
372
+ val bgrData = rgbToBgr(rgbData)
373
+ createBitmapFromBgr(bgrData, width, height)
269
374
  } catch (e: Exception) {
270
375
  Log.e(TAG, "Error creating bitmap from RGB: ${e.message}", e)
271
376
  null