react-native-vision-camera-spoof-detector 1.0.5 → 1.0.7

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 (78) hide show
  1. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_deps +0 -0
  2. package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_log +124 -62
  3. package/android/.cxx/Debug/2a1it684/arm64-v8a/CMakeFiles/CMakeOutput.log +22 -22
  4. package/android/.cxx/Debug/2a1it684/arm64-v8a/configure_fingerprint.bin +15 -15
  5. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{codemodel-v2-053850119b681f93cecc.json → codemodel-v2-52ac1d8b40b69596389b.json} +2 -2
  6. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{index-2025-11-18T18-09-57-0510.json → index-2025-11-25T14-14-30-0191.json} +2 -2
  7. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{target-yuv-Debug-a12ad49ff79080871bce.json → target-yuv-Debug-97f9e822dbd84a186d44.json} +2 -2
  8. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{target-yuv_shared-Debug-2b00e64621a56b8dd632.json → target-yuv_shared-Debug-3730d20e27c3481c06b2.json} +2 -2
  9. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_deps +0 -0
  10. package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_log +117 -59
  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 +109 -55
  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 -55
  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/AntiSpoofingResult.dex +0 -0
  23. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofFrameProcessor.dex +0 -0
  24. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.dex +0 -0
  25. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced.dex +0 -0
  26. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder$ImageFormat.dex +0 -0
  27. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder.dex +0 -0
  28. package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/YuvToFloatArrayConverter.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 +62 -1
  31. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_456_timing.txt +9 -0
  32. package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +58 -1
  33. package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_1740_timing.txt → generate_cxx_metadata_455_timing.txt} +1 -1
  34. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +54 -1
  35. package/android/build/intermediates/cxx/Debug/2a1it684/logs/{armeabi-v7a/generate_cxx_metadata_546_timing.txt → x86/generate_cxx_metadata_455_timing.txt} +1 -1
  36. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +54 -1
  37. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_455_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/AntiSpoofingResult.class +0 -0
  40. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
  41. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  42. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  43. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
  44. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder.class +0 -0
  45. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/YuvToFloatArrayConverter.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/AntiSpoofingResult.class +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/YuvToFloatArrayConverter.class +0 -0
  65. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +26 -6
  66. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +51 -20
  67. package/android/src/main/java/com/faceantispoof/ImageFormatEncoder.kt +217 -34
  68. package/android/src/main/java/com/faceantispoof/YuvToFloatArrayConverter.kt +103 -12
  69. package/package.json +1 -1
  70. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_1740_timing.txt +0 -9
  71. package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_546_timing.txt +0 -10
  72. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_1740_timing.txt +0 -3
  73. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_548_timing.txt +0 -3
  74. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_1740_timing.txt +0 -9
  75. package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_548_timing.txt +0 -9
  76. /package/android/.cxx/Debug/2a1it684/arm64-v8a/.cmake/api/v1/reply/{index-2025-11-24T15-42-26-0120.json → index-2025-11-25T14-48-56-0722.json} +0 -0
  77. /package/android/.cxx/Debug/2a1it684/x86/.cmake/api/v1/reply/{index-2025-11-18T18-10-04-0841.json → index-2025-11-25T14-14-37-0548.json} +0 -0
  78. /package/android/.cxx/Debug/2a1it684/x86_64/.cmake/api/v1/reply/{index-2025-11-24T15-42-27-0064.json → index-2025-11-25T14-49-17-0490.json} +0 -0
@@ -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,
@@ -88,29 +96,46 @@ object ImageFormatEncoder {
88
96
  return try {
89
97
  validateImageDimensions(width, height)
90
98
 
99
+ if (rgbFloats.isEmpty()) {
100
+ Log.e(TAG, "RGB float array is empty")
101
+ return null
102
+ }
103
+
104
+ val expectedSize = width * height * 3
105
+ if (rgbFloats.size < expectedSize) {
106
+ Log.e(TAG, "RGB float array too small: expected $expectedSize, got ${rgbFloats.size}")
107
+ return null
108
+ }
109
+
91
110
  // Convert float array (normalized 0-1) to byte array (0-255)
92
- val rgbBytes = ByteArray(width * height * 3)
111
+ val rgbBytes = ByteArray(expectedSize)
93
112
  var idx = 0
94
- for (i in rgbFloats.indices) {
95
- rgbBytes[idx++] = (rgbFloats[i] * 255f).toInt().coerceIn(0, 255).toByte()
113
+ for (i in 0 until expectedSize) {
114
+ if (idx >= rgbFloats.size) {
115
+ Log.e(TAG, "Float array index out of bounds at $idx")
116
+ return null
117
+ }
118
+ val floatVal = rgbFloats[idx++].coerceIn(0f, 1f)
119
+ rgbBytes[i] = (floatVal * 255f).toInt().coerceIn(0, 255).toByte()
96
120
  }
97
121
 
98
122
  rgbToBase64(rgbBytes, width, height, format, quality)
99
123
  } catch (e: Exception) {
100
- 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)
101
125
  null
102
126
  }
103
127
  }
