@trustchex/react-native-sdk 1.355.1 → 1.357.0
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/README.md +2 -9
- package/TrustchexSDK.podspec +5 -4
- package/android/build.gradle +6 -4
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +45 -25
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +168 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +871 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +245 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +785 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +419 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +818 -0
- package/ios/Camera/TrustchexCameraManager.m +37 -0
- package/ios/Camera/TrustchexCameraManager.swift +125 -0
- package/ios/Camera/TrustchexCameraView.swift +1176 -0
- package/ios/MLKit/MLKitModule.m +23 -0
- package/ios/MLKit/MLKitModule.swift +250 -0
- package/ios/MRZValidation.m +39 -0
- package/ios/MRZValidation.swift +802 -0
- package/ios/MRZValidator.swift +466 -0
- package/ios/OpenCV/OpenCVModule.h +4 -0
- package/ios/OpenCV/OpenCVModule.mm +810 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +2 -3
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +1 -2
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +418 -193
- package/lib/module/Screens/Static/OTPVerificationScreen.js +11 -11
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +5 -1
- package/lib/module/Screens/Static/ResultScreen.js +25 -2
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +25 -7
- package/lib/module/Shared/Components/DebugNavigationPanel.js +234 -24
- package/lib/module/Shared/Components/EIDScanner.js +99 -9
- package/lib/module/Shared/Components/FaceCamera.js +170 -179
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +2151 -771
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +109 -107
- package/lib/module/Shared/Components/TrustchexCamera.js +122 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +91 -0
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +2 -124
- package/lib/module/Shared/EIDReader/tlv/tlvInputStream.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputState.js +4 -4
- package/lib/module/Shared/EIDReader/tlv/tlvOutputStream.js +4 -4
- package/lib/module/Shared/Libs/analytics.utils.js +2 -2
- package/lib/module/Shared/Libs/debug.utils.js +132 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +6 -5
- package/lib/module/Shared/Libs/demo.utils.js +13 -3
- package/lib/module/Shared/Libs/mrz.utils.js +1 -175
- package/lib/module/Shared/Libs/native-device-info.utils.js +12 -6
- package/lib/module/Shared/Libs/tts.utils.js +40 -6
- package/lib/module/Shared/Services/AnalyticsService.js +9 -8
- package/lib/module/Shared/Types/mrzFields.js +1 -0
- package/lib/module/Translation/Resources/en.js +87 -88
- package/lib/module/Translation/Resources/tr.js +84 -85
- package/lib/module/Trustchex.js +9 -2
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +2 -2
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +18 -4
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -4
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts +2 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +124 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts +11 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts +2 -39
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts +42 -0
- package/lib/typescript/src/Shared/Libs/debug.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +0 -4
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts +4 -3
- package/lib/typescript/src/Shared/Libs/tts.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +2 -2
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts +11 -0
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +4 -5
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +2 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +4 -35
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +7 -5
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +2 -3
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +498 -216
- package/src/Screens/Static/OTPVerificationScreen.tsx +37 -31
- package/src/Screens/Static/QrCodeScanningScreen.tsx +8 -1
- package/src/Screens/Static/ResultScreen.tsx +136 -88
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +46 -13
- package/src/Shared/Components/DebugNavigationPanel.tsx +290 -34
- package/src/Shared/Components/EIDScanner.tsx +94 -16
- package/src/Shared/Components/FaceCamera.tsx +236 -203
- package/src/Shared/Components/IdentityDocumentCamera.tsx +3073 -1030
- package/src/Shared/Components/QrCodeScannerCamera.tsx +133 -127
- package/src/Shared/Components/TrustchexCamera.tsx +289 -0
- package/src/Shared/Config/camera-enhancement.config.ts +2 -2
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +96 -0
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +2 -125
- package/src/Shared/EIDReader/tlv/tlvInputStream.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputState.ts +4 -4
- package/src/Shared/EIDReader/tlv/tlvOutputStream.ts +4 -4
- package/src/Shared/Libs/analytics.utils.ts +48 -20
- package/src/Shared/Libs/debug.utils.ts +149 -0
- package/src/Shared/Libs/deeplink.utils.ts +7 -5
- package/src/Shared/Libs/demo.utils.ts +4 -0
- package/src/Shared/Libs/http-client.ts +12 -8
- package/src/Shared/Libs/mrz.utils.ts +1 -163
- package/src/Shared/Libs/native-device-info.utils.ts +12 -6
- package/src/Shared/Libs/tts.utils.ts +48 -6
- package/src/Shared/Services/AnalyticsService.ts +69 -24
- package/src/Shared/Types/identificationInfo.ts +2 -2
- package/src/Shared/Types/mrzFields.ts +29 -0
- package/src/Translation/Resources/en.ts +90 -100
- package/src/Translation/Resources/tr.ts +89 -97
- package/src/Translation/index.ts +1 -1
- package/src/Trustchex.tsx +21 -4
- package/src/index.tsx +14 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/barcodescanner/BarcodeScannerFrameProcessorPlugin.kt +0 -301
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/BitmapUtils.kt +0 -205
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/CropperPlugin.kt +0 -72
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/FrameMetadata.kt +0 -4
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/facedetector/FaceDetectorFrameProcessorPlugin.kt +0 -303
- package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/textrecognition/TextRecognitionFrameProcessorPlugin.kt +0 -115
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin-Bridging-Header.h +0 -9
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.swift +0 -188
- package/ios/VisionCameraPlugins/Cropper/Cropper-Bridging-Header.h +0 -13
- package/ios/VisionCameraPlugins/Cropper/Cropper.h +0 -20
- package/ios/VisionCameraPlugins/Cropper/Cropper.mm +0 -22
- package/ios/VisionCameraPlugins/Cropper/Cropper.swift +0 -145
- package/ios/VisionCameraPlugins/Cropper/CropperUtils.swift +0 -49
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.mm +0 -22
- package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.swift +0 -320
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin-Bridging-Header.h +0 -4
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.mm +0 -27
- package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.swift +0 -144
- package/lib/module/Shared/Libs/camera.utils.js +0 -308
- package/lib/module/Shared/Libs/frame-enhancement.utils.js +0 -133
- package/lib/module/Shared/Libs/opencv.utils.js +0 -21
- package/lib/module/Shared/Libs/worklet.utils.js +0 -68
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.js +0 -46
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/index.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.js +0 -26
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.js +0 -197
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.js +0 -101
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.js +0 -60
- package/lib/module/Shared/VisionCameraPlugins/Cropper/index.js +0 -47
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/Camera.js +0 -42
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/detectFaces.js +0 -35
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/index.js +0 -4
- package/lib/module/Shared/VisionCameraPlugins/FaceDetector/types.js +0 -3
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/Camera.js +0 -56
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.js +0 -9
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/index.js +0 -6
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/scanText.js +0 -20
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/translateText.js +0 -19
- package/lib/module/Shared/VisionCameraPlugins/TextRecognition/types.js +0 -3
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +0 -87
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +0 -25
- package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts +0 -3
- package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts +0 -9
- package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts +0 -13
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts +0 -12
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts +0 -52
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts +0 -62
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts +0 -34
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts +0 -32
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts +0 -23
- package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts +0 -9
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts +0 -79
- package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts +0 -6
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts +0 -5
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts +0 -3
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts.map +0 -1
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts +0 -67
- package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts.map +0 -1
- package/src/Shared/Libs/camera.utils.ts +0 -345
- package/src/Shared/Libs/frame-enhancement.utils.ts +0 -217
- package/src/Shared/Libs/opencv.utils.ts +0 -40
- package/src/Shared/Libs/worklet.utils.ts +0 -72
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.ts +0 -79
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +0 -46
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/index.ts +0 -60
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.ts +0 -32
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/types.ts +0 -82
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.ts +0 -195
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.ts +0 -135
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.ts +0 -84
- package/src/Shared/VisionCameraPlugins/Cropper/index.ts +0 -78
- package/src/Shared/VisionCameraPlugins/FaceDetector/Camera.tsx +0 -63
- package/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.ts +0 -44
- package/src/Shared/VisionCameraPlugins/FaceDetector/index.ts +0 -3
- package/src/Shared/VisionCameraPlugins/FaceDetector/types.ts +0 -99
- package/src/Shared/VisionCameraPlugins/TextRecognition/Camera.tsx +0 -76
- package/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.ts +0 -18
- package/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/index.ts +0 -7
- package/src/Shared/VisionCameraPlugins/TextRecognition/scanText.ts +0 -27
- package/src/Shared/VisionCameraPlugins/TextRecognition/translateText.ts +0 -21
- package/src/Shared/VisionCameraPlugins/TextRecognition/types.ts +0 -141
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
package com.trustchex.reactnativesdk.opencv
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.BitmapFactory
|
|
5
|
+
import android.util.Base64
|
|
6
|
+
import com.facebook.react.bridge.*
|
|
7
|
+
import org.opencv.android.OpenCVLoader
|
|
8
|
+
import org.opencv.android.Utils
|
|
9
|
+
import org.opencv.core.*
|
|
10
|
+
import org.opencv.imgproc.Imgproc
|
|
11
|
+
import java.io.ByteArrayOutputStream
|
|
12
|
+
|
|
13
|
+
class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
14
|
+
|
|
15
|
+
override fun getName() = "OpenCVModule"
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
private var opencvInitialized = false
|
|
19
|
+
private const val HOLOGRAM_NON_ZERO_THRESHOLD = 600
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
init {
|
|
23
|
+
if (!opencvInitialized) {
|
|
24
|
+
opencvInitialized = OpenCVLoader.initLocal()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Detect hologram from a sequence of face images captured while torch is toggling.
|
|
30
|
+
* Mirrors the JS detectHologram logic:
|
|
31
|
+
* 1. Compute absdiff between consecutive frames
|
|
32
|
+
* 2. Convert to HSV, inRange filter for holographic colors
|
|
33
|
+
* 3. Blend significant diffs
|
|
34
|
+
* 4. Adaptive threshold on combined mask
|
|
35
|
+
* 5. Return hologram overlay + mask if non-zero count exceeds threshold
|
|
36
|
+
*
|
|
37
|
+
* @param base64Images Array of base64 JPEG face images
|
|
38
|
+
* @param threshold Non-zero pixel count threshold (default 3500)
|
|
39
|
+
* @param promise Returns { hologramImage, hologramMask } or null
|
|
40
|
+
*/
|
|
41
|
+
@ReactMethod
|
|
42
|
+
fun detectHologram(base64Images: ReadableArray, threshold: Int, promise: Promise) {
|
|
43
|
+
// Run on background thread to avoid blocking the main/JS thread (matching iOS)
|
|
44
|
+
Thread {
|
|
45
|
+
try {
|
|
46
|
+
if (!opencvInitialized) {
|
|
47
|
+
promise.reject("OPENCV_ERROR", "OpenCV not initialized")
|
|
48
|
+
return@Thread
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
val imageCount = base64Images.size()
|
|
52
|
+
if (imageCount < 2) {
|
|
53
|
+
promise.resolve(null)
|
|
54
|
+
return@Thread
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Decode all images to Mat
|
|
58
|
+
val mats = mutableListOf<Mat>()
|
|
59
|
+
for (i in 0 until imageCount) {
|
|
60
|
+
val b64 = base64Images.getString(i) ?: continue
|
|
61
|
+
val mat = base64ToMat(b64) ?: continue
|
|
62
|
+
mats.add(mat)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (mats.size < 2) {
|
|
66
|
+
mats.forEach { it.release() }
|
|
67
|
+
promise.resolve(null)
|
|
68
|
+
return@Thread
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Multi-range HSV filtering for holographic colors
|
|
72
|
+
// Range 1: Cyan-green holographic reflections
|
|
73
|
+
val lowerBound1 = Scalar(35.0, 80.0, 80.0)
|
|
74
|
+
val upperBound1 = Scalar(85.0, 255.0, 255.0)
|
|
75
|
+
// Range 2: Blue-violet holographic reflections
|
|
76
|
+
val lowerBound2 = Scalar(100.0, 80.0, 80.0)
|
|
77
|
+
val upperBound2 = Scalar(160.0, 255.0, 255.0)
|
|
78
|
+
val diffs = mutableListOf<Mat>()
|
|
79
|
+
val brightestImages = mutableListOf<Mat>()
|
|
80
|
+
|
|
81
|
+
for (i in 0 until mats.size - 1) {
|
|
82
|
+
val diff = Mat()
|
|
83
|
+
Core.absdiff(mats[i], mats[i + 1], diff)
|
|
84
|
+
|
|
85
|
+
val hsv = Mat()
|
|
86
|
+
Imgproc.cvtColor(diff, hsv, Imgproc.COLOR_RGB2HSV)
|
|
87
|
+
|
|
88
|
+
// Apply multi-range HSV filtering
|
|
89
|
+
val mask1 = Mat()
|
|
90
|
+
val mask2 = Mat()
|
|
91
|
+
val mask = Mat()
|
|
92
|
+
Core.inRange(hsv, lowerBound1, upperBound1, mask1)
|
|
93
|
+
Core.inRange(hsv, lowerBound2, upperBound2, mask2)
|
|
94
|
+
Core.bitwise_or(mask1, mask2, mask)
|
|
95
|
+
mask1.release()
|
|
96
|
+
mask2.release()
|
|
97
|
+
|
|
98
|
+
val maskNonZero = Core.countNonZero(mask)
|
|
99
|
+
|
|
100
|
+
if (maskNonZero > HOLOGRAM_NON_ZERO_THRESHOLD) {
|
|
101
|
+
diffs.add(mask)
|
|
102
|
+
brightestImages.add(mats[i].clone())
|
|
103
|
+
brightestImages.add(mats[i + 1].clone())
|
|
104
|
+
|
|
105
|
+
// Early termination: if first pair has very strong signal, skip rest
|
|
106
|
+
if (i == 0 && maskNonZero > HOLOGRAM_NON_ZERO_THRESHOLD * 4) {
|
|
107
|
+
diff.release()
|
|
108
|
+
hsv.release()
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
mask.release()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
diff.release()
|
|
116
|
+
hsv.release()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
mats.forEach { it.release() }
|
|
120
|
+
mats.clear()
|
|
121
|
+
|
|
122
|
+
if (diffs.isEmpty()) {
|
|
123
|
+
brightestImages.forEach { it.release() }
|
|
124
|
+
promise.resolve(null)
|
|
125
|
+
return@Thread
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Combine all difference masks
|
|
129
|
+
val hologramMask = diffs[0].clone()
|
|
130
|
+
|
|
131
|
+
for (i in 1 until diffs.size) {
|
|
132
|
+
Core.bitwise_or(hologramMask, diffs[i], hologramMask)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
diffs.forEach { it.release() }
|
|
136
|
+
|
|
137
|
+
// Apply morphological operations to clean up the mask
|
|
138
|
+
val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, org.opencv.core.Size(3.0, 3.0))
|
|
139
|
+
Imgproc.morphologyEx(hologramMask, hologramMask, Imgproc.MORPH_CLOSE, kernel)
|
|
140
|
+
Imgproc.morphologyEx(hologramMask, hologramMask, Imgproc.MORPH_OPEN, kernel)
|
|
141
|
+
kernel.release()
|
|
142
|
+
|
|
143
|
+
// Check if significant hologram detected
|
|
144
|
+
val count = Core.countNonZero(hologramMask)
|
|
145
|
+
|
|
146
|
+
if (count < threshold) {
|
|
147
|
+
hologramMask.release()
|
|
148
|
+
brightestImages.forEach { it.release() }
|
|
149
|
+
promise.resolve(null)
|
|
150
|
+
return@Thread
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Find the best image that shows the most hologram features
|
|
154
|
+
// Score each image based on how much hologram variation it shows in the mask regions
|
|
155
|
+
var bestImage = brightestImages[0]
|
|
156
|
+
var maxHologramScore = 0.0
|
|
157
|
+
|
|
158
|
+
for (img in brightestImages) {
|
|
159
|
+
// Calculate hologram visibility score by measuring color variation in mask regions
|
|
160
|
+
val maskedRegion = Mat()
|
|
161
|
+
img.copyTo(maskedRegion, hologramMask)
|
|
162
|
+
|
|
163
|
+
// Convert to HSV to measure saturation (hologram has high saturation)
|
|
164
|
+
val hsv = Mat()
|
|
165
|
+
Imgproc.cvtColor(maskedRegion, hsv, Imgproc.COLOR_RGB2HSV)
|
|
166
|
+
|
|
167
|
+
val channels = mutableListOf<Mat>()
|
|
168
|
+
Core.split(hsv, channels)
|
|
169
|
+
|
|
170
|
+
// Score based on average saturation in hologram regions
|
|
171
|
+
val saturationMean = Core.mean(channels[1], hologramMask)
|
|
172
|
+
val score = saturationMean.`val`[0]
|
|
173
|
+
|
|
174
|
+
channels.forEach { it.release() }
|
|
175
|
+
hsv.release()
|
|
176
|
+
maskedRegion.release()
|
|
177
|
+
|
|
178
|
+
if (score > maxHologramScore) {
|
|
179
|
+
maxHologramScore = score
|
|
180
|
+
bestImage = img
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Use the best image that shows the most prominent hologram - show full document
|
|
185
|
+
val hologram = Mat()
|
|
186
|
+
bestImage.copyTo(hologram)
|
|
187
|
+
|
|
188
|
+
brightestImages.forEach { it.release() }
|
|
189
|
+
|
|
190
|
+
// Convert results to base64
|
|
191
|
+
val hologramBase64 = matToBase64(hologram)
|
|
192
|
+
val maskBase64 = matToBase64(hologramMask)
|
|
193
|
+
|
|
194
|
+
hologram.release()
|
|
195
|
+
hologramMask.release()
|
|
196
|
+
|
|
197
|
+
if (hologramBase64 != null && maskBase64 != null) {
|
|
198
|
+
val resultMap = Arguments.createMap()
|
|
199
|
+
resultMap.putString("hologramImage", hologramBase64)
|
|
200
|
+
resultMap.putString("hologramMask", maskBase64)
|
|
201
|
+
promise.resolve(resultMap)
|
|
202
|
+
} else {
|
|
203
|
+
promise.resolve(null)
|
|
204
|
+
}
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
promise.reject("HOLOGRAM_ERROR", e.message)
|
|
207
|
+
}
|
|
208
|
+
}.start()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Compare two images for similarity using thresholded absdiff.
|
|
213
|
+
* Mirrors the JS areImagesSimilar logic:
|
|
214
|
+
* 1. Grayscale → GaussianBlur → Otsu threshold on both
|
|
215
|
+
* 2. absdiff between thresholded images
|
|
216
|
+
* 3. Count non-zero pixels
|
|
217
|
+
*
|
|
218
|
+
* @param base64Image1 First image (base64 JPEG)
|
|
219
|
+
* @param base64Image2 Second image (base64 JPEG)
|
|
220
|
+
* @param threshold Non-zero pixel count below which images are "similar" (default 15000)
|
|
221
|
+
* @param promise Returns boolean
|
|
222
|
+
*/
|
|
223
|
+
@ReactMethod
|
|
224
|
+
fun areImagesSimilar(base64Image1: String, base64Image2: String, threshold: Int, promise: Promise) {
|
|
225
|
+
try {
|
|
226
|
+
if (!opencvInitialized) {
|
|
227
|
+
promise.resolve(false)
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
val mat1 = base64ToMat(base64Image1)
|
|
232
|
+
val mat2 = base64ToMat(base64Image2)
|
|
233
|
+
|
|
234
|
+
if (mat1 == null || mat2 == null) {
|
|
235
|
+
mat1?.release()
|
|
236
|
+
mat2?.release()
|
|
237
|
+
promise.resolve(false)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
val thresh1 = applyThreshold(mat1)
|
|
242
|
+
val thresh2 = applyThreshold(mat2)
|
|
243
|
+
|
|
244
|
+
// Resize to match if different sizes
|
|
245
|
+
if (thresh1.size() != thresh2.size()) {
|
|
246
|
+
Imgproc.resize(thresh2, thresh2, thresh1.size())
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
val diff = Mat()
|
|
250
|
+
Core.absdiff(thresh1, thresh2, diff)
|
|
251
|
+
val count = Core.countNonZero(diff)
|
|
252
|
+
|
|
253
|
+
mat1.release()
|
|
254
|
+
mat2.release()
|
|
255
|
+
thresh1.release()
|
|
256
|
+
thresh2.release()
|
|
257
|
+
diff.release()
|
|
258
|
+
|
|
259
|
+
promise.resolve(count < threshold)
|
|
260
|
+
} catch (e: Exception) {
|
|
261
|
+
promise.resolve(false)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Crop face images from a full frame using face bounds from ML Kit.
|
|
267
|
+
* Returns array of base64 face images sorted by x-position (left to right).
|
|
268
|
+
*
|
|
269
|
+
* @param base64Image Full frame as base64 JPEG
|
|
270
|
+
* @param faceBounds ReadableArray of { x, y, width, height } face bounds
|
|
271
|
+
* @param imageWidth Full frame width
|
|
272
|
+
* @param imageHeight Full frame height
|
|
273
|
+
* @param promise Returns array of base64 cropped face images (240x320)
|
|
274
|
+
*/
|
|
275
|
+
@ReactMethod
|
|
276
|
+
fun cropFaceImages(base64Image: String, faceBounds: ReadableArray, imageWidth: Int, imageHeight: Int, promise: Promise) {
|
|
277
|
+
try {
|
|
278
|
+
if (!opencvInitialized) {
|
|
279
|
+
promise.resolve(Arguments.createArray())
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
val mat = base64ToMat(base64Image)
|
|
284
|
+
if (mat == null) {
|
|
285
|
+
promise.resolve(Arguments.createArray())
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Collect bounds and sort by x position (left to right)
|
|
290
|
+
val boundsWithIndex = mutableListOf<Pair<Int, ReadableMap>>()
|
|
291
|
+
for (i in 0 until faceBounds.size()) {
|
|
292
|
+
val bounds = faceBounds.getMap(i) ?: continue
|
|
293
|
+
boundsWithIndex.add(Pair(i, bounds))
|
|
294
|
+
}
|
|
295
|
+
boundsWithIndex.sortBy { it.second.getDouble("x").toInt() }
|
|
296
|
+
|
|
297
|
+
val result = Arguments.createArray()
|
|
298
|
+
|
|
299
|
+
for ((_, bounds) in boundsWithIndex) {
|
|
300
|
+
val x = bounds.getDouble("x").toInt()
|
|
301
|
+
val y = bounds.getDouble("y").toInt()
|
|
302
|
+
val w = bounds.getDouble("width").toInt()
|
|
303
|
+
val h = bounds.getDouble("height").toInt()
|
|
304
|
+
|
|
305
|
+
if (w <= 0 || h <= 0) continue
|
|
306
|
+
|
|
307
|
+
// Add 25% padding around the face bounding box
|
|
308
|
+
val padX = (w * 0.25).toInt()
|
|
309
|
+
val padY = (h * 0.25).toInt()
|
|
310
|
+
|
|
311
|
+
// Face bounds from ML Kit are in portrait coordinates (matching the rotated JPEG)
|
|
312
|
+
val cropX = (x - padX).coerceAtLeast(0)
|
|
313
|
+
val cropY = (y - padY).coerceAtLeast(0)
|
|
314
|
+
val cropRight = (x + w + padX).coerceAtMost(mat.cols())
|
|
315
|
+
val cropBottom = (y + h + padY).coerceAtMost(mat.rows())
|
|
316
|
+
val cropW = cropRight - cropX
|
|
317
|
+
val cropH = cropBottom - cropY
|
|
318
|
+
|
|
319
|
+
if (cropW <= 0 || cropH <= 0) continue
|
|
320
|
+
|
|
321
|
+
val roi = Rect(cropX, cropY, cropW, cropH)
|
|
322
|
+
val cropped = Mat(mat, roi)
|
|
323
|
+
|
|
324
|
+
// Resize to standard 240x320
|
|
325
|
+
val resized = Mat()
|
|
326
|
+
Imgproc.resize(cropped, resized, Size(240.0, 320.0))
|
|
327
|
+
|
|
328
|
+
// Add face without blur check
|
|
329
|
+
val faceBase64 = matToBase64(resized)
|
|
330
|
+
if (faceBase64 != null) {
|
|
331
|
+
result.pushString(faceBase64)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
cropped.release()
|
|
335
|
+
resized.release()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
mat.release()
|
|
339
|
+
promise.resolve(result)
|
|
340
|
+
} catch (e: Exception) {
|
|
341
|
+
promise.resolve(Arguments.createArray())
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private fun applyThreshold(src: Mat): Mat {
|
|
346
|
+
val gray = Mat()
|
|
347
|
+
if (src.channels() > 1) {
|
|
348
|
+
Imgproc.cvtColor(src, gray, Imgproc.COLOR_RGB2GRAY)
|
|
349
|
+
} else {
|
|
350
|
+
src.copyTo(gray)
|
|
351
|
+
}
|
|
352
|
+
val ksize = Size(5.0, 5.0)
|
|
353
|
+
Imgproc.GaussianBlur(gray, gray, ksize, 0.0)
|
|
354
|
+
Imgproc.threshold(gray, gray, 0.0, 255.0, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU)
|
|
355
|
+
return gray
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Calculate average brightness of the full image.
|
|
360
|
+
* @param base64Image Base64 JPEG image
|
|
361
|
+
* @param promise Returns average brightness value (0-255)
|
|
362
|
+
*/
|
|
363
|
+
@ReactMethod
|
|
364
|
+
fun getAverageBrightness(base64Image: String, promise: Promise) {
|
|
365
|
+
try {
|
|
366
|
+
val mat = base64ToMat(base64Image)
|
|
367
|
+
if (mat == null) {
|
|
368
|
+
promise.resolve(0.0)
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
val gray = Mat()
|
|
372
|
+
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
373
|
+
val mean = Core.mean(gray)
|
|
374
|
+
mat.release()
|
|
375
|
+
gray.release()
|
|
376
|
+
promise.resolve(mean.`val`[0])
|
|
377
|
+
} catch (e: Exception) {
|
|
378
|
+
promise.resolve(0.0)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Check average brightness of a circular region within the image.
|
|
384
|
+
* @param base64Image Base64 JPEG image
|
|
385
|
+
* @param minX Region origin X
|
|
386
|
+
* @param minY Region origin Y
|
|
387
|
+
* @param width Region width
|
|
388
|
+
* @param height Region height
|
|
389
|
+
* @param threshold Brightness threshold
|
|
390
|
+
* @param promise Returns true if average brightness in circular region exceeds threshold
|
|
391
|
+
*/
|
|
392
|
+
@ReactMethod
|
|
393
|
+
fun isCircularRegionBright(base64Image: String, minX: Int, minY: Int, width: Int, height: Int, threshold: Double, promise: Promise) {
|
|
394
|
+
try {
|
|
395
|
+
val mat = base64ToMat(base64Image)
|
|
396
|
+
if (mat == null) {
|
|
397
|
+
promise.resolve(false)
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
val gray = Mat()
|
|
401
|
+
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
402
|
+
|
|
403
|
+
// Create circular mask
|
|
404
|
+
val mask = Mat.zeros(gray.rows(), gray.cols(), CvType.CV_8U)
|
|
405
|
+
val centerX = minX + width / 2
|
|
406
|
+
val centerY = minY + height / 2
|
|
407
|
+
val radius = Math.min(width, height) / 2
|
|
408
|
+
Imgproc.circle(mask, Point(centerX.toDouble(), centerY.toDouble()), radius, Scalar(255.0), -1)
|
|
409
|
+
|
|
410
|
+
val mean = Core.mean(gray, mask)
|
|
411
|
+
mat.release()
|
|
412
|
+
gray.release()
|
|
413
|
+
mask.release()
|
|
414
|
+
promise.resolve(mean.`val`[0] > threshold)
|
|
415
|
+
} catch (e: Exception) {
|
|
416
|
+
promise.resolve(false)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Check average brightness of a rectangular region within the image.
|
|
422
|
+
* @param base64Image Base64 JPEG image
|
|
423
|
+
* @param minX Region origin X
|
|
424
|
+
* @param minY Region origin Y
|
|
425
|
+
* @param width Region width
|
|
426
|
+
* @param height Region height
|
|
427
|
+
* @param threshold Brightness threshold
|
|
428
|
+
* @param promise Returns true if average brightness in rectangular region exceeds threshold
|
|
429
|
+
*/
|
|
430
|
+
@ReactMethod
|
|
431
|
+
fun isRectangularRegionBright(base64Image: String, minX: Int, minY: Int, width: Int, height: Int, threshold: Double, promise: Promise) {
|
|
432
|
+
try {
|
|
433
|
+
val mat = base64ToMat(base64Image)
|
|
434
|
+
if (mat == null) {
|
|
435
|
+
promise.resolve(false)
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
val gray = Mat()
|
|
439
|
+
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
440
|
+
|
|
441
|
+
// Validate region bounds
|
|
442
|
+
val x = Math.max(0, minX)
|
|
443
|
+
val y = Math.max(0, minY)
|
|
444
|
+
val w = Math.min(width, gray.cols() - x)
|
|
445
|
+
val h = Math.min(height, gray.rows() - y)
|
|
446
|
+
|
|
447
|
+
if (w <= 0 || h <= 0) {
|
|
448
|
+
mat.release()
|
|
449
|
+
gray.release()
|
|
450
|
+
promise.resolve(false)
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Extract ROI (Region of Interest)
|
|
455
|
+
val roi = org.opencv.core.Rect(x, y, w, h)
|
|
456
|
+
val regionMat = Mat(gray, roi)
|
|
457
|
+
|
|
458
|
+
// Calculate mean brightness within the region
|
|
459
|
+
val mean = Core.mean(regionMat)
|
|
460
|
+
|
|
461
|
+
mat.release()
|
|
462
|
+
gray.release()
|
|
463
|
+
regionMat.release()
|
|
464
|
+
|
|
465
|
+
promise.resolve(mean.`val`[0] > threshold)
|
|
466
|
+
} catch (e: Exception) {
|
|
467
|
+
promise.resolve(false)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Check if an image is blurry using Laplacian variance.
|
|
473
|
+
* @param base64Image Base64 JPEG image
|
|
474
|
+
* @param threshold Variance below this = blurry (default 10)
|
|
475
|
+
* @param promise Returns true if image is blurry
|
|
476
|
+
*/
|
|
477
|
+
@ReactMethod
|
|
478
|
+
fun checkBlurry(base64Image: String, threshold: Double, promise: Promise) {
|
|
479
|
+
try {
|
|
480
|
+
val mat = base64ToMat(base64Image)
|
|
481
|
+
if (mat == null) {
|
|
482
|
+
promise.resolve(false)
|
|
483
|
+
return
|
|
484
|
+
}
|
|
485
|
+
val gray = Mat()
|
|
486
|
+
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
487
|
+
|
|
488
|
+
val laplacian = Mat()
|
|
489
|
+
Imgproc.Laplacian(gray, laplacian, CvType.CV_64F)
|
|
490
|
+
|
|
491
|
+
val mean = MatOfDouble()
|
|
492
|
+
val stdDev = MatOfDouble()
|
|
493
|
+
Core.meanStdDev(laplacian, mean, stdDev)
|
|
494
|
+
|
|
495
|
+
val variance = if (stdDev.rows() > 0) {
|
|
496
|
+
val std = stdDev.get(0, 0)[0]
|
|
497
|
+
std * std
|
|
498
|
+
} else 0.0
|
|
499
|
+
|
|
500
|
+
mat.release()
|
|
501
|
+
gray.release()
|
|
502
|
+
laplacian.release()
|
|
503
|
+
mean.release()
|
|
504
|
+
stdDev.release()
|
|
505
|
+
|
|
506
|
+
promise.resolve(variance < threshold)
|
|
507
|
+
} catch (e: Exception) {
|
|
508
|
+
promise.resolve(false)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Check if a specific region of an image is blurry using Laplacian variance.
|
|
514
|
+
* This is useful for checking blur only in the area of interest (e.g., center region)
|
|
515
|
+
* while ignoring background blur from depth-of-field effects.
|
|
516
|
+
*
|
|
517
|
+
* @param base64Image Base64 JPEG image
|
|
518
|
+
* @param centerXPercent Center X position as percentage (0.0-1.0)
|
|
519
|
+
* @param centerYPercent Center Y position as percentage (0.0-1.0)
|
|
520
|
+
* @param widthPercent Region width as percentage (0.0-1.0)
|
|
521
|
+
* @param heightPercent Region height as percentage (0.0-1.0)
|
|
522
|
+
* @param threshold Variance below this = blurry (default 100)
|
|
523
|
+
* @param promise Returns true if region is blurry
|
|
524
|
+
*/
|
|
525
|
+
@ReactMethod
|
|
526
|
+
fun checkBlurryInRegion(
|
|
527
|
+
base64Image: String,
|
|
528
|
+
centerXPercent: Double,
|
|
529
|
+
centerYPercent: Double,
|
|
530
|
+
widthPercent: Double,
|
|
531
|
+
heightPercent: Double,
|
|
532
|
+
threshold: Double,
|
|
533
|
+
promise: Promise
|
|
534
|
+
) {
|
|
535
|
+
try {
|
|
536
|
+
val mat = base64ToMat(base64Image)
|
|
537
|
+
if (mat == null) {
|
|
538
|
+
promise.resolve(false)
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
val imgWidth = mat.cols()
|
|
543
|
+
val imgHeight = mat.rows()
|
|
544
|
+
|
|
545
|
+
// Calculate region bounds
|
|
546
|
+
val regionWidth = (imgWidth * widthPercent).toInt().coerceAtLeast(1)
|
|
547
|
+
val regionHeight = (imgHeight * heightPercent).toInt().coerceAtLeast(1)
|
|
548
|
+
val regionX = ((imgWidth * centerXPercent) - regionWidth / 2).toInt().coerceIn(0, imgWidth - regionWidth)
|
|
549
|
+
val regionY = ((imgHeight * centerYPercent) - regionHeight / 2).toInt().coerceIn(0, imgHeight - regionHeight)
|
|
550
|
+
|
|
551
|
+
// Extract region of interest
|
|
552
|
+
val roi = Rect(regionX, regionY, regionWidth, regionHeight)
|
|
553
|
+
val regionMat = Mat(mat, roi)
|
|
554
|
+
|
|
555
|
+
val gray = Mat()
|
|
556
|
+
Imgproc.cvtColor(regionMat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
557
|
+
|
|
558
|
+
val laplacian = Mat()
|
|
559
|
+
Imgproc.Laplacian(gray, laplacian, CvType.CV_64F)
|
|
560
|
+
|
|
561
|
+
val mean = MatOfDouble()
|
|
562
|
+
val stdDev = MatOfDouble()
|
|
563
|
+
Core.meanStdDev(laplacian, mean, stdDev)
|
|
564
|
+
|
|
565
|
+
val variance = if (stdDev.rows() > 0) {
|
|
566
|
+
val std = stdDev.get(0, 0)[0]
|
|
567
|
+
std * std
|
|
568
|
+
} else 0.0
|
|
569
|
+
|
|
570
|
+
mat.release()
|
|
571
|
+
regionMat.release()
|
|
572
|
+
gray.release()
|
|
573
|
+
laplacian.release()
|
|
574
|
+
mean.release()
|
|
575
|
+
stdDev.release()
|
|
576
|
+
|
|
577
|
+
promise.resolve(variance < threshold)
|
|
578
|
+
} catch (e: Exception) {
|
|
579
|
+
promise.resolve(false)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private fun base64ToMat(base64: String): Mat? {
|
|
584
|
+
return try {
|
|
585
|
+
val bytes = Base64.decode(base64, Base64.NO_WRAP)
|
|
586
|
+
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return null
|
|
587
|
+
val mat = Mat()
|
|
588
|
+
Utils.bitmapToMat(bitmap, mat)
|
|
589
|
+
bitmap.recycle()
|
|
590
|
+
mat
|
|
591
|
+
} catch (e: Exception) {
|
|
592
|
+
null
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Detect ID card boundaries based on text blocks and face positions.
|
|
598
|
+
* Creates a bounding rectangle that encompasses all detected elements.
|
|
599
|
+
*
|
|
600
|
+
* @param base64Image Base64 JPEG image
|
|
601
|
+
* @param textBlocks Array of detected text blocks with bounding boxes
|
|
602
|
+
* @param faces Array of detected faces with bounding boxes
|
|
603
|
+
* @param imageWidth Width of the image
|
|
604
|
+
* @param imageHeight Height of the image
|
|
605
|
+
* @param promise Returns card bounds as { x, y, width, height, corners: [{x, y}, ...] } or null
|
|
606
|
+
*/
|
|
607
|
+
@ReactMethod
|
|
608
|
+
fun detectCardBounds(
|
|
609
|
+
base64Image: String,
|
|
610
|
+
textBlocks: ReadableArray,
|
|
611
|
+
faces: ReadableArray,
|
|
612
|
+
imageWidth: Int,
|
|
613
|
+
imageHeight: Int,
|
|
614
|
+
promise: Promise
|
|
615
|
+
) {
|
|
616
|
+
try {
|
|
617
|
+
// Collect all element bounds for clustering
|
|
618
|
+
val allElements = mutableListOf<Rect>()
|
|
619
|
+
|
|
620
|
+
// Process text blocks
|
|
621
|
+
for (i in 0 until textBlocks.size()) {
|
|
622
|
+
val block = textBlocks.getMap(i) ?: continue
|
|
623
|
+
val frame = block.getMap("blockFrame") ?: continue
|
|
624
|
+
|
|
625
|
+
val x = frame.getInt("x")
|
|
626
|
+
val y = frame.getInt("y")
|
|
627
|
+
val width = frame.getInt("width")
|
|
628
|
+
val height = frame.getInt("height")
|
|
629
|
+
|
|
630
|
+
allElements.add(Rect(x, y, width, height))
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Process faces
|
|
634
|
+
for (i in 0 until faces.size()) {
|
|
635
|
+
val face = faces.getMap(i) ?: continue
|
|
636
|
+
val bounds = face.getMap("bounds") ?: continue
|
|
637
|
+
|
|
638
|
+
val x = bounds.getInt("x")
|
|
639
|
+
val y = bounds.getInt("y")
|
|
640
|
+
val width = bounds.getInt("width")
|
|
641
|
+
val height = bounds.getInt("height")
|
|
642
|
+
|
|
643
|
+
allElements.add(Rect(x, y, width, height))
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (allElements.isEmpty()) {
|
|
647
|
+
android.util.Log.d("OpenCVModule", "No elements detected for card bounds")
|
|
648
|
+
promise.resolve(null)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Calculate the centroid of all elements
|
|
653
|
+
var centerX = 0
|
|
654
|
+
var centerY = 0
|
|
655
|
+
for (rect in allElements) {
|
|
656
|
+
val rectRight = rect.x + rect.width
|
|
657
|
+
val rectBottom = rect.y + rect.height
|
|
658
|
+
centerX += (rect.x + rectRight) / 2
|
|
659
|
+
centerY += (rect.y + rectBottom) / 2
|
|
660
|
+
}
|
|
661
|
+
centerX /= allElements.size
|
|
662
|
+
centerY /= allElements.size
|
|
663
|
+
|
|
664
|
+
// Calculate distances from centroid and filter outliers
|
|
665
|
+
val distances = allElements.map { rect ->
|
|
666
|
+
val rectRight = rect.x + rect.width
|
|
667
|
+
val rectBottom = rect.y + rect.height
|
|
668
|
+
val elemCenterX = (rect.x + rectRight) / 2
|
|
669
|
+
val elemCenterY = (rect.y + rectBottom) / 2
|
|
670
|
+
val dx = elemCenterX - centerX
|
|
671
|
+
val dy = elemCenterY - centerY
|
|
672
|
+
Math.sqrt((dx * dx + dy * dy).toDouble())
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Calculate median distance
|
|
676
|
+
val sortedDistances = distances.sorted()
|
|
677
|
+
val medianDistance = sortedDistances[sortedDistances.size / 2]
|
|
678
|
+
|
|
679
|
+
// Filter out elements that are more than 2x the median distance from center
|
|
680
|
+
// This removes outliers that are far from the main cluster
|
|
681
|
+
val threshold = medianDistance * 2.0
|
|
682
|
+
val filteredElements = allElements.filterIndexed { index, _ ->
|
|
683
|
+
distances[index] <= threshold
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
android.util.Log.d("OpenCVModule", "Filtered ${allElements.size - filteredElements.size} outlier elements (${allElements.size} -> ${filteredElements.size})")
|
|
687
|
+
|
|
688
|
+
if (filteredElements.isEmpty()) {
|
|
689
|
+
android.util.Log.d("OpenCVModule", "No elements after filtering outliers")
|
|
690
|
+
promise.resolve(null)
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Now calculate bounds from filtered elements
|
|
695
|
+
var minX = imageWidth
|
|
696
|
+
var minY = imageHeight
|
|
697
|
+
var maxX = 0
|
|
698
|
+
var maxY = 0
|
|
699
|
+
|
|
700
|
+
for (rect in filteredElements) {
|
|
701
|
+
val rectRight = rect.x + rect.width
|
|
702
|
+
val rectBottom = rect.y + rect.height
|
|
703
|
+
minX = Math.min(minX, rect.x)
|
|
704
|
+
minY = Math.min(minY, rect.y)
|
|
705
|
+
maxX = Math.max(maxX, rectRight)
|
|
706
|
+
maxY = Math.max(maxY, rectBottom)
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
val elementCount = filteredElements.size
|
|
710
|
+
|
|
711
|
+
android.util.Log.d("OpenCVModule", "Detected elements: $elementCount, bounds: ($minX, $minY) to ($maxX, $maxY)")
|
|
712
|
+
|
|
713
|
+
// Calculate raw bounding box from elements
|
|
714
|
+
val elementsWidth = maxX - minX
|
|
715
|
+
val elementsHeight = maxY - minY
|
|
716
|
+
|
|
717
|
+
android.util.Log.d("OpenCVModule", "Elements size: ${elementsWidth}x${elementsHeight}, frame: ${imageWidth}x${imageHeight}")
|
|
718
|
+
|
|
719
|
+
// Validate minimum size (elements should occupy at least 5% of frame)
|
|
720
|
+
val minArea = (imageWidth * imageHeight * 0.05).toInt()
|
|
721
|
+
if (elementsWidth * elementsHeight < minArea) {
|
|
722
|
+
android.util.Log.d("OpenCVModule", "Elements too small: ${elementsWidth * elementsHeight} < $minArea")
|
|
723
|
+
promise.resolve(null)
|
|
724
|
+
return
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Add minimal padding (5% on all sides) to create tight frame
|
|
728
|
+
val paddingX = (elementsWidth * 0.05).toInt()
|
|
729
|
+
val paddingY = (elementsHeight * 0.05).toInt()
|
|
730
|
+
|
|
731
|
+
// Calculate card bounds with padding, clamped to image boundaries
|
|
732
|
+
val cardX = Math.max(0, minX - paddingX)
|
|
733
|
+
val cardY = Math.max(0, minY - paddingY)
|
|
734
|
+
val cardRight = Math.min(imageWidth, maxX + paddingX)
|
|
735
|
+
val cardBottom = Math.min(imageHeight, maxY + paddingY)
|
|
736
|
+
val cardWidth = cardRight - cardX
|
|
737
|
+
val cardHeight = cardBottom - cardY
|
|
738
|
+
|
|
739
|
+
android.util.Log.d("OpenCVModule", "Card bounds: ($cardX, $cardY) ${cardWidth}x${cardHeight}")
|
|
740
|
+
|
|
741
|
+
// Validate aspect ratio is reasonable for a document (very lenient: 1.0 - 2.5)
|
|
742
|
+
val aspectRatio = cardWidth.toDouble() / cardHeight.toDouble().coerceAtLeast(1.0)
|
|
743
|
+
android.util.Log.d("OpenCVModule", "Card aspect ratio: $aspectRatio")
|
|
744
|
+
|
|
745
|
+
if (aspectRatio < 1.0 || aspectRatio > 2.5) {
|
|
746
|
+
android.util.Log.d("OpenCVModule", "Aspect ratio out of range: $aspectRatio")
|
|
747
|
+
promise.resolve(null)
|
|
748
|
+
return
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Create corner points (rectangular bounds)
|
|
752
|
+
val corners = WritableNativeArray()
|
|
753
|
+
|
|
754
|
+
// Top-left
|
|
755
|
+
val tl = WritableNativeMap()
|
|
756
|
+
tl.putDouble("x", cardX.toDouble())
|
|
757
|
+
tl.putDouble("y", cardY.toDouble())
|
|
758
|
+
corners.pushMap(tl)
|
|
759
|
+
|
|
760
|
+
// Top-right
|
|
761
|
+
val tr = WritableNativeMap()
|
|
762
|
+
tr.putDouble("x", (cardX + cardWidth).toDouble())
|
|
763
|
+
tr.putDouble("y", cardY.toDouble())
|
|
764
|
+
corners.pushMap(tr)
|
|
765
|
+
|
|
766
|
+
// Bottom-right
|
|
767
|
+
val br = WritableNativeMap()
|
|
768
|
+
br.putDouble("x", (cardX + cardWidth).toDouble())
|
|
769
|
+
br.putDouble("y", (cardY + cardHeight).toDouble())
|
|
770
|
+
corners.pushMap(br)
|
|
771
|
+
|
|
772
|
+
// Bottom-left
|
|
773
|
+
val bl = WritableNativeMap()
|
|
774
|
+
bl.putDouble("x", cardX.toDouble())
|
|
775
|
+
bl.putDouble("y", (cardY + cardHeight).toDouble())
|
|
776
|
+
corners.pushMap(bl)
|
|
777
|
+
|
|
778
|
+
val result = WritableNativeMap()
|
|
779
|
+
result.putInt("x", cardX)
|
|
780
|
+
result.putInt("y", cardY)
|
|
781
|
+
result.putInt("width", cardWidth)
|
|
782
|
+
result.putInt("height", cardHeight)
|
|
783
|
+
result.putArray("corners", corners)
|
|
784
|
+
result.putDouble("angle", 0.0) // Rectangular alignment
|
|
785
|
+
|
|
786
|
+
promise.resolve(result)
|
|
787
|
+
} catch (e: Exception) {
|
|
788
|
+
promise.resolve(null)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
private fun matToBase64(mat: Mat): String? {
|
|
793
|
+
return try {
|
|
794
|
+
val bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888)
|
|
795
|
+
// Convert single-channel to 3-channel for bitmap conversion
|
|
796
|
+
val colorMat = if (mat.channels() == 1) {
|
|
797
|
+
val rgb = Mat()
|
|
798
|
+
Imgproc.cvtColor(mat, rgb, Imgproc.COLOR_GRAY2RGBA)
|
|
799
|
+
rgb
|
|
800
|
+
} else if (mat.channels() == 3) {
|
|
801
|
+
val rgba = Mat()
|
|
802
|
+
Imgproc.cvtColor(mat, rgba, Imgproc.COLOR_RGB2RGBA)
|
|
803
|
+
rgba
|
|
804
|
+
} else {
|
|
805
|
+
mat
|
|
806
|
+
}
|
|
807
|
+
Utils.matToBitmap(colorMat, bitmap)
|
|
808
|
+
if (colorMat !== mat) colorMat.release()
|
|
809
|
+
|
|
810
|
+
val stream = ByteArrayOutputStream()
|
|
811
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream)
|
|
812
|
+
bitmap.recycle()
|
|
813
|
+
Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP)
|
|
814
|
+
} catch (e: Exception) {
|
|
815
|
+
null
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|