react-native-neuroscan 0.1.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/LICENSE +20 -0
- package/Neuroscan.podspec +29 -0
- package/README.md +233 -0
- package/android/app/build/generated/source/codegen/RCTAppDependencyProvider.h +25 -0
- package/android/app/build/generated/source/codegen/RCTAppDependencyProvider.mm +35 -0
- package/android/app/build/generated/source/codegen/RCTModuleProviders.h +16 -0
- package/android/app/build/generated/source/codegen/RCTModuleProviders.mm +51 -0
- package/android/app/build/generated/source/codegen/RCTModulesConformingToProtocolsProvider.h +18 -0
- package/android/app/build/generated/source/codegen/RCTModulesConformingToProtocolsProvider.mm +54 -0
- package/android/app/build/generated/source/codegen/RCTThirdPartyComponentsProvider.h +16 -0
- package/android/app/build/generated/source/codegen/RCTThirdPartyComponentsProvider.mm +30 -0
- package/android/app/build/generated/source/codegen/ReactAppDependencyProvider.podspec +34 -0
- package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeNeuroscanSpec.java +67 -0
- package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/NeuroScanCameraViewManagerDelegate.java +72 -0
- package/android/app/build/generated/source/codegen/java/com/facebook/react/viewmanagers/NeuroScanCameraViewManagerInterface.java +29 -0
- package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +36 -0
- package/android/app/build/generated/source/codegen/jni/RNNeuroScanSpec-generated.cpp +74 -0
- package/android/app/build/generated/source/codegen/jni/RNNeuroScanSpec.h +31 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/ComponentDescriptors.cpp +22 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/ComponentDescriptors.h +24 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/EventEmitters.cpp +62 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/EventEmitters.h +54 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/Props.cpp +32 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/Props.h +34 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/RNNeuroScanSpecJSI-generated.cpp +80 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/RNNeuroScanSpecJSI.h +134 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/ShadowNodes.cpp +17 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/ShadowNodes.h +32 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/States.cpp +16 -0
- package/android/app/build/generated/source/codegen/jni/react/renderer/components/RNNeuroScanSpec/States.h +29 -0
- package/android/build.gradle +73 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/neuroscan/NeuroscanModule.kt +342 -0
- package/android/src/main/java/com/neuroscan/NeuroscanPackage.kt +36 -0
- package/ios/DocumentScannerController.swift +115 -0
- package/ios/NeuroScanImpl.swift +226 -0
- package/ios/Neuroscan.h +5 -0
- package/ios/Neuroscan.mm +118 -0
- package/lib/module/NativeNeuroscan.js +5 -0
- package/lib/module/NativeNeuroscan.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeNeuroscan.d.ts +47 -0
- package/lib/typescript/src/NativeNeuroscan.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +126 -0
- package/src/NativeNeuroscan.ts +52 -0
- package/src/index.tsx +2 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GenerateModuleH.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#pragma once
|
|
11
|
+
|
|
12
|
+
#include <ReactCommon/TurboModule.h>
|
|
13
|
+
#include <react/bridging/Bridging.h>
|
|
14
|
+
|
|
15
|
+
namespace facebook::react {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JSI_EXPORT NativeNeuroscanCxxSpecJSI : public TurboModule {
|
|
19
|
+
protected:
|
|
20
|
+
NativeNeuroscanCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
|
|
21
|
+
|
|
22
|
+
public:
|
|
23
|
+
virtual jsi::Value scanDocument(jsi::Runtime &rt, jsi::Object options) = 0;
|
|
24
|
+
virtual jsi::Value detectEdges(jsi::Runtime &rt, jsi::String imageUri) = 0;
|
|
25
|
+
virtual jsi::Value cropDocument(jsi::Runtime &rt, jsi::String imageUri, jsi::Object corners) = 0;
|
|
26
|
+
virtual jsi::Value processImage(jsi::Runtime &rt, jsi::String imageUri, jsi::Object options) = 0;
|
|
27
|
+
virtual jsi::Value scanAndProcess(jsi::Runtime &rt, jsi::String imageUri, jsi::Object processOptions) = 0;
|
|
28
|
+
virtual jsi::Value cleanupTempFiles(jsi::Runtime &rt) = 0;
|
|
29
|
+
virtual void addListener(jsi::Runtime &rt, jsi::String eventType) = 0;
|
|
30
|
+
virtual void removeListeners(jsi::Runtime &rt, double count) = 0;
|
|
31
|
+
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
template <typename T>
|
|
35
|
+
class JSI_EXPORT NativeNeuroscanCxxSpec : public TurboModule {
|
|
36
|
+
public:
|
|
37
|
+
jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
|
|
38
|
+
return delegate_.create(rt, propName);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override {
|
|
42
|
+
return delegate_.getPropertyNames(runtime);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static constexpr std::string_view kModuleName = "Neuroscan";
|
|
46
|
+
|
|
47
|
+
protected:
|
|
48
|
+
NativeNeuroscanCxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
|
|
49
|
+
: TurboModule(std::string{NativeNeuroscanCxxSpec::kModuleName}, jsInvoker),
|
|
50
|
+
delegate_(reinterpret_cast<T*>(this), jsInvoker) {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
class Delegate : public NativeNeuroscanCxxSpecJSI {
|
|
55
|
+
public:
|
|
56
|
+
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
|
|
57
|
+
NativeNeuroscanCxxSpecJSI(std::move(jsInvoker)), instance_(instance) {
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
jsi::Value scanDocument(jsi::Runtime &rt, jsi::Object options) override {
|
|
62
|
+
static_assert(
|
|
63
|
+
bridging::getParameterCount(&T::scanDocument) == 2,
|
|
64
|
+
"Expected scanDocument(...) to have 2 parameters");
|
|
65
|
+
|
|
66
|
+
return bridging::callFromJs<jsi::Value>(
|
|
67
|
+
rt, &T::scanDocument, jsInvoker_, instance_, std::move(options));
|
|
68
|
+
}
|
|
69
|
+
jsi::Value detectEdges(jsi::Runtime &rt, jsi::String imageUri) override {
|
|
70
|
+
static_assert(
|
|
71
|
+
bridging::getParameterCount(&T::detectEdges) == 2,
|
|
72
|
+
"Expected detectEdges(...) to have 2 parameters");
|
|
73
|
+
|
|
74
|
+
return bridging::callFromJs<jsi::Value>(
|
|
75
|
+
rt, &T::detectEdges, jsInvoker_, instance_, std::move(imageUri));
|
|
76
|
+
}
|
|
77
|
+
jsi::Value cropDocument(jsi::Runtime &rt, jsi::String imageUri, jsi::Object corners) override {
|
|
78
|
+
static_assert(
|
|
79
|
+
bridging::getParameterCount(&T::cropDocument) == 3,
|
|
80
|
+
"Expected cropDocument(...) to have 3 parameters");
|
|
81
|
+
|
|
82
|
+
return bridging::callFromJs<jsi::Value>(
|
|
83
|
+
rt, &T::cropDocument, jsInvoker_, instance_, std::move(imageUri), std::move(corners));
|
|
84
|
+
}
|
|
85
|
+
jsi::Value processImage(jsi::Runtime &rt, jsi::String imageUri, jsi::Object options) override {
|
|
86
|
+
static_assert(
|
|
87
|
+
bridging::getParameterCount(&T::processImage) == 3,
|
|
88
|
+
"Expected processImage(...) to have 3 parameters");
|
|
89
|
+
|
|
90
|
+
return bridging::callFromJs<jsi::Value>(
|
|
91
|
+
rt, &T::processImage, jsInvoker_, instance_, std::move(imageUri), std::move(options));
|
|
92
|
+
}
|
|
93
|
+
jsi::Value scanAndProcess(jsi::Runtime &rt, jsi::String imageUri, jsi::Object processOptions) override {
|
|
94
|
+
static_assert(
|
|
95
|
+
bridging::getParameterCount(&T::scanAndProcess) == 3,
|
|
96
|
+
"Expected scanAndProcess(...) to have 3 parameters");
|
|
97
|
+
|
|
98
|
+
return bridging::callFromJs<jsi::Value>(
|
|
99
|
+
rt, &T::scanAndProcess, jsInvoker_, instance_, std::move(imageUri), std::move(processOptions));
|
|
100
|
+
}
|
|
101
|
+
jsi::Value cleanupTempFiles(jsi::Runtime &rt) override {
|
|
102
|
+
static_assert(
|
|
103
|
+
bridging::getParameterCount(&T::cleanupTempFiles) == 1,
|
|
104
|
+
"Expected cleanupTempFiles(...) to have 1 parameters");
|
|
105
|
+
|
|
106
|
+
return bridging::callFromJs<jsi::Value>(
|
|
107
|
+
rt, &T::cleanupTempFiles, jsInvoker_, instance_);
|
|
108
|
+
}
|
|
109
|
+
void addListener(jsi::Runtime &rt, jsi::String eventType) override {
|
|
110
|
+
static_assert(
|
|
111
|
+
bridging::getParameterCount(&T::addListener) == 2,
|
|
112
|
+
"Expected addListener(...) to have 2 parameters");
|
|
113
|
+
|
|
114
|
+
return bridging::callFromJs<void>(
|
|
115
|
+
rt, &T::addListener, jsInvoker_, instance_, std::move(eventType));
|
|
116
|
+
}
|
|
117
|
+
void removeListeners(jsi::Runtime &rt, double count) override {
|
|
118
|
+
static_assert(
|
|
119
|
+
bridging::getParameterCount(&T::removeListeners) == 2,
|
|
120
|
+
"Expected removeListeners(...) to have 2 parameters");
|
|
121
|
+
|
|
122
|
+
return bridging::callFromJs<void>(
|
|
123
|
+
rt, &T::removeListeners, jsInvoker_, instance_, std::move(count));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private:
|
|
127
|
+
friend class NativeNeuroscanCxxSpec;
|
|
128
|
+
T *instance_;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
Delegate delegate_;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateShadowNodeCpp.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <react/renderer/components/RNNeuroScanSpec/ShadowNodes.h>
|
|
12
|
+
|
|
13
|
+
namespace facebook::react {
|
|
14
|
+
|
|
15
|
+
extern const char NeuroScanCameraViewComponentName[] = "NeuroScanCameraView";
|
|
16
|
+
|
|
17
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateShadowNodeH.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#include <react/renderer/components/RNNeuroScanSpec/EventEmitters.h>
|
|
14
|
+
#include <react/renderer/components/RNNeuroScanSpec/Props.h>
|
|
15
|
+
#include <react/renderer/components/RNNeuroScanSpec/States.h>
|
|
16
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
17
|
+
#include <jsi/jsi.h>
|
|
18
|
+
|
|
19
|
+
namespace facebook::react {
|
|
20
|
+
|
|
21
|
+
JSI_EXPORT extern const char NeuroScanCameraViewComponentName[];
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* `ShadowNode` for <NeuroScanCameraView> component.
|
|
25
|
+
*/
|
|
26
|
+
using NeuroScanCameraViewShadowNode = ConcreteViewShadowNode<
|
|
27
|
+
NeuroScanCameraViewComponentName,
|
|
28
|
+
NeuroScanCameraViewProps,
|
|
29
|
+
NeuroScanCameraViewEventEmitter,
|
|
30
|
+
NeuroScanCameraViewState>;
|
|
31
|
+
|
|
32
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateStateCpp.js
|
|
9
|
+
*/
|
|
10
|
+
#include <react/renderer/components/RNNeuroScanSpec/States.h>
|
|
11
|
+
|
|
12
|
+
namespace facebook::react {
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GenerateStateH.js
|
|
8
|
+
*/
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#ifdef ANDROID
|
|
12
|
+
#include <folly/dynamic.h>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
namespace facebook::react {
|
|
16
|
+
|
|
17
|
+
class NeuroScanCameraViewState {
|
|
18
|
+
public:
|
|
19
|
+
NeuroScanCameraViewState() = default;
|
|
20
|
+
|
|
21
|
+
#ifdef ANDROID
|
|
22
|
+
NeuroScanCameraViewState(NeuroScanCameraViewState const &previousState, folly::dynamic data){};
|
|
23
|
+
folly::dynamic getDynamic() const {
|
|
24
|
+
return {};
|
|
25
|
+
};
|
|
26
|
+
#endif
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.Neuroscan = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Neuroscan[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.neuroscan"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
|
|
68
|
+
// ML Kit Document Scanner
|
|
69
|
+
implementation "com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1"
|
|
70
|
+
|
|
71
|
+
// Coroutines
|
|
72
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
|
|
73
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
package com.neuroscan
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Bitmap
|
|
6
|
+
import android.graphics.BitmapFactory
|
|
7
|
+
import android.content.IntentSender
|
|
8
|
+
import com.facebook.react.bridge.*
|
|
9
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanning
|
|
10
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions
|
|
11
|
+
import com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult
|
|
12
|
+
import kotlinx.coroutines.*
|
|
13
|
+
import java.io.File
|
|
14
|
+
import java.io.FileOutputStream
|
|
15
|
+
import java.util.UUID
|
|
16
|
+
|
|
17
|
+
class NeuroscanModule(reactContext: ReactApplicationContext) :
|
|
18
|
+
NativeNeuroscanSpec(reactContext) {
|
|
19
|
+
|
|
20
|
+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
21
|
+
|
|
22
|
+
private var scanPromise: Promise? = null
|
|
23
|
+
private val activityListener = ScanActivityListener()
|
|
24
|
+
|
|
25
|
+
private val tempDir: File
|
|
26
|
+
get() {
|
|
27
|
+
val dir = File(reactApplicationContext.cacheDir, "neuroscan")
|
|
28
|
+
if (!dir.exists()) dir.mkdirs()
|
|
29
|
+
return dir
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
init {
|
|
33
|
+
reactContext.addActivityEventListener(activityListener)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun getName(): String = NAME
|
|
37
|
+
|
|
38
|
+
// MARK: - scanDocument
|
|
39
|
+
|
|
40
|
+
override fun scanDocument(options: ReadableMap, promise: Promise) {
|
|
41
|
+
val activity = currentActivity
|
|
42
|
+
if (activity == null) {
|
|
43
|
+
promise.reject("CAMERA_UNAVAILABLE", "No activity available")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
val maxPages = if (options.hasKey("maxPages")) options.getInt("maxPages") else 0
|
|
48
|
+
|
|
49
|
+
val scannerOptions = GmsDocumentScannerOptions.Builder()
|
|
50
|
+
.setGalleryImportAllowed(true)
|
|
51
|
+
.setResultFormats(GmsDocumentScannerOptions.RESULT_FORMAT_JPEG)
|
|
52
|
+
.setScannerMode(GmsDocumentScannerOptions.SCANNER_MODE_FULL)
|
|
53
|
+
.apply {
|
|
54
|
+
if (maxPages > 0) setPageLimit(maxPages)
|
|
55
|
+
}
|
|
56
|
+
.build()
|
|
57
|
+
|
|
58
|
+
val scanner = GmsDocumentScanning.getClient(scannerOptions)
|
|
59
|
+
|
|
60
|
+
scanPromise = promise
|
|
61
|
+
|
|
62
|
+
scanner.getStartScanIntent(activity)
|
|
63
|
+
.addOnSuccessListener { intentSender: IntentSender ->
|
|
64
|
+
try {
|
|
65
|
+
activity.startIntentSenderForResult(
|
|
66
|
+
intentSender,
|
|
67
|
+
SCAN_REQUEST_CODE,
|
|
68
|
+
null, 0, 0, 0
|
|
69
|
+
)
|
|
70
|
+
} catch (e: Exception) {
|
|
71
|
+
scanPromise?.reject("SCANNER_FAILED", "Failed to start scanner: ${e.message}", e)
|
|
72
|
+
scanPromise = null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
.addOnFailureListener { e: Exception ->
|
|
76
|
+
scanPromise?.reject("SCANNER_FAILED", "Failed to initialize scanner: ${e.message}", e)
|
|
77
|
+
scanPromise = null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - processImage
|
|
82
|
+
|
|
83
|
+
override fun processImage(options: ReadableMap, promise: Promise) {
|
|
84
|
+
scope.launch {
|
|
85
|
+
try {
|
|
86
|
+
val imageUrl = options.getString("imageUrl")
|
|
87
|
+
?: throw IllegalArgumentException("imageUrl is required")
|
|
88
|
+
val grayscale = if (options.hasKey("grayscale")) options.getBoolean("grayscale") else false
|
|
89
|
+
val contrast = if (options.hasKey("contrast")) options.getDouble("contrast") else 0.0
|
|
90
|
+
val brightness = if (options.hasKey("brightness")) options.getDouble("brightness") else 0.0
|
|
91
|
+
val sharpness = if (options.hasKey("sharpness")) options.getDouble("sharpness") else 0.0
|
|
92
|
+
val rotation = if (options.hasKey("rotation")) options.getDouble("rotation").toFloat() else 0f
|
|
93
|
+
val cropX = if (options.hasKey("cropX")) options.getDouble("cropX") else -1.0
|
|
94
|
+
val cropY = if (options.hasKey("cropY")) options.getDouble("cropY") else -1.0
|
|
95
|
+
val cropWidth = if (options.hasKey("cropWidth")) options.getDouble("cropWidth") else -1.0
|
|
96
|
+
val cropHeight = if (options.hasKey("cropHeight")) options.getDouble("cropHeight") else -1.0
|
|
97
|
+
val threshold = if (options.hasKey("threshold")) options.getDouble("threshold").toInt() else 0
|
|
98
|
+
val outputFormat = if (options.hasKey("outputFormat")) options.getString("outputFormat") ?: "jpeg" else "jpeg"
|
|
99
|
+
val quality = if (options.hasKey("quality")) options.getDouble("quality").toInt() else 90
|
|
100
|
+
|
|
101
|
+
// Load bitmap
|
|
102
|
+
val filePath = imageUrl.removePrefix("file://")
|
|
103
|
+
val originalBitmap = BitmapFactory.decodeFile(filePath)
|
|
104
|
+
?: throw IllegalArgumentException("Failed to load image from $imageUrl")
|
|
105
|
+
|
|
106
|
+
var bitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
107
|
+
originalBitmap.recycle()
|
|
108
|
+
|
|
109
|
+
// --- Filter order: crop -> rotate -> brightness/contrast -> sharpen -> grayscale/threshold ---
|
|
110
|
+
|
|
111
|
+
// 1. CROP
|
|
112
|
+
if (cropX >= 0 && cropY >= 0 && cropWidth > 0 && cropHeight > 0) {
|
|
113
|
+
val x = (cropX * bitmap.width).toInt().coerceAtLeast(0)
|
|
114
|
+
val y = (cropY * bitmap.height).toInt().coerceAtLeast(0)
|
|
115
|
+
val w = (cropWidth * bitmap.width).toInt().coerceAtMost(bitmap.width - x)
|
|
116
|
+
val h = (cropHeight * bitmap.height).toInt().coerceAtMost(bitmap.height - y)
|
|
117
|
+
if (w > 0 && h > 0) {
|
|
118
|
+
val cropped = Bitmap.createBitmap(bitmap, x, y, w, h)
|
|
119
|
+
bitmap.recycle()
|
|
120
|
+
bitmap = cropped
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 2. ROTATION
|
|
125
|
+
val rotationInt = rotation.toInt() % 360
|
|
126
|
+
if (rotationInt != 0) {
|
|
127
|
+
val matrix = android.graphics.Matrix()
|
|
128
|
+
matrix.postRotate(rotationInt.toFloat())
|
|
129
|
+
val rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
130
|
+
bitmap.recycle()
|
|
131
|
+
bitmap = rotated
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. BRIGHTNESS & CONTRAST (ColorMatrix)
|
|
135
|
+
if (brightness != 0.0 || contrast != 0.0) {
|
|
136
|
+
val contrastFactor = (1.0 + contrast / 100.0).toFloat()
|
|
137
|
+
val brightnessOffset = (brightness / 100.0 * 255.0).toFloat()
|
|
138
|
+
val translate = (1.0f - contrastFactor) * 128f
|
|
139
|
+
|
|
140
|
+
val cm = android.graphics.ColorMatrix(floatArrayOf(
|
|
141
|
+
contrastFactor, 0f, 0f, 0f, translate + brightnessOffset,
|
|
142
|
+
0f, contrastFactor, 0f, 0f, translate + brightnessOffset,
|
|
143
|
+
0f, 0f, contrastFactor, 0f, translate + brightnessOffset,
|
|
144
|
+
0f, 0f, 0f, 1f, 0f
|
|
145
|
+
))
|
|
146
|
+
|
|
147
|
+
val paint = android.graphics.Paint()
|
|
148
|
+
paint.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
|
|
149
|
+
val temp = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
150
|
+
val canvas = android.graphics.Canvas(temp)
|
|
151
|
+
canvas.drawBitmap(bitmap, 0f, 0f, paint)
|
|
152
|
+
bitmap.recycle()
|
|
153
|
+
bitmap = temp
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 4. SHARPNESS (3x3 convolution kernel)
|
|
157
|
+
if (sharpness > 0) {
|
|
158
|
+
val amount = (sharpness / 100.0).toFloat()
|
|
159
|
+
val center = 1.0f + 4.0f * amount
|
|
160
|
+
val side = -amount
|
|
161
|
+
val kernel = floatArrayOf(
|
|
162
|
+
0f, side, 0f,
|
|
163
|
+
side, center, side,
|
|
164
|
+
0f, side, 0f
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
val width = bitmap.width
|
|
168
|
+
val height = bitmap.height
|
|
169
|
+
val srcPixels = IntArray(width * height)
|
|
170
|
+
bitmap.getPixels(srcPixels, 0, width, 0, 0, width, height)
|
|
171
|
+
val dstPixels = IntArray(width * height)
|
|
172
|
+
// Copy edge pixels
|
|
173
|
+
System.arraycopy(srcPixels, 0, dstPixels, 0, srcPixels.size)
|
|
174
|
+
|
|
175
|
+
for (y in 1 until height - 1) {
|
|
176
|
+
for (x in 1 until width - 1) {
|
|
177
|
+
var r = 0f; var g = 0f; var b = 0f
|
|
178
|
+
for (ky in -1..1) {
|
|
179
|
+
for (kx in -1..1) {
|
|
180
|
+
val pixel = srcPixels[(y + ky) * width + (x + kx)]
|
|
181
|
+
val k = kernel[(ky + 1) * 3 + (kx + 1)]
|
|
182
|
+
r += ((pixel shr 16) and 0xFF) * k
|
|
183
|
+
g += ((pixel shr 8) and 0xFF) * k
|
|
184
|
+
b += (pixel and 0xFF) * k
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
val a = (srcPixels[y * width + x] shr 24) and 0xFF
|
|
188
|
+
dstPixels[y * width + x] = (a shl 24) or
|
|
189
|
+
(r.toInt().coerceIn(0, 255) shl 16) or
|
|
190
|
+
(g.toInt().coerceIn(0, 255) shl 8) or
|
|
191
|
+
b.toInt().coerceIn(0, 255)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
bitmap.setPixels(dstPixels, 0, width, 0, 0, width, height)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 5. GRAYSCALE or THRESHOLD
|
|
198
|
+
if (threshold > 0) {
|
|
199
|
+
val width = bitmap.width
|
|
200
|
+
val height = bitmap.height
|
|
201
|
+
val pixels = IntArray(width * height)
|
|
202
|
+
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
|
203
|
+
|
|
204
|
+
for (i in pixels.indices) {
|
|
205
|
+
val pixel = pixels[i]
|
|
206
|
+
val a = (pixel shr 24) and 0xFF
|
|
207
|
+
val r = (pixel shr 16) and 0xFF
|
|
208
|
+
val g = (pixel shr 8) and 0xFF
|
|
209
|
+
val b = pixel and 0xFF
|
|
210
|
+
val gray = (0.299 * r + 0.587 * g + 0.114 * b).toInt()
|
|
211
|
+
val bw = if (gray >= threshold) 255 else 0
|
|
212
|
+
pixels[i] = (a shl 24) or (bw shl 16) or (bw shl 8) or bw
|
|
213
|
+
}
|
|
214
|
+
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
|
|
215
|
+
} else if (grayscale) {
|
|
216
|
+
val cm = android.graphics.ColorMatrix()
|
|
217
|
+
cm.setSaturation(0f)
|
|
218
|
+
val paint = android.graphics.Paint()
|
|
219
|
+
paint.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
|
|
220
|
+
val temp = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
221
|
+
val canvas = android.graphics.Canvas(temp)
|
|
222
|
+
canvas.drawBitmap(bitmap, 0f, 0f, paint)
|
|
223
|
+
bitmap.recycle()
|
|
224
|
+
bitmap = temp
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 6. Save result
|
|
228
|
+
val savedUrl = saveBitmap(bitmap, outputFormat, quality)
|
|
229
|
+
bitmap.recycle()
|
|
230
|
+
|
|
231
|
+
if (savedUrl != null) {
|
|
232
|
+
val resultMap = Arguments.createMap().apply {
|
|
233
|
+
putString("imageUrl", savedUrl)
|
|
234
|
+
}
|
|
235
|
+
promise.resolve(resultMap)
|
|
236
|
+
} else {
|
|
237
|
+
promise.reject("PROCESS_FAILED", "Failed to save processed image")
|
|
238
|
+
}
|
|
239
|
+
} catch (e: Exception) {
|
|
240
|
+
promise.reject("PROCESS_FAILED", "Failed to process image: ${e.message}", e)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// MARK: - cleanupTempFiles
|
|
246
|
+
|
|
247
|
+
override fun cleanupTempFiles(promise: Promise) {
|
|
248
|
+
scope.launch {
|
|
249
|
+
try {
|
|
250
|
+
if (tempDir.exists()) {
|
|
251
|
+
tempDir.deleteRecursively()
|
|
252
|
+
}
|
|
253
|
+
promise.resolve(true)
|
|
254
|
+
} catch (e: Exception) {
|
|
255
|
+
promise.reject("CLEANUP_FAILED", "Failed to cleanup temp files: ${e.message}", e)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// MARK: - Event Emitter
|
|
261
|
+
|
|
262
|
+
override fun addListener(eventType: String) {}
|
|
263
|
+
override fun removeListeners(count: Double) {}
|
|
264
|
+
|
|
265
|
+
// MARK: - Helpers
|
|
266
|
+
|
|
267
|
+
private fun saveBitmap(bitmap: Bitmap, format: String = "jpeg", quality: Int = 90): String? {
|
|
268
|
+
return try {
|
|
269
|
+
val ext = if (format == "png") "png" else "jpg"
|
|
270
|
+
val file = File(tempDir, "${UUID.randomUUID()}.$ext")
|
|
271
|
+
|
|
272
|
+
FileOutputStream(file).use { out ->
|
|
273
|
+
val compressFormat = if (format == "png") Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG
|
|
274
|
+
bitmap.compress(compressFormat, quality, out)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
"file://${file.absolutePath}"
|
|
278
|
+
} catch (e: Exception) {
|
|
279
|
+
null
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// MARK: - Activity Result Handling
|
|
284
|
+
|
|
285
|
+
private inner class ScanActivityListener : BaseActivityEventListener() {
|
|
286
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
287
|
+
if (requestCode != SCAN_REQUEST_CODE) return
|
|
288
|
+
|
|
289
|
+
val promise = scanPromise ?: return
|
|
290
|
+
scanPromise = null
|
|
291
|
+
|
|
292
|
+
if (resultCode == Activity.RESULT_CANCELED) {
|
|
293
|
+
promise.reject("SCANNER_CANCELLED", "Document scanner was cancelled")
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
val result = GmsDocumentScanningResult.fromActivityResultIntent(data)
|
|
298
|
+
if (result == null) {
|
|
299
|
+
promise.reject("SCANNER_FAILED", "Failed to get scanner result")
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
scope.launch {
|
|
304
|
+
try {
|
|
305
|
+
val urls = mutableListOf<String>()
|
|
306
|
+
val pages = result.getPages() ?: emptyList()
|
|
307
|
+
|
|
308
|
+
for (page in pages) {
|
|
309
|
+
val imageUri = page.getImageUri()
|
|
310
|
+
val inputStream = reactApplicationContext.contentResolver.openInputStream(imageUri)
|
|
311
|
+
val bitmap = BitmapFactory.decodeStream(inputStream)
|
|
312
|
+
inputStream?.close()
|
|
313
|
+
|
|
314
|
+
if (bitmap != null) {
|
|
315
|
+
val savedUrl = saveBitmap(bitmap)
|
|
316
|
+
if (savedUrl != null) {
|
|
317
|
+
urls.add(savedUrl)
|
|
318
|
+
}
|
|
319
|
+
bitmap.recycle()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
val resultMap = Arguments.createMap().apply {
|
|
324
|
+
putArray("imageUrls", Arguments.createArray().apply {
|
|
325
|
+
urls.forEach { pushString(it) }
|
|
326
|
+
})
|
|
327
|
+
putInt("pageCount", urls.size)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
promise.resolve(resultMap)
|
|
331
|
+
} catch (e: Exception) {
|
|
332
|
+
promise.reject("SCANNER_FAILED", "Failed to process scanned documents: ${e.message}", e)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
companion object {
|
|
339
|
+
const val NAME = NativeNeuroscanSpec.NAME
|
|
340
|
+
private const val SCAN_REQUEST_CODE = 9001
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.neuroscan
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
@Suppress("unused")
|
|
9
|
+
import com.facebook.react.uimanager.ViewManager
|
|
10
|
+
|
|
11
|
+
class NeuroscanPackage : BaseReactPackage() {
|
|
12
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
13
|
+
return if (name == NeuroscanModule.NAME) {
|
|
14
|
+
NeuroscanModule(reactContext)
|
|
15
|
+
} else {
|
|
16
|
+
null
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
21
|
+
mapOf(
|
|
22
|
+
NeuroscanModule.NAME to ReactModuleInfo(
|
|
23
|
+
name = NeuroscanModule.NAME,
|
|
24
|
+
className = NeuroscanModule.NAME,
|
|
25
|
+
canOverrideExistingModule = false,
|
|
26
|
+
needsEagerInit = false,
|
|
27
|
+
isCxxModule = false,
|
|
28
|
+
isTurboModule = true
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
34
|
+
return emptyList()
|
|
35
|
+
}
|
|
36
|
+
}
|