104
128
 
105
129
  /**
106
130
  * Convert RGB data to raw image bytes (not Base64).
131
+ * Performs RGB→BGR conversion for OpenCV compatibility.
107
132
  *
108
133
  * @param rgbData RGB data as byte array
109
134
  * @param width Image width
110
135
  * @param height Image height
111
136
  * @param format Target image format
112
137
  * @param quality Compression quality for JPEG
113
- * @return Raw image bytes, or null if encoding fails
138
+ * @return Raw image bytes in BGR format, or null if encoding fails
114
139
  */
115
140
  fun rgbToImageBytes(
116
141
  rgbData: ByteArray,
@@ -123,16 +148,12 @@ object ImageFormatEncoder {
123
148
  validateImageDimensions(width, height)
124
149
  validateRgbData(rgbData, width, height)
125
150
 
126
- val bitmap = createBitmapFromRgb(rgbData, width, height)
127
- val imageBytes = when (format) {
128
- ImageFormat.JPEG -> compressBitmapToBytes(bitmap, format, quality)
129
- ImageFormat.PNG -> compressBitmapToBytes(bitmap, format, 100)
130
- ImageFormat.BMP -> compressBitmapToBytes(bitmap, format, 100)
131
- }
132
- bitmap.recycle()
133
- imageBytes
151
+ // CRITICAL: Convert RGB to BGR for OpenCV compatibility
152
+ val bgrData = rgbToBgr(rgbData)
153
+
154
+ bgrToImageBytes(bgrData, width, height, format, quality)
134
155
  } catch (e: Exception) {
135
- 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)
136
157
  null
137
158
  }
138
159
  }
@@ -196,21 +217,164 @@ object ImageFormatEncoder {
196
217
 
197
218
  // ============ Private Helper Methods ============
198
219
 
220
+ /**
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...
226
+ */
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? {
265
+ return try {
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")
308
+ return null
309
+ }
310
+ if (width <= 0 || height <= 0) {
311
+ Log.e(TAG, "Invalid dimensions: width=$width, height=$height")
312
+ return null
313
+ }
314
+
315
+ val expectedSize = width * height * 3
316
+ if (bgrData.size < expectedSize) {
317
+ Log.e(TAG, "BGR data too small: expected at least $expectedSize, got ${bgrData.size}")
318
+ return null
319
+ }
320
+
321
+ // Convert BGR to ARGB for Bitmap (Android uses ARGB_8888)
322
+ // BGR order: B, G, R
323
+ val pixels = IntArray(width * height)
324
+ var bgrIdx = 0
325
+ for (i in pixels.indices) {
326
+ if (bgrIdx + 2 >= bgrData.size) {
327
+ Log.e(TAG, "BGR buffer index out of bounds at pixel $i")
328
+ return null
329
+ }
330
+ val b = bgrData[bgrIdx++].toInt() and 0xFF
331
+ val g = bgrData[bgrIdx++].toInt() and 0xFF
332
+ val r = bgrData[bgrIdx++].toInt() and 0xFF
333
+ pixels[i] = 0xFF000000.toInt() or (r shl 16) or (g shl 8) or b // ARGB
334
+ }
335
+
336
+ val bitmap = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
337
+ if (bitmap == null) {
338
+ Log.e(TAG, "Failed to create bitmap from BGR")
339
+ return null
340
+ }
341
+ bitmap
342
+ } catch (e: Exception) {
343
+ Log.e(TAG, "Error creating bitmap from BGR: ${e.message}", e)
344
+ null
345
+ }
346
+ }
347
+
199
348
  /**
200
349
  * Create Bitmap from RGB byte array.
201
350
  * RGB format: RGBRGBRGB... (3 bytes per pixel, no alpha)
351
+ * DEPRECATED: Use createBitmapFromBgr instead for OpenCV compatibility
202
352
  */
