@mleonard9/vin-scanner 0.1.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/README.md +110 -0
- package/android/build.gradle +102 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerModule.kt +93 -0
- package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerPackage.kt +27 -0
- package/android/src/main/java/com/visioncameratextrecognition/VisionCameraTextRecognitionModule.kt +130 -0
- package/ios/VisionCameraBarcodeScanner.m +175 -0
- package/ios/VisionCameraTextRecognition.m +114 -0
- package/lib/commonjs/index.js +87 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/scanBarcodes.js +46 -0
- package/lib/commonjs/scanBarcodes.js.map +1 -0
- package/lib/commonjs/scanText.js +29 -0
- package/lib/commonjs/scanText.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/vinUtils.js +240 -0
- package/lib/commonjs/vinUtils.js.map +1 -0
- package/lib/module/index.js +80 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/scanBarcodes.js +40 -0
- package/lib/module/scanBarcodes.js.map +1 -0
- package/lib/module/scanText.js +23 -0
- package/lib/module/scanText.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/vinUtils.js +230 -0
- package/lib/module/vinUtils.js.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/scanBarcodes.d.ts +3 -0
- package/lib/typescript/src/scanBarcodes.d.ts.map +1 -0
- package/lib/typescript/src/scanText.d.ts +3 -0
- package/lib/typescript/src/scanText.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +104 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/vinUtils.d.ts +19 -0
- package/lib/typescript/src/vinUtils.d.ts.map +1 -0
- package/package.json +168 -0
- package/src/index.tsx +131 -0
- package/src/scanBarcodes.ts +76 -0
- package/src/scanText.ts +36 -0
- package/src/types.ts +145 -0
- package/src/vinUtils.ts +368 -0
- package/vin-scanner.podspec +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
<div align="center">
|
|
3
|
+
|
|
4
|
+
# @mleonard9/vin-scanner
|
|
5
|
+
|
|
6
|
+
High-performance VIN detection for React Native powered by Google ML Kit barcode + text recognition and `react-native-vision-camera`.
|
|
7
|
+
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- `react-native-vision-camera` >= 3.9.0
|
|
13
|
+
- `react-native-worklets-core` >= 0.4.0
|
|
14
|
+
- iOS 13+ / Android 21+
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
yarn add @mleonard9/vin-scanner
|
|
20
|
+
# or
|
|
21
|
+
npm install @mleonard9/vin-scanner
|
|
22
|
+
|
|
23
|
+
# iOS
|
|
24
|
+
cd ios && pod install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import React, { useMemo, useState } from 'react';
|
|
31
|
+
import { StyleSheet, View } from 'react-native';
|
|
32
|
+
import { useCameraDevice } from 'react-native-vision-camera';
|
|
33
|
+
import { Camera as VinScannerCamera } from '@mleonard9/vin-scanner';
|
|
34
|
+
|
|
35
|
+
export function VinScannerExample(): JSX.Element {
|
|
36
|
+
const device = useCameraDevice('back');
|
|
37
|
+
const [results, setResults] = useState([]);
|
|
38
|
+
|
|
39
|
+
const options = useMemo(
|
|
40
|
+
() => ({
|
|
41
|
+
barcode: { formats: ['code-39', 'code-128', 'pdf-417'] },
|
|
42
|
+
text: { language: 'latin' },
|
|
43
|
+
detection: { resultMode: 'all' as const },
|
|
44
|
+
}),
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (device == null) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={StyleSheet.absoluteFill}>
|
|
54
|
+
<VinScannerCamera
|
|
55
|
+
style={StyleSheet.absoluteFill}
|
|
56
|
+
device={device}
|
|
57
|
+
options={options}
|
|
58
|
+
callback={(event) => {
|
|
59
|
+
setResults(event.candidates);
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Every frame, the camera runs ML Kit barcode + text recognition, extracts 17-character VIN candidates, validates them (including the optional checksum), and routes a payload to `callback`.
|
|
68
|
+
|
|
69
|
+
### Callback payload
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
type VinScannerEvent = {
|
|
73
|
+
mode: 'best' | 'all';
|
|
74
|
+
timestamp: number;
|
|
75
|
+
best?: VinCandidate | null;
|
|
76
|
+
candidates: VinCandidate[];
|
|
77
|
+
raw: {
|
|
78
|
+
barcodes: BarcodeDetection[];
|
|
79
|
+
textBlocks: TextDetection[];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`VinCandidate` contains `{ value, source: 'barcode' | 'text', confidence, boundingBox }`.
|
|
85
|
+
`resultMode === 'best'` returns at most one candidate per frame, while `'all'` returns every candidate so you can render overlays/selectors.
|
|
86
|
+
|
|
87
|
+
### Options
|
|
88
|
+
|
|
89
|
+
| Path | Type | Description | Default |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `options.barcode.enabled` | boolean | Enable barcode scanning | `true` |
|
|
92
|
+
| `options.barcode.formats` | `BarcodeFormat[]` | Restrict ML Kit formats (`'code-39'`, `'code-128'`, `'pdf-417'`, etc.) | `['all']` |
|
|
93
|
+
| `options.text.enabled` | boolean | Enable text recognition | `true` |
|
|
94
|
+
| `options.text.language` | `'latin' \| 'chinese' \| 'devanagari' \| 'japanese' \| 'korean'` | ML Kit language pack | `'latin'` |
|
|
95
|
+
| `options.detection.resultMode` | `'best' \| 'all'` | Emit a single best VIN or every candidate | `'best'` |
|
|
96
|
+
| `options.detection.preferBarcode` | boolean | Prefer barcode hits when selecting the best VIN | `true` |
|
|
97
|
+
| `options.detection.validateChecksum` | boolean | Run VIN checksum validation | `true` |
|
|
98
|
+
| `options.detection.emitDuplicates` | boolean | Emit unchanged payloads every frame | `false` for `'best'`, `true` for `'all'` |
|
|
99
|
+
|
|
100
|
+
## Publishing (internal use)
|
|
101
|
+
|
|
102
|
+
This package is scoped (`@mleonard9/vin-scanner`) and marked as `access: restricted`. To release a new build:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
yarn prepare # builds /lib via bob
|
|
106
|
+
npm publish --access restricted
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Ensure the authenticated npm user has access to the `@mleonard9` scope.
|
|
110
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
|
|
3
|
+
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["VisionCameraBarcodeScanner_kotlinVersion"]
|
|
4
|
+
|
|
5
|
+
repositories {
|
|
6
|
+
google()
|
|
7
|
+
mavenCentral()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
dependencies {
|
|
11
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
12
|
+
// noinspection DifferentKotlinGradleVersion
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def isNewArchitectureEnabled() {
|
|
18
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.android.library"
|
|
22
|
+
apply plugin: "kotlin-android"
|
|
23
|
+
|
|
24
|
+
if (isNewArchitectureEnabled()) {
|
|
25
|
+
apply plugin: "com.facebook.react"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def getExtOrDefault(name) {
|
|
29
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["VisionCameraBarcodeScanner_" + name]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def getExtOrIntegerDefault(name) {
|
|
33
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["VisionCameraBarcodeScanner_" + name]).toInteger()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def supportsNamespace() {
|
|
37
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
38
|
+
def major = parsed[0].toInteger()
|
|
39
|
+
def minor = parsed[1].toInteger()
|
|
40
|
+
|
|
41
|
+
// Namespace support was added in 7.3.0
|
|
42
|
+
return (major == 7 && minor >= 3) || major >= 8
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
android {
|
|
46
|
+
if (supportsNamespace()) {
|
|
47
|
+
namespace "com.visioncamerabarcodescanner"
|
|
48
|
+
|
|
49
|
+
sourceSets {
|
|
50
|
+
main {
|
|
51
|
+
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
57
|
+
|
|
58
|
+
defaultConfig {
|
|
59
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
60
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
buildTypes {
|
|
65
|
+
release {
|
|
66
|
+
minifyEnabled false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lintOptions {
|
|
71
|
+
disable "GradleCompatible"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
compileOptions {
|
|
75
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
76
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
repositories {
|
|
81
|
+
mavenCentral()
|
|
82
|
+
google()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
86
|
+
|
|
87
|
+
dependencies {
|
|
88
|
+
// For < 0.71, this will be from the local maven repo
|
|
89
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
90
|
+
//noinspection GradleDynamicVersion
|
|
91
|
+
implementation "com.facebook.react:react-native:+"
|
|
92
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
93
|
+
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
|
|
94
|
+
implementation 'com.google.mlkit:text-recognition:16.0.0'
|
|
95
|
+
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0'
|
|
96
|
+
implementation 'com.google.mlkit:text-recognition-devanagari:16.0.0'
|
|
97
|
+
implementation 'com.google.mlkit:text-recognition-japanese:16.0.0'
|
|
98
|
+
implementation 'com.google.mlkit:text-recognition-korean:16.0.0'
|
|
99
|
+
implementation project(':react-native-vision-camera')
|
|
100
|
+
implementation "androidx.camera:camera-core:1.1.0"
|
|
101
|
+
}
|
|
102
|
+
|
package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerModule.kt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package com.visioncamerabarcodescanner
|
|
2
|
+
|
|
3
|
+
import android.media.Image
|
|
4
|
+
import com.facebook.react.bridge.WritableNativeArray
|
|
5
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
6
|
+
import com.google.android.gms.tasks.Task
|
|
7
|
+
import com.google.android.gms.tasks.Tasks
|
|
8
|
+
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
9
|
+
import com.google.mlkit.vision.barcode.BarcodeScanning
|
|
10
|
+
import com.google.mlkit.vision.barcode.common.Barcode
|
|
11
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ALL_FORMATS
|
|
12
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_AZTEC
|
|
13
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODABAR
|
|
14
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_128
|
|
15
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_39
|
|
16
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_CODE_93
|
|
17
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_DATA_MATRIX
|
|
18
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_ITF
|
|
19
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_PDF417
|
|
20
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_QR_CODE
|
|
21
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_A
|
|
22
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_UPC_E
|
|
23
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_8
|
|
24
|
+
import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_13
|
|
25
|
+
import com.google.mlkit.vision.common.InputImage
|
|
26
|
+
import com.mrousavy.camera.frameprocessors.Frame
|
|
27
|
+
import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
|
|
28
|
+
import com.mrousavy.camera.frameprocessors.VisionCameraProxy
|
|
29
|
+
|
|
30
|
+
class VisionCameraBarcodeScannerModule(proxy : VisionCameraProxy, options: Map<String, Any>?): FrameProcessorPlugin() {
|
|
31
|
+
|
|
32
|
+
override fun callback(frame: Frame, arguments: Map<String, Any>?): Any {
|
|
33
|
+
try {
|
|
34
|
+
val optionsBuilder = BarcodeScannerOptions.Builder()
|
|
35
|
+
if (arguments?.get("code-128").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_128)
|
|
36
|
+
else if (arguments?.get("code-39").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_39)
|
|
37
|
+
else if (arguments?.get("code-93").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_93)
|
|
38
|
+
else if (arguments?.get("codabar").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODABAR)
|
|
39
|
+
else if (arguments?.get("ean-13").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_EAN_13)
|
|
40
|
+
else if (arguments?.get("ean-8").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_EAN_8)
|
|
41
|
+
else if (arguments?.get("itf").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_ITF)
|
|
42
|
+
else if (arguments?.get("upc-e").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_UPC_E)
|
|
43
|
+
else if (arguments?.get("upc-a").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_UPC_A)
|
|
44
|
+
else if (arguments?.get("qr").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_QR_CODE)
|
|
45
|
+
else if (arguments?.get("pdf-417").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_PDF417)
|
|
46
|
+
else if (arguments?.get("aztec").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_AZTEC)
|
|
47
|
+
else if (arguments?.get("data-matrix").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_DATA_MATRIX)
|
|
48
|
+
else if (arguments?.get("all").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_ALL_FORMATS)
|
|
49
|
+
else optionsBuilder.setBarcodeFormats(FORMAT_ALL_FORMATS)
|
|
50
|
+
|
|
51
|
+
val scanner = BarcodeScanning.getClient(optionsBuilder.build())
|
|
52
|
+
val mediaImage: Image = frame.image
|
|
53
|
+
val image = InputImage.fromMediaImage(mediaImage, frame.imageProxy.imageInfo.rotationDegrees)
|
|
54
|
+
val task: Task<List<Barcode>> = scanner.process(image)
|
|
55
|
+
val barcodes: List<Barcode> = Tasks.await(task)
|
|
56
|
+
val array = WritableNativeArray()
|
|
57
|
+
for (barcode in barcodes) {
|
|
58
|
+
val map = WritableNativeMap()
|
|
59
|
+
val bounds = barcode.boundingBox
|
|
60
|
+
if (bounds != null) {
|
|
61
|
+
map.putInt("width",bounds.width())
|
|
62
|
+
map.putInt("height",bounds.height())
|
|
63
|
+
map.putInt("top",bounds.top)
|
|
64
|
+
map.putInt("bottom",bounds.bottom)
|
|
65
|
+
map.putInt("left",bounds.left)
|
|
66
|
+
map.putInt("right",bounds.right)
|
|
67
|
+
}
|
|
68
|
+
val rawValue = barcode.rawValue
|
|
69
|
+
map.putString("rawValue",rawValue)
|
|
70
|
+
val valueType = barcode.valueType
|
|
71
|
+
when (valueType) {
|
|
72
|
+
Barcode.TYPE_WIFI -> {
|
|
73
|
+
val ssid = barcode.wifi!!.ssid
|
|
74
|
+
map.putString("ssid",ssid)
|
|
75
|
+
val password = barcode.wifi!!.password
|
|
76
|
+
map.putString("password",password)
|
|
77
|
+
}
|
|
78
|
+
Barcode.TYPE_URL -> {
|
|
79
|
+
val title = barcode.url!!.title
|
|
80
|
+
map.putString("title",title)
|
|
81
|
+
val url = barcode.url!!.url
|
|
82
|
+
map.putString("url",url)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
array.pushMap(map)
|
|
86
|
+
}
|
|
87
|
+
return array.toArrayList()
|
|
88
|
+
} catch (e: Exception) {
|
|
89
|
+
throw Exception("Error processing barcode scanner: $e ")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}
|
package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerPackage.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.visioncamerabarcodescanner
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
|
|
8
|
+
import com.visioncameratextrecognition.VisionCameraTextRecognitionModule
|
|
9
|
+
|
|
10
|
+
class VisionCameraBarcodeScannerPackage : ReactPackage {
|
|
11
|
+
companion object {
|
|
12
|
+
init {
|
|
13
|
+
FrameProcessorPluginRegistry.addFrameProcessorPlugin("scanBarcodes") {proxy,options ->
|
|
14
|
+
VisionCameraBarcodeScannerModule(proxy,options)
|
|
15
|
+
}
|
|
16
|
+
FrameProcessorPluginRegistry.addFrameProcessorPlugin("scanText") { proxy, options ->
|
|
17
|
+
VisionCameraTextRecognitionModule(proxy, options)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
22
|
+
return emptyList()
|
|
23
|
+
}
|
|
24
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
25
|
+
return emptyList()
|
|
26
|
+
}
|
|
27
|
+
}
|
package/android/src/main/java/com/visioncameratextrecognition/VisionCameraTextRecognitionModule.kt
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
package com.visioncameratextrecognition
|
|
2
|
+
|
|
3
|
+
import android.media.Image
|
|
4
|
+
import com.facebook.react.bridge.WritableNativeArray
|
|
5
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
6
|
+
import com.google.android.gms.tasks.Task
|
|
7
|
+
import com.google.android.gms.tasks.Tasks
|
|
8
|
+
import com.google.mlkit.vision.common.InputImage
|
|
9
|
+
import com.google.mlkit.vision.text.Text
|
|
10
|
+
import com.google.mlkit.vision.text.TextRecognition
|
|
11
|
+
import com.google.mlkit.vision.text.TextRecognizer
|
|
12
|
+
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
|
|
13
|
+
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
|
|
14
|
+
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
|
|
15
|
+
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
|
|
16
|
+
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
|
|
17
|
+
import com.mrousavy.camera.frameprocessor.Frame
|
|
18
|
+
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin
|
|
19
|
+
import com.mrousavy.camera.frameprocessor.VisionCameraProxy
|
|
20
|
+
import com.mrousavy.camera.types.Orientation
|
|
21
|
+
import java.util.ArrayList
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VisionCameraTextRecognitionModule(proxy : VisionCameraProxy, options: Map<String, Any>?): FrameProcessorPlugin() {
|
|
25
|
+
private var recognizer: TextRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
|
|
26
|
+
private var language = options?.get("language").toString()
|
|
27
|
+
|
|
28
|
+
init {
|
|
29
|
+
recognizer = when (language) {
|
|
30
|
+
"latin" -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
|
|
31
|
+
"chinese" -> TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
|
|
32
|
+
"devanagari" -> TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())
|
|
33
|
+
"japanese" -> TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
|
|
34
|
+
"korean" -> TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
|
|
35
|
+
else -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
override fun callback(frame: Frame, arguments: Map<String, Any>?): ArrayList<Any> {
|
|
39
|
+
try {
|
|
40
|
+
val mediaImage: Image = frame.image
|
|
41
|
+
val orientation : Orientation = frame.orientation
|
|
42
|
+
val array = WritableNativeArray()
|
|
43
|
+
val image = InputImage.fromMediaImage(mediaImage, orientation.toDegrees())
|
|
44
|
+
val task: Task<Text> = recognizer.process(image)
|
|
45
|
+
val result: Text? = Tasks.await(task)
|
|
46
|
+
val resultText = result?.text
|
|
47
|
+
for (block in result?.textBlocks!!) {
|
|
48
|
+
val map = WritableNativeMap()
|
|
49
|
+
map.putString("resultText",resultText)
|
|
50
|
+
val blockText = block.text
|
|
51
|
+
map.putString("blockText",blockText)
|
|
52
|
+
val blockCornerPoints = block.cornerPoints?.size
|
|
53
|
+
if (blockCornerPoints != null) {
|
|
54
|
+
map.putInt("size",blockCornerPoints)
|
|
55
|
+
}
|
|
56
|
+
val blockFrameBottom = block.boundingBox?.bottom
|
|
57
|
+
val blockFrameTop = block.boundingBox?.top
|
|
58
|
+
val blockFrameLeft = block.boundingBox?.left
|
|
59
|
+
val blockFrameRight = block.boundingBox?.right
|
|
60
|
+
if (blockFrameBottom != null) {
|
|
61
|
+
map.putInt("blockFrameBottom",blockFrameBottom)
|
|
62
|
+
}
|
|
63
|
+
if (blockFrameLeft != null) {
|
|
64
|
+
map.putInt("blockFrameLeft",blockFrameLeft)
|
|
65
|
+
}
|
|
66
|
+
if (blockFrameTop != null) {
|
|
67
|
+
map.putInt("blockFrameTop",blockFrameTop)
|
|
68
|
+
}
|
|
69
|
+
if (blockFrameRight != null) {
|
|
70
|
+
map.putInt("blockFrameRight",blockFrameRight)
|
|
71
|
+
}
|
|
72
|
+
for (line in block.lines) {
|
|
73
|
+
val lineText = line.text
|
|
74
|
+
map.putString("lineText",lineText)
|
|
75
|
+
val lineCornerPoints = line.cornerPoints?.size
|
|
76
|
+
if (lineCornerPoints != null) {
|
|
77
|
+
map.putInt("size",lineCornerPoints)
|
|
78
|
+
}
|
|
79
|
+
val lineFrameBottom = line.boundingBox?.bottom
|
|
80
|
+
val lineFrameTop = line.boundingBox?.top
|
|
81
|
+
val lineFrameLeft = line.boundingBox?.left
|
|
82
|
+
val lineFrameRight = line.boundingBox?.right
|
|
83
|
+
if (lineFrameBottom != null) {
|
|
84
|
+
map.putInt("lineFrameBottom",lineFrameBottom)
|
|
85
|
+
}
|
|
86
|
+
if (lineFrameLeft != null) {
|
|
87
|
+
map.putInt("lineFrameLeft",lineFrameLeft)
|
|
88
|
+
}
|
|
89
|
+
if (lineFrameTop != null) {
|
|
90
|
+
map.putInt("lineFrameTop",lineFrameTop)
|
|
91
|
+
}
|
|
92
|
+
if (lineFrameRight != null) {
|
|
93
|
+
map.putInt("lineFrameRight",lineFrameRight)
|
|
94
|
+
}
|
|
95
|
+
for (element in line.elements) {
|
|
96
|
+
val elementText = element.text
|
|
97
|
+
map.putString("elementText",elementText)
|
|
98
|
+
|
|
99
|
+
val elementCornerPoints = line.cornerPoints?.size
|
|
100
|
+
if (elementCornerPoints != null) {
|
|
101
|
+
map.putInt("size",elementCornerPoints)
|
|
102
|
+
}
|
|
103
|
+
val elementFrameBottom = line.boundingBox?.bottom
|
|
104
|
+
val elementFrameTop = line.boundingBox?.top
|
|
105
|
+
val elementFrameLeft = line.boundingBox?.left
|
|
106
|
+
val elementFrameRight = line.boundingBox?.right
|
|
107
|
+
if (elementFrameBottom != null) {
|
|
108
|
+
map.putInt("elementFrameBottom",elementFrameBottom)
|
|
109
|
+
}
|
|
110
|
+
if (elementFrameLeft != null) {
|
|
111
|
+
map.putInt("elementFrameLeft",elementFrameLeft)
|
|
112
|
+
}
|
|
113
|
+
if (elementFrameTop != null) {
|
|
114
|
+
map.putInt("elementFrameTop",elementFrameTop)
|
|
115
|
+
}
|
|
116
|
+
if (elementFrameRight != null) {
|
|
117
|
+
map.putInt("elementFrameRight",elementFrameRight)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
array.pushMap(map)
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return array.toArrayList()
|
|
125
|
+
} catch (e: Exception) {
|
|
126
|
+
throw Exception("Error processing text recognition: $e ")
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#import <MLKitBarcodeScanning/MLKitBarcodeScanning.h>
|
|
2
|
+
#import <MLKitBarcodeScanning/MLKBarcodeScannerOptions.h>
|
|
3
|
+
#import <VisionCamera/FrameProcessorPlugin.h>
|
|
4
|
+
#import <VisionCamera/FrameProcessorPluginRegistry.h>
|
|
5
|
+
#import <VisionCamera/Frame.h>
|
|
6
|
+
#import <React/RCTBridgeModule.h>
|
|
7
|
+
@import MLKitVision;
|
|
8
|
+
|
|
9
|
+
@interface VisionCameraBarcodeScannerPlugin : FrameProcessorPlugin
|
|
10
|
+
|
|
11
|
+
@property(nonatomic, strong) NSDictionary *configuration;
|
|
12
|
+
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
@implementation VisionCameraBarcodeScannerPlugin {
|
|
16
|
+
MLKBarcodeScannerOptions *options;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
- (instancetype _Nonnull)initWithProxy:(VisionCameraProxyHolder*)proxy
|
|
20
|
+
withOptions:(NSDictionary* _Nullable)options {
|
|
21
|
+
self = [super initWithProxy:proxy withOptions:options];
|
|
22
|
+
if (self) {
|
|
23
|
+
_configuration = options ?: @{};
|
|
24
|
+
}
|
|
25
|
+
return self;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (id _Nullable)callback:(Frame* _Nonnull)frame
|
|
29
|
+
withArguments:(NSDictionary* _Nullable)arguments {
|
|
30
|
+
NSDictionary *config = arguments ?: self.configuration ?: @{};
|
|
31
|
+
MLKBarcodeFormat formatMask = [self formatMaskFromConfig:config];
|
|
32
|
+
options = [[MLKBarcodeScannerOptions alloc] initWithFormats:formatMask];
|
|
33
|
+
|
|
34
|
+
MLKBarcodeScanner *barcodeScanner = [MLKBarcodeScanner barcodeScannerWithOptions:options];
|
|
35
|
+
|
|
36
|
+
CMSampleBufferRef buffer = frame.buffer;
|
|
37
|
+
UIImageOrientation orientation = frame.orientation;
|
|
38
|
+
MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:buffer];
|
|
39
|
+
image.orientation = orientation;
|
|
40
|
+
NSMutableArray *data = [NSMutableArray array];
|
|
41
|
+
dispatch_group_t dispatchGroup = dispatch_group_create();
|
|
42
|
+
dispatch_group_enter(dispatchGroup);
|
|
43
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
44
|
+
[barcodeScanner processImage:image
|
|
45
|
+
completion:^(NSArray<MLKBarcode *> *_Nullable barcodes,
|
|
46
|
+
NSError *_Nullable error) {
|
|
47
|
+
if (error != nil) {
|
|
48
|
+
RCTResponseErrorBlock error;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (barcodes.count > 0) {
|
|
52
|
+
for (MLKBarcode *barcode in barcodes) {
|
|
53
|
+
NSMutableDictionary *obj = [[NSMutableDictionary alloc] init];
|
|
54
|
+
|
|
55
|
+
NSString *displayValue = barcode.displayValue;
|
|
56
|
+
obj[@"displayValue"] = displayValue;
|
|
57
|
+
NSString *rawValue = barcode.rawValue;
|
|
58
|
+
obj[@"rawValue"] = rawValue;
|
|
59
|
+
obj[@"format"] = [self stringFromFormat:barcode.format];
|
|
60
|
+
obj[@"left"] = @(CGRectGetMinX(barcode.frame));
|
|
61
|
+
obj[@"top"] = @(CGRectGetMinY(barcode.frame));
|
|
62
|
+
obj[@"right"] = @(CGRectGetMaxX(barcode.frame));
|
|
63
|
+
obj[@"bottom"] = @(CGRectGetMaxY(barcode.frame));
|
|
64
|
+
obj[@"width"] = @(barcode.frame.size.width);
|
|
65
|
+
obj[@"height"] = @(barcode.frame.size.height);
|
|
66
|
+
|
|
67
|
+
MLKBarcodeValueType valueType = barcode.valueType;
|
|
68
|
+
|
|
69
|
+
switch (valueType) {
|
|
70
|
+
case MLKBarcodeValueTypeWiFi:
|
|
71
|
+
obj[@"ssid"] = barcode.wifi.ssid;
|
|
72
|
+
obj[@"password"] = barcode.wifi.password;
|
|
73
|
+
break;
|
|
74
|
+
case MLKBarcodeValueTypeURL:
|
|
75
|
+
obj[@"url"] = barcode.URL.url;
|
|
76
|
+
obj[@"title"] = barcode.URL.title;
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
[data addObject:obj];
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
dispatch_group_leave(dispatchGroup);
|
|
88
|
+
}];
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
VISION_EXPORT_FRAME_PROCESSOR(VisionCameraBarcodeScannerPlugin, scanBarcodes)
|
|
96
|
+
|
|
97
|
+
@end
|
|
98
|
+
|
|
99
|
+
static BOOL optionEnabled(NSDictionary *config, NSString *key) {
|
|
100
|
+
id value = config[key];
|
|
101
|
+
if (!value) {
|
|
102
|
+
return NO;
|
|
103
|
+
}
|
|
104
|
+
if ([value isKindOfClass:[NSNumber class]]) {
|
|
105
|
+
return [(NSNumber *)value boolValue];
|
|
106
|
+
}
|
|
107
|
+
if ([value isKindOfClass:[NSString class]]) {
|
|
108
|
+
return [value boolValue];
|
|
109
|
+
}
|
|
110
|
+
return NO;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@implementation VisionCameraBarcodeScannerPlugin (Configuration)
|
|
114
|
+
|
|
115
|
+
- (MLKBarcodeFormat)formatMaskFromConfig:(NSDictionary *)config {
|
|
116
|
+
if (optionEnabled(config, @"all")) {
|
|
117
|
+
return MLKBarcodeFormatAll;
|
|
118
|
+
}
|
|
119
|
+
MLKBarcodeFormat mask = 0;
|
|
120
|
+
if (optionEnabled(config, @"aztec")) mask |= MLKBarcodeFormatAztec;
|
|
121
|
+
if (optionEnabled(config, @"code-128")) mask |= MLKBarcodeFormatCode128;
|
|
122
|
+
if (optionEnabled(config, @"code-39")) mask |= MLKBarcodeFormatCode39;
|
|
123
|
+
if (optionEnabled(config, @"code-93")) mask |= MLKBarcodeFormatCode93;
|
|
124
|
+
if (optionEnabled(config, @"codabar")) mask |= MLKBarcodeFormatCodaBar;
|
|
125
|
+
if (optionEnabled(config, @"data-matrix")) mask |= MLKBarcodeFormatDataMatrix;
|
|
126
|
+
if (optionEnabled(config, @"ean-13")) mask |= MLKBarcodeFormatEAN13;
|
|
127
|
+
if (optionEnabled(config, @"ean-8")) mask |= MLKBarcodeFormatEAN8;
|
|
128
|
+
if (optionEnabled(config, @"itf")) mask |= MLKBarcodeFormatITF;
|
|
129
|
+
if (optionEnabled(config, @"pdf-417")) mask |= MLKBarcodeFormatPDF417;
|
|
130
|
+
if (optionEnabled(config, @"qr")) mask |= MLKBarcodeFormatQRCode;
|
|
131
|
+
if (optionEnabled(config, @"upc-a")) mask |= MLKBarcodeFormatUPCA;
|
|
132
|
+
if (optionEnabled(config, @"upc-e")) mask |= MLKBarcodeFormatUPCE;
|
|
133
|
+
|
|
134
|
+
if (mask == 0) {
|
|
135
|
+
mask = MLKBarcodeFormatAll;
|
|
136
|
+
}
|
|
137
|
+
return mask;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (NSString *)stringFromFormat:(MLKBarcodeFormat)format {
|
|
141
|
+
switch (format) {
|
|
142
|
+
case MLKBarcodeFormatAztec:
|
|
143
|
+
return @"aztec";
|
|
144
|
+
case MLKBarcodeFormatCode128:
|
|
145
|
+
return @"code-128";
|
|
146
|
+
case MLKBarcodeFormatCode39:
|
|
147
|
+
return @"code-39";
|
|
148
|
+
case MLKBarcodeFormatCode93:
|
|
149
|
+
return @"code-93";
|
|
150
|
+
case MLKBarcodeFormatCodaBar:
|
|
151
|
+
return @"codabar";
|
|
152
|
+
case MLKBarcodeFormatDataMatrix:
|
|
153
|
+
return @"data-matrix";
|
|
154
|
+
case MLKBarcodeFormatEAN13:
|
|
155
|
+
return @"ean-13";
|
|
156
|
+
case MLKBarcodeFormatEAN8:
|
|
157
|
+
return @"ean-8";
|
|
158
|
+
case MLKBarcodeFormatITF:
|
|
159
|
+
return @"itf";
|
|
160
|
+
case MLKBarcodeFormatPDF417:
|
|
161
|
+
return @"pdf-417";
|
|
162
|
+
case MLKBarcodeFormatQRCode:
|
|
163
|
+
return @"qr";
|
|
164
|
+
case MLKBarcodeFormatUPCA:
|
|
165
|
+
return @"upc-a";
|
|
166
|
+
case MLKBarcodeFormatUPCE:
|
|
167
|
+
return @"upc-e";
|
|
168
|
+
case MLKBarcodeFormatAll:
|
|
169
|
+
return @"all";
|
|
170
|
+
default:
|
|
171
|
+
return @"unknown";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@end
|