@trustchex/react-native-sdk 1.361.0 → 1.362.1
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/TrustchexSDK.podspec +3 -1
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +0 -13
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +0 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -39
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +94 -13
- package/ios/Camera/TrustchexCameraManager.m +0 -2
- package/ios/Camera/TrustchexCameraManager.swift +0 -7
- package/ios/Camera/TrustchexCameraView.swift +16 -47
- package/ios/OpenCV/OpenCVHelper.h +17 -0
- package/ios/OpenCV/OpenCVHelper.mm +128 -0
- package/ios/OpenCV/OpenCVModule.h +6 -0
- package/ios/OpenCV/OpenCVModule.mm +141 -0
- package/ios/TrustchexSDK-Bridging-Header.h +8 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +175 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +4 -0
- package/lib/module/Shared/Components/EIDScanner.js +0 -78
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +188 -150
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +0 -3
- package/lib/module/Shared/Libs/mrz.utils.js +265 -0
- package/lib/module/Trustchex.js +4 -0
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +0 -19
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +18 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +2 -1
- package/src/Screens/Debug/MRZTestScreen.tsx +209 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +5 -0
- package/src/Shared/Components/EIDScanner.tsx +0 -53
- package/src/Shared/Components/IdentityDocumentCamera.tsx +228 -146
- package/src/Shared/Components/QrCodeScannerCamera.tsx +0 -9
- package/src/Shared/Components/TrustchexCamera.tsx +0 -20
- package/src/Shared/Libs/mrz.utils.ts +289 -1
- package/src/Trustchex.tsx +5 -0
- package/src/index.tsx +3 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +0 -785
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +0 -419
- package/ios/MRZValidation.m +0 -39
- package/ios/MRZValidation.swift +0 -802
- package/ios/MRZValidator.swift +0 -466
package/TrustchexSDK.podspec
CHANGED
|
@@ -14,7 +14,9 @@ Pod::Spec.new do |s|
|
|
|
14
14
|
s.source = { :git => "https://github.com/trustchex/trustchex.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
# Make OpenCVHelper accessible to Swift code
|
|
19
|
+
s.public_header_files = "ios/OpenCV/OpenCVHelper.h"
|
|
18
20
|
|
|
19
21
|
# ML Kit dependencies
|
|
20
22
|
s.dependency "GoogleMLKit/TextRecognition"
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
7
7
|
<uses-permission android:name="android.permission.VIBRATE" />
|
|
8
8
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
|
9
|
+
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
|
9
10
|
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
|
10
11
|
</manifest>
|
|
@@ -8,7 +8,6 @@ import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
|
8
8
|
import com.facebook.react.uimanager.ViewManager
|
|
9
9
|
import com.trustchex.reactnativesdk.camera.TrustchexCameraManager
|
|
10
10
|
import com.trustchex.reactnativesdk.mlkit.MLKitModule
|
|
11
|
-
import com.trustchex.reactnativesdk.mrz.MRZValidationModule
|
|
12
11
|
import com.trustchex.reactnativesdk.opencv.OpenCVModule
|
|
13
12
|
import java.util.HashMap
|
|
14
13
|
|
|
@@ -19,7 +18,6 @@ class TrustchexSDKPackage : BaseReactPackage() {
|
|
|
19
18
|
"DeviceBrightness" -> DeviceBrightnessModule(reactContext)
|
|
20
19
|
"MLKitModule" -> MLKitModule(reactContext)
|
|
21
20
|
"OpenCVModule" -> OpenCVModule(reactContext)
|
|
22
|
-
"MRZValidation" -> MRZValidationModule(reactContext)
|
|
23
21
|
else -> null
|
|
24
22
|
}
|
|
25
23
|
}
|
|
@@ -76,17 +74,6 @@ class TrustchexSDKPackage : BaseReactPackage() {
|
|
|
76
74
|
false // isTurboModule
|
|
77
75
|
)
|
|
78
76
|
|
|
79
|
-
// MRZ Validation module
|
|
80
|
-
moduleInfos["MRZValidation"] =
|
|
81
|
-
ReactModuleInfo(
|
|
82
|
-
"MRZValidation",
|
|
83
|
-
"MRZValidation",
|
|
84
|
-
false, // canOverrideExistingModule
|
|
85
|
-
false, // needsEagerInit
|
|
86
|
-
false, // isCxxModule
|
|
87
|
-
false // isTurboModule
|
|
88
|
-
)
|
|
89
|
-
|
|
90
77
|
moduleInfos
|
|
91
78
|
}
|
|
92
79
|
}
|
|
@@ -63,11 +63,6 @@ class TrustchexCameraManager : SimpleViewManager<TrustchexCameraView>() {
|
|
|
63
63
|
view.setBarcodeScanningEnabled(enabled)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
@ReactProp(name = "enableMrzValidation")
|
|
67
|
-
fun setEnableMrzValidation(view: TrustchexCameraView, enabled: Boolean) {
|
|
68
|
-
view.setMrzValidationEnabled(enabled)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
66
|
@ReactProp(name = "includeBase64")
|
|
72
67
|
fun setIncludeBase64(view: TrustchexCameraView, enabled: Boolean) {
|
|
73
68
|
view.setIncludeBase64(enabled)
|
|
@@ -95,7 +90,6 @@ class TrustchexCameraManager : SimpleViewManager<TrustchexCameraView>() {
|
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
override fun receiveCommand(view: TrustchexCameraView, commandId: Int, args: com.facebook.react.bridge.ReadableArray?) {
|
|
98
|
-
android.util.Log.d("TrustchexCamera", "receiveCommand(Int) called with commandId: $commandId")
|
|
99
93
|
when (commandId) {
|
|
100
94
|
COMMAND_SET_FOCUS_POINT -> {
|
|
101
95
|
if (args != null && args.size() >= 2) {
|
|
@@ -108,7 +102,6 @@ class TrustchexCameraManager : SimpleViewManager<TrustchexCameraView>() {
|
|
|
108
102
|
}
|
|
109
103
|
}
|
|
110
104
|
COMMAND_START_RECORDING -> {
|
|
111
|
-
android.util.Log.d("TrustchexCamera", "COMMAND_START_RECORDING received")
|
|
112
105
|
view.startRecording()
|
|
113
106
|
}
|
|
114
107
|
COMMAND_STOP_RECORDING -> {
|
|
@@ -129,7 +122,6 @@ class TrustchexCameraManager : SimpleViewManager<TrustchexCameraView>() {
|
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
override fun receiveCommand(view: TrustchexCameraView, commandName: String, args: com.facebook.react.bridge.ReadableArray?) {
|
|
132
|
-
android.util.Log.d("TrustchexCamera", "receiveCommand(String) called with commandName: $commandName")
|
|
133
125
|
when (commandName) {
|
|
134
126
|
"setFocusPoint" -> {
|
|
135
127
|
if (args != null && args.size() >= 2) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
package com.trustchex.reactnativesdk.camera
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
+
import android.graphics.Bitmap
|
|
4
5
|
import android.graphics.BitmapFactory
|
|
5
6
|
import android.graphics.ImageFormat
|
|
6
7
|
import android.graphics.Matrix
|
|
7
8
|
import android.graphics.Rect
|
|
8
9
|
import android.graphics.YuvImage
|
|
10
|
+
import android.util.Base64
|
|
9
11
|
import android.util.Size
|
|
10
12
|
import android.widget.FrameLayout
|
|
11
13
|
import androidx.annotation.OptIn
|
|
@@ -25,7 +27,6 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
|
|
|
25
27
|
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
26
28
|
import com.google.mlkit.vision.barcode.common.Barcode
|
|
27
29
|
import com.google.mlkit.vision.common.InputImage
|
|
28
|
-
import com.trustchex.reactnativesdk.mrz.MRZValidator
|
|
29
30
|
import com.google.mlkit.vision.face.FaceDetection
|
|
30
31
|
import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
31
32
|
import com.google.mlkit.vision.text.TextRecognition
|
|
@@ -67,7 +68,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
67
68
|
private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
68
69
|
private var torchEnabled = false
|
|
69
70
|
private var frameProcessingEnabled = false
|
|
70
|
-
private var targetFps =
|
|
71
|
+
private var targetFps = 10
|
|
71
72
|
private var isCameraInitialized = false
|
|
72
73
|
private var isStoppingRecording = false // Track if stopRecording was called to prevent cancelRecording from deleting the file
|
|
73
74
|
|
|
@@ -75,7 +76,6 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
75
76
|
private var faceDetectionEnabled = false
|
|
76
77
|
private var textRecognitionEnabled = false
|
|
77
78
|
private var barcodeScanningEnabled = false
|
|
78
|
-
private var mrzValidationEnabled = false
|
|
79
79
|
private var includeBase64 = false
|
|
80
80
|
|
|
81
81
|
// ML Kit detector instances (lazy, created once)
|
|
@@ -98,10 +98,15 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
98
98
|
.setBarcodeFormats(
|
|
99
99
|
Barcode.FORMAT_PDF417,
|
|
100
100
|
Barcode.FORMAT_QR_CODE,
|
|
101
|
+
Barcode.FORMAT_CODABAR,
|
|
101
102
|
Barcode.FORMAT_CODE_128,
|
|
102
103
|
Barcode.FORMAT_CODE_39,
|
|
104
|
+
Barcode.FORMAT_CODE_93,
|
|
103
105
|
Barcode.FORMAT_EAN_13,
|
|
104
106
|
Barcode.FORMAT_EAN_8,
|
|
107
|
+
Barcode.FORMAT_ITF,
|
|
108
|
+
Barcode.FORMAT_UPC_A,
|
|
109
|
+
Barcode.FORMAT_UPC_E,
|
|
105
110
|
Barcode.FORMAT_AZTEC,
|
|
106
111
|
Barcode.FORMAT_DATA_MATRIX
|
|
107
112
|
)
|
|
@@ -178,7 +183,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
178
183
|
// Heuristic: If we have 3+ back cameras, index 2 is often Ultra Wide
|
|
179
184
|
}
|
|
180
185
|
} catch (e: Exception) {
|
|
181
|
-
|
|
186
|
+
// Failed to enumerate cameras
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
return CameraSelector.DEFAULT_BACK_CAMERA
|
|
@@ -209,10 +214,6 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
209
214
|
barcodeScanningEnabled = enabled
|
|
210
215
|
}
|
|
211
216
|
|
|
212
|
-
fun setMrzValidationEnabled(enabled: Boolean) {
|
|
213
|
-
mrzValidationEnabled = enabled
|
|
214
|
-
}
|
|
215
|
-
|
|
216
217
|
fun setIncludeBase64(enabled: Boolean) {
|
|
217
218
|
includeBase64 = enabled
|
|
218
219
|
}
|
|
@@ -357,11 +358,15 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
357
358
|
return
|
|
358
359
|
}
|
|
359
360
|
|
|
360
|
-
//
|
|
361
|
-
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
|
362
|
-
|
|
361
|
+
// Get rotation degrees early for preprocessing
|
|
363
362
|
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
364
363
|
|
|
364
|
+
// Create InputImage directly from camera frame — zero-copy, no base64 for ML Kit
|
|
365
|
+
val inputImage = InputImage.fromMediaImage(mediaImage, rotationDegrees)
|
|
366
|
+
|
|
367
|
+
// Use original image for text recognition
|
|
368
|
+
val textInputImage = inputImage
|
|
369
|
+
|
|
365
370
|
// Calculate portrait-oriented dimensions early (JPEG is already rotated)
|
|
366
371
|
val isRotated = rotationDegrees == 90 || rotationDegrees == 270
|
|
367
372
|
val reportedWidth = if (isRotated) imageProxy.height else imageProxy.width
|
|
@@ -385,7 +390,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
385
390
|
} else null
|
|
386
391
|
|
|
387
392
|
val textTask = if (textRecognitionEnabled) {
|
|
388
|
-
textRecognizer.process(
|
|
393
|
+
textRecognizer.process(textInputImage).also { tasks.add(it) }
|
|
389
394
|
} else null
|
|
390
395
|
|
|
391
396
|
val barcodeTask = if (barcodeScanningEnabled) {
|
|
@@ -471,28 +476,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
471
476
|
}
|
|
472
477
|
}
|
|
473
478
|
|
|
474
|
-
// MRZ validation
|
|
475
|
-
if (mrzValidationEnabled && textTask != null && textTask.isSuccessful) {
|
|
476
|
-
val rawText = textTask.result.text
|
|
477
|
-
val mrzResult = MRZValidator.validateWithCorrections(rawText)
|
|
478
|
-
val mrzMap = Arguments.createMap()
|
|
479
|
-
mrzMap.putBoolean("valid", mrzResult.valid)
|
|
480
|
-
mrzMap.putString("format", mrzResult.format)
|
|
481
|
-
mrzResult.error?.let { mrzMap.putString("error", it) }
|
|
482
|
-
mrzResult.documentCode?.let { mrzMap.putString("documentCode", it) }
|
|
483
|
-
mrzResult.issuingState?.let { mrzMap.putString("issuingState", it) }
|
|
484
|
-
mrzResult.documentNumber?.let { mrzMap.putString("documentNumber", it) }
|
|
485
|
-
mrzResult.lastName?.let { mrzMap.putString("lastName", it) }
|
|
486
|
-
mrzResult.firstName?.let { mrzMap.putString("firstName", it) }
|
|
487
|
-
mrzResult.birthDate?.let { mrzMap.putString("birthDate", it) }
|
|
488
|
-
mrzResult.sex?.let { mrzMap.putString("sex", it) }
|
|
489
|
-
mrzResult.expirationDate?.let { mrzMap.putString("expirationDate", it) }
|
|
490
|
-
mrzResult.nationality?.let { mrzMap.putString("nationality", it) }
|
|
491
|
-
mrzResult.optional1?.let { mrzMap.putString("optional1", it) }
|
|
492
|
-
mrzResult.optional2?.let { mrzMap.putString("optional2", it) }
|
|
493
|
-
mrzResult.rawLines?.let { mrzMap.putString("rawLines", it) }
|
|
494
|
-
frameData.putMap("mrzResult", mrzMap)
|
|
495
|
-
}
|
|
479
|
+
// MRZ validation moved to JavaScript - native implementation removed
|
|
496
480
|
|
|
497
481
|
// Barcode scanning results
|
|
498
482
|
if (barcodeTask != null) {
|
|
@@ -605,7 +589,6 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
605
589
|
// Check plane count
|
|
606
590
|
val planeCount = imageProxy.planes.size
|
|
607
591
|
if (planeCount < 3) {
|
|
608
|
-
android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: Expected 3 planes (YUV), got $planeCount")
|
|
609
592
|
return null
|
|
610
593
|
}
|
|
611
594
|
|
|
@@ -644,7 +627,6 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
644
627
|
nv21[nv21Offset++] = vBuffer.get(uvIndex)
|
|
645
628
|
nv21[nv21Offset++] = uBuffer.get(uvIndex)
|
|
646
629
|
} else {
|
|
647
|
-
android.util.Log.w("TrustchexCamera", "UV index out of bounds: $uvIndex at row=$row, col=$col")
|
|
648
630
|
nv21[nv21Offset++] = 128.toByte()
|
|
649
631
|
nv21[nv21Offset++] = 128.toByte()
|
|
650
632
|
}
|
|
@@ -673,7 +655,6 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
673
655
|
|
|
674
656
|
return android.util.Base64.encodeToString(jpegStream.toByteArray(), android.util.Base64.NO_WRAP)
|
|
675
657
|
} catch (e: Exception) {
|
|
676
|
-
android.util.Log.e("TrustchexCamera", "[yuvImageProxyToJpegBase64] ERROR: ${e.message}", e)
|
|
677
658
|
return null
|
|
678
659
|
}
|
|
679
660
|
}
|
|
@@ -728,6 +709,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
728
709
|
|
|
729
710
|
activeRecording = videoCapture.output
|
|
730
711
|
.prepareRecording(reactContext, fileOutputOptions)
|
|
712
|
+
.withAudioEnabled()
|
|
731
713
|
.start(ContextCompat.getMainExecutor(reactContext)) { event ->
|
|
732
714
|
when (event) {
|
|
733
715
|
is VideoRecordEvent.Finalize -> {
|
|
@@ -800,7 +782,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
800
782
|
|
|
801
783
|
camera.cameraControl.startFocusAndMetering(afAction)
|
|
802
784
|
} catch (e: Exception) {
|
|
803
|
-
|
|
785
|
+
// Failed to apply camera settings
|
|
804
786
|
}
|
|
805
787
|
}
|
|
806
788
|
private fun sendFrameEvent(frameData: WritableMap) {
|
|
@@ -840,7 +822,7 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
840
822
|
.getJSModule(RCTEventEmitter::class.java)
|
|
841
823
|
.receiveEvent(id, TrustchexCameraManager.EVENT_RECORDING_FINISHED, event)
|
|
842
824
|
} catch (e: Exception) {
|
|
843
|
-
|
|
825
|
+
// Error sending recording finished event
|
|
844
826
|
}
|
|
845
827
|
}
|
|
846
828
|
|
|
@@ -864,6 +846,45 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
864
846
|
}
|
|
865
847
|
}
|
|
866
848
|
|
|
849
|
+
/**
|
|
850
|
+
* Convert YUV ImageProxy to Bitmap with rotation applied
|
|
851
|
+
*/
|
|
852
|
+
private fun yuvImageToBitmap(imageProxy: ImageProxy, rotationDegrees: Int): Bitmap? {
|
|
853
|
+
return try {
|
|
854
|
+
val yBuffer = imageProxy.planes[0].buffer
|
|
855
|
+
val uBuffer = imageProxy.planes[1].buffer
|
|
856
|
+
val vBuffer = imageProxy.planes[2].buffer
|
|
857
|
+
|
|
858
|
+
val ySize = yBuffer.remaining()
|
|
859
|
+
val uSize = uBuffer.remaining()
|
|
860
|
+
val vSize = vBuffer.remaining()
|
|
861
|
+
|
|
862
|
+
val nv21 = ByteArray(ySize + uSize + vSize)
|
|
863
|
+
yBuffer.get(nv21, 0, ySize)
|
|
864
|
+
vBuffer.get(nv21, ySize, vSize)
|
|
865
|
+
uBuffer.get(nv21, ySize + vSize, uSize)
|
|
866
|
+
|
|
867
|
+
val yuvImage = YuvImage(nv21, ImageFormat.NV21, imageProxy.width, imageProxy.height, null)
|
|
868
|
+
val out = ByteArrayOutputStream()
|
|
869
|
+
yuvImage.compressToJpeg(Rect(0, 0, imageProxy.width, imageProxy.height), 100, out)
|
|
870
|
+
val imageBytes = out.toByteArray()
|
|
871
|
+
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
|
872
|
+
|
|
873
|
+
// Apply rotation if needed
|
|
874
|
+
if (rotationDegrees != 0 && bitmap != null) {
|
|
875
|
+
val matrix = Matrix()
|
|
876
|
+
matrix.postRotate(rotationDegrees.toFloat())
|
|
877
|
+
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
878
|
+
bitmap.recycle()
|
|
879
|
+
rotatedBitmap
|
|
880
|
+
} else {
|
|
881
|
+
bitmap
|
|
882
|
+
}
|
|
883
|
+
} catch (e: Exception) {
|
|
884
|
+
null
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
867
888
|
fun cleanup() {
|
|
868
889
|
// Cancel any active recording and delete file
|
|
869
890
|
if (activeRecording != null) {
|
|
@@ -644,7 +644,6 @@ class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
644
644
|
}
|
|
645
645
|
|
|
646
646
|
if (allElements.isEmpty()) {
|
|
647
|
-
android.util.Log.d("OpenCVModule", "No elements detected for card bounds")
|
|
648
647
|
promise.resolve(null)
|
|
649
648
|
return
|
|
650
649
|
}
|
|
@@ -683,10 +682,7 @@ class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
683
682
|
distances[index] <= threshold
|
|
684
683
|
}
|
|
685
684
|
|
|
686
|
-
android.util.Log.d("OpenCVModule", "Filtered ${allElements.size - filteredElements.size} outlier elements (${allElements.size} -> ${filteredElements.size})")
|
|
687
|
-
|
|
688
685
|
if (filteredElements.isEmpty()) {
|
|
689
|
-
android.util.Log.d("OpenCVModule", "No elements after filtering outliers")
|
|
690
686
|
promise.resolve(null)
|
|
691
687
|
return
|
|
692
688
|
}
|
|
@@ -708,18 +704,13 @@ class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
708
704
|
|
|
709
705
|
val elementCount = filteredElements.size
|
|
710
706
|
|
|
711
|
-
android.util.Log.d("OpenCVModule", "Detected elements: $elementCount, bounds: ($minX, $minY) to ($maxX, $maxY)")
|
|
712
|
-
|
|
713
707
|
// Calculate raw bounding box from elements
|
|
714
708
|
val elementsWidth = maxX - minX
|
|
715
709
|
val elementsHeight = maxY - minY
|
|
716
710
|
|
|
717
|
-
android.util.Log.d("OpenCVModule", "Elements size: ${elementsWidth}x${elementsHeight}, frame: ${imageWidth}x${imageHeight}")
|
|
718
|
-
|
|
719
711
|
// Validate minimum size (elements should occupy at least 5% of frame)
|
|
720
712
|
val minArea = (imageWidth * imageHeight * 0.05).toInt()
|
|
721
713
|
if (elementsWidth * elementsHeight < minArea) {
|
|
722
|
-
android.util.Log.d("OpenCVModule", "Elements too small: ${elementsWidth * elementsHeight} < $minArea")
|
|
723
714
|
promise.resolve(null)
|
|
724
715
|
return
|
|
725
716
|
}
|
|
@@ -736,14 +727,10 @@ class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
736
727
|
val cardWidth = cardRight - cardX
|
|
737
728
|
val cardHeight = cardBottom - cardY
|
|
738
729
|
|
|
739
|
-
android.util.Log.d("OpenCVModule", "Card bounds: ($cardX, $cardY) ${cardWidth}x${cardHeight}")
|
|
740
|
-
|
|
741
730
|
// Validate aspect ratio is reasonable for a document (very lenient: 1.0 - 2.5)
|
|
742
731
|
val aspectRatio = cardWidth.toDouble() / cardHeight.toDouble().coerceAtLeast(1.0)
|
|
743
|
-
android.util.Log.d("OpenCVModule", "Card aspect ratio: $aspectRatio")
|
|
744
732
|
|
|
745
733
|
if (aspectRatio < 1.0 || aspectRatio > 2.5) {
|
|
746
|
-
android.util.Log.d("OpenCVModule", "Aspect ratio out of range: $aspectRatio")
|
|
747
734
|
promise.resolve(null)
|
|
748
735
|
return
|
|
749
736
|
}
|
|
@@ -815,4 +802,98 @@ class OpenCVModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
815
802
|
null
|
|
816
803
|
}
|
|
817
804
|
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Preprocess image for better OCR text recognition using OpenCV.
|
|
808
|
+
* Steps:
|
|
809
|
+
* 1. Convert to grayscale
|
|
810
|
+
* 2. Bilateral filter for noise reduction while preserving edges
|
|
811
|
+
* 3. CLAHE for adaptive contrast enhancement
|
|
812
|
+
* 4. Sharpen the image to enhance text edges
|
|
813
|
+
* 5. Optional: Adaptive threshold for high-contrast text extraction
|
|
814
|
+
*
|
|
815
|
+
* @param base64Image Base64 encoded input image
|
|
816
|
+
* @param applyThresholding Whether to apply adaptive thresholding (default: false)
|
|
817
|
+
* @param promise Returns preprocessed image as base64
|
|
818
|
+
*/
|
|
819
|
+
@ReactMethod
|
|
820
|
+
fun preprocessImageForOCR(base64Image: String, applyThresholding: Boolean, promise: Promise) {
|
|
821
|
+
try {
|
|
822
|
+
if (!opencvInitialized) {
|
|
823
|
+
promise.reject("OPENCV_ERROR", "OpenCV not initialized")
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
val mat = base64ToMat(base64Image)
|
|
828
|
+
if (mat == null) {
|
|
829
|
+
promise.reject("DECODE_ERROR", "Failed to decode image")
|
|
830
|
+
return
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// 1. Convert to grayscale
|
|
834
|
+
val gray = Mat()
|
|
835
|
+
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_RGB2GRAY)
|
|
836
|
+
mat.release()
|
|
837
|
+
|
|
838
|
+
// 2. Apply bilateral filter for noise reduction while preserving edges
|
|
839
|
+
// This is better than Gaussian blur for text as it keeps edges sharp
|
|
840
|
+
val filtered = Mat()
|
|
841
|
+
Imgproc.bilateralFilter(gray, filtered, 9, 75.0, 75.0)
|
|
842
|
+
gray.release()
|
|
843
|
+
|
|
844
|
+
// 3. Apply CLAHE (Contrast Limited Adaptive Histogram Equalization)
|
|
845
|
+
// This enhances local contrast, making text stand out better
|
|
846
|
+
val clahe = Imgproc.createCLAHE(2.0, Size(8.0, 8.0))
|
|
847
|
+
val enhanced = Mat()
|
|
848
|
+
clahe.apply(filtered, enhanced)
|
|
849
|
+
filtered.release()
|
|
850
|
+
|
|
851
|
+
// 4. Sharpen the image to enhance text edges
|
|
852
|
+
// Use unsharp masking: original + (original - blurred) * amount
|
|
853
|
+
val blurred = Mat()
|
|
854
|
+
Imgproc.GaussianBlur(enhanced, blurred, Size(0.0, 0.0), 3.0)
|
|
855
|
+
val sharpened = Mat()
|
|
856
|
+
Core.addWeighted(enhanced, 1.5, blurred, -0.5, 0.0, sharpened)
|
|
857
|
+
blurred.release()
|
|
858
|
+
enhanced.release()
|
|
859
|
+
|
|
860
|
+
// 5. Optional: Apply adaptive thresholding for binary text extraction
|
|
861
|
+
val result = if (applyThresholding) {
|
|
862
|
+
val thresholded = Mat()
|
|
863
|
+
// Use Gaussian adaptive threshold - better for varying illumination
|
|
864
|
+
Imgproc.adaptiveThreshold(
|
|
865
|
+
sharpened,
|
|
866
|
+
thresholded,
|
|
867
|
+
255.0,
|
|
868
|
+
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
|
869
|
+
Imgproc.THRESH_BINARY,
|
|
870
|
+
11,
|
|
871
|
+
2.0
|
|
872
|
+
)
|
|
873
|
+
sharpened.release()
|
|
874
|
+
|
|
875
|
+
// Apply morphological operations to clean up noise
|
|
876
|
+
val kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, Size(2.0, 2.0))
|
|
877
|
+
val cleaned = Mat()
|
|
878
|
+
Imgproc.morphologyEx(thresholded, cleaned, Imgproc.MORPH_CLOSE, kernel)
|
|
879
|
+
thresholded.release()
|
|
880
|
+
kernel.release()
|
|
881
|
+
|
|
882
|
+
cleaned
|
|
883
|
+
} else {
|
|
884
|
+
sharpened
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
val resultBase64 = matToBase64(result)
|
|
888
|
+
result.release()
|
|
889
|
+
|
|
890
|
+
if (resultBase64 != null) {
|
|
891
|
+
promise.resolve(resultBase64)
|
|
892
|
+
} else {
|
|
893
|
+
promise.reject("ENCODE_ERROR", "Failed to encode result")
|
|
894
|
+
}
|
|
895
|
+
} catch (e: Exception) {
|
|
896
|
+
promise.reject("OCR_PREPROCESS_ERROR", e.message)
|
|
897
|
+
}
|
|
898
|
+
}
|
|
818
899
|
}
|
|
@@ -13,7 +13,6 @@ RCT_EXPORT_VIEW_PROPERTY(enableFrameProcessing, BOOL)
|
|
|
13
13
|
RCT_EXPORT_VIEW_PROPERTY(enableFaceDetection, BOOL)
|
|
14
14
|
RCT_EXPORT_VIEW_PROPERTY(enableTextRecognition, BOOL)
|
|
15
15
|
RCT_EXPORT_VIEW_PROPERTY(enableBarcodeScanning, BOOL)
|
|
16
|
-
RCT_EXPORT_VIEW_PROPERTY(enableMrzValidation, BOOL)
|
|
17
16
|
RCT_EXPORT_VIEW_PROPERTY(includeBase64, BOOL)
|
|
18
17
|
RCT_EXPORT_VIEW_PROPERTY(torchEnabled, BOOL)
|
|
19
18
|
RCT_EXPORT_VIEW_PROPERTY(targetFps, NSNumber)
|
|
@@ -24,7 +23,6 @@ RCT_EXTERN_METHOD(setEnableFrameProcessing:(nonnull NSNumber *)reactTag enabled:
|
|
|
24
23
|
RCT_EXTERN_METHOD(setEnableFaceDetection:(nonnull NSNumber *)reactTag enabled:(BOOL)enabled)
|
|
25
24
|
RCT_EXTERN_METHOD(setEnableTextRecognition:(nonnull NSNumber *)reactTag enabled:(BOOL)enabled)
|
|
26
25
|
RCT_EXTERN_METHOD(setEnableBarcodeScanning:(nonnull NSNumber *)reactTag enabled:(BOOL)enabled)
|
|
27
|
-
RCT_EXTERN_METHOD(setEnableMrzValidation:(nonnull NSNumber *)reactTag enabled:(BOOL)enabled)
|
|
28
26
|
RCT_EXTERN_METHOD(setIncludeBase64:(nonnull NSNumber *)reactTag enabled:(BOOL)enabled)
|
|
29
27
|
RCT_EXTERN_METHOD(setTargetFps:(nonnull NSNumber *)reactTag fps:(nonnull NSNumber *)fps)
|
|
30
28
|
RCT_EXTERN_METHOD(setFocusPoint:(nonnull NSNumber *)reactTag x:(nonnull NSNumber *)x y:(nonnull NSNumber *)y)
|
|
@@ -56,13 +56,6 @@ class TrustchexCameraManager: RCTViewManager {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
@objc func setEnableMrzValidation(_ reactTag: NSNumber, enabled: Bool) {
|
|
60
|
-
self.bridge.uiManager.addUIBlock { (_, viewRegistry) in
|
|
61
|
-
guard let view = viewRegistry?[reactTag] as? TrustchexCameraView else { return }
|
|
62
|
-
view.enableMrzValidation = enabled
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
59
|
@objc func setIncludeBase64(_ reactTag: NSNumber, enabled: Bool) {
|
|
67
60
|
self.bridge.uiManager.addUIBlock { (_, viewRegistry) in
|
|
68
61
|
guard let view = viewRegistry?[reactTag] as? TrustchexCameraView else { return }
|