203
- private fun createBitmapFromRgb(rgbData: ByteArray, width: Int, height: Int): Bitmap {
204
- // Convert RGB to ARGB for Bitmap (Android uses ARGB_8888)
205
- val pixels = IntArray(width * height)
206
- var rgbIdx = 0
207
- for (i in pixels.indices) {
208
- val r = rgbData[rgbIdx++].toInt() and 0xFF
209
- val g = rgbData[rgbIdx++].toInt() and 0xFF
210
- val b = rgbData[rgbIdx++].toInt() and 0xFF
211
- pixels[i] = 0xFF000000.toInt() or (r shl 16) or (g shl 8) or b // ARGB
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)
374
+ } catch (e: Exception) {
375
+ Log.e(TAG, "Error creating bitmap from RGB: ${e.message}", e)
376
+ null
212
377
  }
213
- return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
214
378
  }
215
379
 
216
380
  /**
@@ -221,9 +385,28 @@ object ImageFormatEncoder {
221
385
  format: ImageFormat,
222
386
  quality: Int
223
387
  ): ByteArray {
224
- val outputStream = ByteArrayOutputStream()
225
- bitmap.compress(format.compressFormat, quality, outputStream)
226
- return outputStream.toByteArray()
388
+ return try {
389
+ if (bitmap.width <= 0 || bitmap.height <= 0) {
390
+ throw IllegalArgumentException("Invalid bitmap dimensions: ${bitmap.width}×${bitmap.height}")
391
+ }
392
+
393
+ val outputStream = ByteArrayOutputStream()
394
+ val compressed = bitmap.compress(format.compressFormat, quality, outputStream)
395
+
396
+ if (!compressed) {
397
+ Log.w(TAG, "Bitmap compression returned false for ${format.name}")
398
+ }
399
+
400
+ val bytes = outputStream.toByteArray()
401
+ if (bytes.isEmpty()) {
402
+ Log.w(TAG, "Compressed image bytes are empty")
403
+ }
404
+
405
+ bytes
406
+ } catch (e: Exception) {
407
+ Log.e(TAG, "Error compressing bitmap: ${e.message}", e)
408
+ ByteArray(0)
409
+ }
227
410
  }
228
411
 
229
412
  /**
@@ -27,28 +27,55 @@ class YuvToFloatArrayConverter {
27
27
  targetHeight: Int = height,
28
28
  outputRgbFloat: FloatArray? = null
29
29
  ): FloatArray {
30
+ if (nv21.isEmpty()) {
31
+ throw IllegalArgumentException("NV21 array is empty")
32
+ }
33
+ if (width <= 0 || height <= 0) {
34
+ throw IllegalArgumentException("Invalid frame dimensions: width=$width, height=$height")
35
+ }
36
+ if (targetWidth <= 0 || targetHeight <= 0) {
37
+ throw IllegalArgumentException("Invalid target dimensions: targetWidth=$targetWidth, targetHeight=$targetHeight")
38
+ }
39
+
30
40
  val outputSize = targetWidth * targetHeight * 3
31
41
  val output = outputRgbFloat?.takeIf { it.size >= outputSize } ?: FloatArray(outputSize)
32
42
 
33
43
  val ySize = width * height
34
44
  val uvPixelStride = 2
45
+
46
+ // Validate NV21 buffer size
47
+ val minExpectedSize = width * height + (width * height) / 2
48
+ if (nv21.size < minExpectedSize) {
49
+ throw IllegalArgumentException("NV21 buffer too small: expected at least $minExpectedSize, got ${nv21.size}")
50
+ }
35
51
 
36
52
  var outIdx = 0
37
53
 
38
54
  for (targetY in 0 until targetHeight) {
39
- // Map target Y to original frame Y (nearest-neighbor)
40
55
  val srcY = (targetY * height) / targetHeight
56
+ if (srcY >= height) {
57
+ throw IndexOutOfBoundsException("Source Y index out of bounds: srcY=$srcY, height=$height")
58
+ }
41
59
  val srcRowOffset = srcY * width
42
60
 
43
61
  for (targetX in 0 until targetWidth) {
44
- // Map target X to original frame X (nearest-neighbor)
45
62
  val srcX = (targetX * width) / targetWidth
63
+ if (srcX >= width) {
64
+ throw IndexOutOfBoundsException("Source X index out of bounds: srcX=$srcX, width=$width")
65
+ }
46
66
 
47
67
  // Get Y component
48
- val yVal = nv21[srcRowOffset + srcX].toInt() and 0xFF
68
+ val yIdx = srcRowOffset + srcX
69
+ if (yIdx >= nv21.size) {
70
+ throw IndexOutOfBoundsException("Y index out of bounds: yIdx=$yIdx, nv21.size=${nv21.size}")
71
+ }
72
+ val yVal = nv21[yIdx].toInt() and 0xFF
49
73
 
50
74
  // Get U and V components (NV21 layout: UV interleaved in second half)
51
75
  val uvIndex = ySize + (srcY / 2) * width + (srcX / 2) * uvPixelStride
76
+ if (uvIndex + 1 >= nv21.size) {
77
+ throw IndexOutOfBoundsException("UV index out of bounds: uvIndex=$uvIndex, nv21.size=${nv21.size}")
78
+ }
52
79
  val vVal = nv21[uvIndex].toInt() and 0xFF
53
80
  val uVal = nv21[uvIndex + 1].toInt() and 0xFF
54
81
 
@@ -62,9 +89,13 @@ class YuvToFloatArrayConverter {
62
89
  val b = (yf + 1.772f * uf).coerceIn(0f, 255f)
63
90
 
64
91
  // Normalize to [0, 1]
65
- output[outIdx++] = r / 255f
66
- output[outIdx++] = g / 255f
67
- output[outIdx++] = b / 255f
92
+ if (outIdx + 2 < output.size) {
93
+ output[outIdx++] = r / 255f
94
+ output[outIdx++] = g / 255f
95
+ output[outIdx++] = b / 255f
96
+ } else {
97
+ throw IndexOutOfBoundsException("Output array index out of bounds: outIdx=$outIdx, output.size=${output.size}")
98
+ }
68
99
  }
69
100
  }
70
101
 
@@ -176,20 +207,48 @@ class YuvToFloatArrayConverter {
176
207
  targetHeight: Int = height,
177
208
  outputGrayscale: IntArray? = null
178
209
  ): IntArray {
210
+ if (nv21.isEmpty()) {
211
+ throw IllegalArgumentException("NV21 array is empty")
212
+ }
213
+ if (width <= 0 || height <= 0) {
214
+ throw IllegalArgumentException("Invalid frame dimensions: width=$width, height=$height")
215
+ }
216
+ if (targetWidth <= 0 || targetHeight <= 0) {
217
+ throw IllegalArgumentException("Invalid target dimensions: targetWidth=$targetWidth, targetHeight=$targetHeight")
218
+ }
219
+
179
220
  val outputSize = targetWidth * targetHeight
180
221
  val output = outputGrayscale?.takeIf { it.size >= outputSize } ?: IntArray(outputSize)
181
222
 
182
223
  val ySize = width * height
224
+ val minExpectedSize = width * height + (width * height) / 2
225
+ if (nv21.size < minExpectedSize) {
226
+ throw IllegalArgumentException("NV21 buffer too small: expected at least $minExpectedSize, got ${nv21.size}")
227
+ }
183
228
 
184
229
  var outIdx = 0
185
230
  for (targetY in 0 until targetHeight) {
186
231
  val srcY = (targetY * height) / targetHeight
232
+ if (srcY >= height) {
233
+ throw IndexOutOfBoundsException("Source Y out of bounds: srcY=$srcY, height=$height")
234
+ }
187
235
  val srcRowOffset = srcY * width
188
236
 
189
237
  for (targetX in 0 until targetWidth) {
190
238
  val srcX = (targetX * width) / targetWidth
191
- val yVal = nv21[srcRowOffset + srcX].toInt() and 0xFF
192
- output[outIdx++] = yVal
239
+ if (srcX >= width) {
240
+ throw IndexOutOfBoundsException("Source X out of bounds: srcX=$srcX, width=$width")
241
+ }
242
+ val yIdx = srcRowOffset + srcX
243
+ if (yIdx >= nv21.size) {
244
+ throw IndexOutOfBoundsException("Y index out of bounds: yIdx=$yIdx, nv21.size=${nv21.size}")
245
+ }
246
+ val yVal = nv21[yIdx].toInt() and 0xFF
247
+ if (outIdx < output.size) {
248
+ output[outIdx++] = yVal
249
+ } else {
250
+ throw IndexOutOfBoundsException("Output index out of bounds: outIdx=$outIdx, output.size=${output.size}")
251
+ }
193
252
  }
194
253
  }
195
254
 
@@ -217,26 +276,54 @@ class YuvToFloatArrayConverter {
217
276
  targetHeight: Int = height,
218
277
  outputRgbBytes: ByteArray? = null
219
278
  ): ByteArray {
279
+ if (nv21.isEmpty()) {
280
+ throw IllegalArgumentException("NV21 array is empty")
281
+ }
282
+ if (width <= 0 || height <= 0) {
283
+ throw IllegalArgumentException("Invalid frame dimensions: width=$width, height=$height")
284
+ }
285
+ if (targetWidth <= 0 || targetHeight <= 0) {
286
+ throw IllegalArgumentException("Invalid target dimensions: targetWidth=$targetWidth, targetHeight=$targetHeight")
287
+ }
288
+
220
289
  val outputSize = targetWidth * targetHeight * 3
221
290
  val output = outputRgbBytes?.takeIf { it.size >= outputSize } ?: ByteArray(outputSize)
222
291
 
223
292
  val ySize = width * height
224
293
  val uvPixelStride = 2
294
+
295
+ val minExpectedSize = width * height + (width * height) / 2
296
+ if (nv21.size < minExpectedSize) {
297
+ throw IllegalArgumentException("NV21 buffer too small: expected at least $minExpectedSize, got ${nv21.size}")
298
+ }
225
299
 
226
300
  var outIdx = 0
227
301
 
228
302
  for (targetY in 0 until targetHeight) {
229
303
  val srcY = (targetY * height) / targetHeight
304
+ if (srcY >= height) {
305
+ throw IndexOutOfBoundsException("Source Y out of bounds: srcY=$srcY, height=$height")
306
+ }
230
307
  val srcRowOffset = srcY * width
231
308
 
232
309
  for (targetX in 0 until targetWidth) {
233
310
  val srcX = (targetX * width) / targetWidth
311
+ if (srcX >= width) {
312
+ throw IndexOutOfBoundsException("Source X out of bounds: srcX=$srcX, width=$width")
313
+ }
234
314
 
235
315
  // Get Y component
236
- val yVal = nv21[srcRowOffset + srcX].toInt() and 0xFF
316
+ val yIdx = srcRowOffset + srcX
317
+ if (yIdx >= nv21.size) {
318
+ throw IndexOutOfBoundsException("Y index out of bounds: yIdx=$yIdx, nv21.size=${nv21.size}")
319
+ }
320
+ val yVal = nv21[yIdx].toInt() and 0xFF
237
321
 
238
322
  // Get U and V components (NV21 layout: UV interleaved in second half)
239
323
  val uvIndex = ySize + (srcY / 2) * width + (srcX / 2) * uvPixelStride
324
+ if (uvIndex + 1 >= nv21.size) {
325
+ throw IndexOutOfBoundsException("UV index out of bounds: uvIndex=$uvIndex, nv21.size=${nv21.size}")
326
+ }
240
327
  val vVal = nv21[uvIndex].toInt() and 0xFF
241
328
  val uVal = nv21[uvIndex + 1].toInt() and 0xFF
242
329
 
@@ -249,9 +336,13 @@ class YuvToFloatArrayConverter {
249
336
  val g = (yf - 0.34414f * uf - 0.71414f * vf).coerceIn(0f, 255f).toInt().toByte()
250
337
  val b = (yf + 1.772f * uf).coerceIn(0f, 255f).toInt().toByte()
251
338
 
252
- output[outIdx++] = r
253
- output[outIdx++] = g
254
- output[outIdx++] = b
339
+ if (outIdx + 2 < output.size) {
340
+ output[outIdx++] = r
341
+ output[outIdx++] = g
342
+ output[outIdx++] = b
343
+ } else {
344
+ throw IndexOutOfBoundsException("Output index out of bounds: outIdx=$outIdx, output.size=${output.size}")
345
+ }
255
346
  }
256
347
  }
257
348
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-vision-camera-spoof-detector",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "High-performance face anti-spoofing and liveness detection module for React Native Vision Camera. Uses TensorFlow Lite with GPU acceleration and optimized YUV processing.",
5
5
  "homepage": "https://github.com/jescon-tech/react-native-vision-camera-spoof-detector",
6
6
  "repository": {
@@ -1,9 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- exec-configure 616ms
5
- [gap of 22ms]
6
- execute-generate-process completed in 638ms
7
- [gap of 16ms]
8
- generate_cxx_metadata completed in 661ms
9
-
@@ -1,10 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- [gap of 56ms]
5
- exec-configure 1633ms
6
- [gap of 33ms]
7
- execute-generate-process completed in 1722ms
8
- [gap of 24ms]
9
- generate_cxx_metadata completed in 1868ms
10
-
@@ -1,3 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata 13ms
3
-
@@ -1,3 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata 41ms
3
-
@@ -1,9 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- exec-configure 531ms
5
- [gap of 19ms]
6
- execute-generate-process completed in 552ms
7
- [gap of 16ms]
8
- generate_cxx_metadata completed in 575ms
9
-
@@ -1,9 +0,0 @@
1
- # C/C++ build system timings
2
- generate_cxx_metadata
3
- execute-generate-process
4
- exec-configure 610ms
5
- [gap of 37ms]
6
- execute-generate-process completed in 648ms
7
- [gap of 16ms]
8
- generate_cxx_metadata completed in 674ms
9
-