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.
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/arm64-v8a/.ninja_log +124 -62
- package/android/.cxx/Debug/2a1it684/arm64-v8a/CMakeFiles/CMakeOutput.log +22 -22
- package/android/.cxx/Debug/2a1it684/arm64-v8a/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{codemodel-v2-053850119b681f93cecc.json → codemodel-v2-52ac1d8b40b69596389b.json} +2 -2
- 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
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.cmake/api/v1/reply/{target-yuv-Debug-a12ad49ff79080871bce.json → target-yuv-Debug-97f9e822dbd84a186d44.json} +2 -2
- 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
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/.ninja_log +117 -59
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/armeabi-v7a/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/x86/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/x86/.ninja_log +109 -55
- package/android/.cxx/Debug/2a1it684/x86/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/x86/configure_fingerprint.bin +15 -15
- package/android/.cxx/Debug/2a1it684/x86_64/.ninja_deps +0 -0
- package/android/.cxx/Debug/2a1it684/x86_64/.ninja_log +109 -55
- package/android/.cxx/Debug/2a1it684/x86_64/CMakeFiles/CMakeOutput.log +20 -20
- package/android/.cxx/Debug/2a1it684/x86_64/configure_fingerprint.bin +15 -15
- package/android/build/.transforms/4ebf3dd0dca4f83fe1776360e21ded81/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/AntiSpoofingResult.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofFrameProcessor.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/FaceAntiSpoofingAdvanced.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder$ImageFormat.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/ImageFormatEncoder.dex +0 -0
- package/android/build/.transforms/c0a07cd9442d741ad3cef3760159211f/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/faceantispoof/YuvToFloatArrayConverter.dex +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/build_stdout_targets.txt +62 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_456_timing.txt +9 -0
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/build_stdout_targets.txt +58 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/armeabi-v7a/{generate_cxx_metadata_1740_timing.txt → generate_cxx_metadata_455_timing.txt} +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/build_stdout_targets.txt +54 -1
- 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
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/build_stdout_targets.txt +54 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_455_timing.txt +9 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/AntiSpoofingResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/ImageFormatEncoder.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/faceantispoof/YuvToFloatArrayConverter.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/AntiSpoofingResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofFrameProcessor.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced$SpoofDetectionResult.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/FaceAntiSpoofingAdvanced.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder$ImageFormat.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/ImageFormatEncoder.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/faceantispoof/YuvToFloatArrayConverter.class +0 -0
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +26 -6
- package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +51 -20
- package/android/src/main/java/com/faceantispoof/ImageFormatEncoder.kt +217 -34
- package/android/src/main/java/com/faceantispoof/YuvToFloatArrayConverter.kt +103 -12
- package/package.json +1 -1
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_1740_timing.txt +0 -9
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/arm64-v8a/generate_cxx_metadata_546_timing.txt +0 -10
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_1740_timing.txt +0 -3
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86/generate_cxx_metadata_548_timing.txt +0 -3
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_1740_timing.txt +0 -9
- package/android/build/intermediates/cxx/Debug/2a1it684/logs/x86_64/generate_cxx_metadata_548_timing.txt +0 -9
- /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
- /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
- /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
|
|
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
|
|
40
|
-
*
|
|
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
|
-
|
|
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(
|
|
111
|
+
val rgbBytes = ByteArray(expectedSize)
|
|
93
112
|
var idx = 0
|
|
94
|
-
for (i in
|
|
95
|
-
|
|
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
|
-
|
|
127
|
-
val
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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.
|
|
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": {
|
|
File without changes
|
|
File without changes
|