capacitor-card-scanner 0.0.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/CapacitorCardScanner.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +268 -0
- package/android/build.gradle +64 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScanner.java +195 -0
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScannerPlugin.java +131 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +296 -0
- package/dist/esm/definitions.d.ts +52 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +19 -0
- package/dist/esm/web.js +86 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +100 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +103 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CardScannerPlugin/CardScanner.swift +151 -0
- package/ios/Sources/CardScannerPlugin/CardScannerPlugin.swift +119 -0
- package/ios/Tests/CardScannerPluginTests/CardScannerTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapacitorCardScanner'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapacitorCardScanner",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapacitorCardScanner",
|
|
10
|
+
targets: ["CardScannerPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "CardScannerPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/CardScannerPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "CardScannerPluginTests",
|
|
25
|
+
dependencies: ["CardScannerPlugin"],
|
|
26
|
+
path: "ios/Tests/CardScannerPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# capacitor-card-scanner
|
|
2
|
+
|
|
3
|
+
Recognize a pokemon card with camera open in background
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
To use npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install capacitor-card-scanner
|
|
11
|
+
````
|
|
12
|
+
|
|
13
|
+
To use yarn
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add capacitor-card-scanner
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Sync native files
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx cap sync
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
The native camera preview is placed **behind** the WebView and the WebView is
|
|
28
|
+
made transparent. Your HTML UI (buttons, overlays, the card frame) is drawn on
|
|
29
|
+
top of the live camera feed.
|
|
30
|
+
|
|
31
|
+
For the camera to be visible, **every element between the page and the camera
|
|
32
|
+
must have a transparent background** (`html`, `body` and the layout containers).
|
|
33
|
+
|
|
34
|
+
### iOS
|
|
35
|
+
|
|
36
|
+
Add to the app's `Info.plist`:
|
|
37
|
+
|
|
38
|
+
```xml
|
|
39
|
+
<key>NSCameraUsageDescription</key>
|
|
40
|
+
<string>The camera is used to scan cards</string>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### OutSystems
|
|
44
|
+
|
|
45
|
+
1. Add a CSS class to the Theme / screen:
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
body.camera-active,
|
|
49
|
+
body.camera-active .layout,
|
|
50
|
+
body.camera-active .main,
|
|
51
|
+
body.camera-active .main-content,
|
|
52
|
+
body.camera-active .content,
|
|
53
|
+
body.camera-active .screen,
|
|
54
|
+
body.camera-active .active-screen {
|
|
55
|
+
background: transparent !important;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. On the screen `OnReady` (JavaScript node):
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const { CardScanner } = Capacitor.Plugins;
|
|
63
|
+
document.body.classList.add('camera-active');
|
|
64
|
+
await CardScanner.start({ position: 'rear' });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
3. On `OnDestroy`:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
document.body.classList.remove('camera-active');
|
|
71
|
+
await Capacitor.Plugins.CardScanner.stop();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
4. Button to scan:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const photo = await Capacitor.Plugins.CardScanner.capture({ quality: 80 });
|
|
78
|
+
$parameters.ImageBase64 = photo.base64;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
<docgen-index>
|
|
84
|
+
|
|
85
|
+
* [`start(...)`](#start)
|
|
86
|
+
* [`stop()`](#stop)
|
|
87
|
+
* [`capture(...)`](#capture)
|
|
88
|
+
* [`setTorch(...)`](#settorch)
|
|
89
|
+
* [`setZoom(...)`](#setzoom)
|
|
90
|
+
* [`flip()`](#flip)
|
|
91
|
+
* [`checkPermissions()`](#checkpermissions)
|
|
92
|
+
* [`requestPermissions()`](#requestpermissions)
|
|
93
|
+
* [Interfaces](#interfaces)
|
|
94
|
+
* [Type Aliases](#type-aliases)
|
|
95
|
+
|
|
96
|
+
</docgen-index>
|
|
97
|
+
|
|
98
|
+
<docgen-api>
|
|
99
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
100
|
+
|
|
101
|
+
### start(...)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
start(options?: StartOptions | undefined) => Promise<void>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Opens the camera **behind** the WebView. The WebView background is made
|
|
108
|
+
transparent, so the HTML page must also use transparent backgrounds
|
|
109
|
+
where the camera should be visible.
|
|
110
|
+
|
|
111
|
+
| Param | Type |
|
|
112
|
+
| ------------- | ----------------------------------------------------- |
|
|
113
|
+
| **`options`** | <code><a href="#startoptions">StartOptions</a></code> |
|
|
114
|
+
|
|
115
|
+
--------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
### stop()
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
stop() => Promise<void>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Stops the camera and removes the preview.
|
|
125
|
+
|
|
126
|
+
--------------------
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
### capture(...)
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
capture(options?: CaptureOptions | undefined) => Promise<CaptureResult>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Takes a photo from the running camera.
|
|
136
|
+
|
|
137
|
+
| Param | Type |
|
|
138
|
+
| ------------- | --------------------------------------------------------- |
|
|
139
|
+
| **`options`** | <code><a href="#captureoptions">CaptureOptions</a></code> |
|
|
140
|
+
|
|
141
|
+
**Returns:** <code>Promise<<a href="#captureresult">CaptureResult</a>></code>
|
|
142
|
+
|
|
143
|
+
--------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
### setTorch(...)
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
setTorch(options: TorchOptions) => Promise<void>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Turns the flashlight on/off.
|
|
153
|
+
|
|
154
|
+
| Param | Type |
|
|
155
|
+
| ------------- | ----------------------------------------------------- |
|
|
156
|
+
| **`options`** | <code><a href="#torchoptions">TorchOptions</a></code> |
|
|
157
|
+
|
|
158
|
+
--------------------
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### setZoom(...)
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
setZoom(options: ZoomOptions) => Promise<void>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Sets the zoom ratio.
|
|
168
|
+
|
|
169
|
+
| Param | Type |
|
|
170
|
+
| ------------- | --------------------------------------------------- |
|
|
171
|
+
| **`options`** | <code><a href="#zoomoptions">ZoomOptions</a></code> |
|
|
172
|
+
|
|
173
|
+
--------------------
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### flip()
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
flip() => Promise<void>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Switches between the rear and front camera.
|
|
183
|
+
|
|
184
|
+
--------------------
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
### checkPermissions()
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
checkPermissions() => Promise<PermissionStatus>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Returns:** <code>Promise<<a href="#permissionstatus">PermissionStatus</a>></code>
|
|
194
|
+
|
|
195
|
+
--------------------
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### requestPermissions()
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
requestPermissions() => Promise<PermissionStatus>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Returns:** <code>Promise<<a href="#permissionstatus">PermissionStatus</a>></code>
|
|
205
|
+
|
|
206
|
+
--------------------
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
### Interfaces
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
#### StartOptions
|
|
213
|
+
|
|
214
|
+
| Prop | Type | Description | Default |
|
|
215
|
+
| -------------- | --------------------------------------------------------- | -------------------- | ------------------- |
|
|
216
|
+
| **`position`** | <code><a href="#cameraposition">CameraPosition</a></code> | Which camera to use. | <code>'rear'</code> |
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
#### CaptureResult
|
|
220
|
+
|
|
221
|
+
| Prop | Type | Description |
|
|
222
|
+
| ------------ | ------------------- | ------------------------------------------------- |
|
|
223
|
+
| **`base64`** | <code>string</code> | Base64 encoded JPEG (without the `data:` prefix). |
|
|
224
|
+
| **`width`** | <code>number</code> | |
|
|
225
|
+
| **`height`** | <code>number</code> | |
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
#### CaptureOptions
|
|
229
|
+
|
|
230
|
+
| Prop | Type | Description | Default |
|
|
231
|
+
| ------------- | ------------------- | --------------------- | --------------- |
|
|
232
|
+
| **`quality`** | <code>number</code> | JPEG quality (0-100). | <code>85</code> |
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
#### TorchOptions
|
|
236
|
+
|
|
237
|
+
| Prop | Type |
|
|
238
|
+
| ------------- | -------------------- |
|
|
239
|
+
| **`enabled`** | <code>boolean</code> |
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
#### ZoomOptions
|
|
243
|
+
|
|
244
|
+
| Prop | Type | Description |
|
|
245
|
+
| ----------- | ------------------- | ----------------------------------------------------- |
|
|
246
|
+
| **`ratio`** | <code>number</code> | Zoom ratio, clamped between the camera's min and max. |
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
#### PermissionStatus
|
|
250
|
+
|
|
251
|
+
| Prop | Type |
|
|
252
|
+
| ------------ | ----------------------------------------------------------- |
|
|
253
|
+
| **`camera`** | <code><a href="#permissionstate">PermissionState</a></code> |
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
### Type Aliases
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
#### CameraPosition
|
|
260
|
+
|
|
261
|
+
<code>'rear' | 'front'</code>
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
#### PermissionState
|
|
265
|
+
|
|
266
|
+
<code>'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'</code>
|
|
267
|
+
|
|
268
|
+
</docgen-api>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace = "pt.tcgclassifier.cardscanner"
|
|
22
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError = false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
repositories {
|
|
47
|
+
google()
|
|
48
|
+
mavenCentral()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
dependencies {
|
|
53
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
54
|
+
implementation project(':capacitor-android')
|
|
55
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
56
|
+
testImplementation "junit:junit:$junitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
59
|
+
def camerax_version = "1.4.2"
|
|
60
|
+
implementation "androidx.camera:camera-core:$camerax_version"
|
|
61
|
+
implementation "androidx.camera:camera-camera2:$camerax_version"
|
|
62
|
+
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
|
63
|
+
implementation "androidx.camera:camera-view:$camerax_version"
|
|
64
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
package pt.tcgclassifier.cardscanner;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.graphics.BitmapFactory;
|
|
5
|
+
import android.graphics.Color;
|
|
6
|
+
import android.graphics.Matrix;
|
|
7
|
+
import android.util.Base64;
|
|
8
|
+
import android.view.ViewGroup;
|
|
9
|
+
import android.webkit.WebView;
|
|
10
|
+
import androidx.annotation.NonNull;
|
|
11
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
12
|
+
import androidx.camera.core.Camera;
|
|
13
|
+
import androidx.camera.core.CameraSelector;
|
|
14
|
+
import androidx.camera.core.ImageCapture;
|
|
15
|
+
import androidx.camera.core.ImageCaptureException;
|
|
16
|
+
import androidx.camera.core.ImageProxy;
|
|
17
|
+
import androidx.camera.core.Preview;
|
|
18
|
+
import androidx.camera.core.ZoomState;
|
|
19
|
+
import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
20
|
+
import androidx.camera.view.PreviewView;
|
|
21
|
+
import androidx.core.content.ContextCompat;
|
|
22
|
+
import com.google.common.util.concurrent.ListenableFuture;
|
|
23
|
+
import java.io.ByteArrayOutputStream;
|
|
24
|
+
import java.nio.ByteBuffer;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Places a CameraX preview behind the Capacitor WebView and makes the WebView
|
|
28
|
+
* transparent, so the HTML UI is drawn on top of the camera feed.
|
|
29
|
+
*/
|
|
30
|
+
public class CardScanner {
|
|
31
|
+
|
|
32
|
+
public interface Callback<T> {
|
|
33
|
+
void onSuccess(T result);
|
|
34
|
+
void onError(String message, Exception e);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static class Photo {
|
|
38
|
+
|
|
39
|
+
public final String base64;
|
|
40
|
+
public final int width;
|
|
41
|
+
public final int height;
|
|
42
|
+
|
|
43
|
+
Photo(String base64, int width, int height) {
|
|
44
|
+
this.base64 = base64;
|
|
45
|
+
this.width = width;
|
|
46
|
+
this.height = height;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private final AppCompatActivity activity;
|
|
51
|
+
private final WebView webView;
|
|
52
|
+
|
|
53
|
+
private PreviewView previewView;
|
|
54
|
+
private ProcessCameraProvider cameraProvider;
|
|
55
|
+
private ImageCapture imageCapture;
|
|
56
|
+
private Camera camera;
|
|
57
|
+
private boolean useFront = false;
|
|
58
|
+
|
|
59
|
+
public CardScanner(AppCompatActivity activity, WebView webView) {
|
|
60
|
+
this.activity = activity;
|
|
61
|
+
this.webView = webView;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public boolean isRunning() {
|
|
65
|
+
return camera != null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public boolean isFront() {
|
|
69
|
+
return useFront;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Must be called on the UI thread. */
|
|
73
|
+
public void start(boolean front, Callback<Void> callback) {
|
|
74
|
+
useFront = front;
|
|
75
|
+
|
|
76
|
+
if (previewView == null) {
|
|
77
|
+
previewView = new PreviewView(activity);
|
|
78
|
+
// TextureView-based mode composes correctly underneath a transparent WebView.
|
|
79
|
+
previewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
|
|
80
|
+
previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
|
|
81
|
+
ViewGroup parent = (ViewGroup) webView.getParent();
|
|
82
|
+
parent.addView(previewView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
webView.setBackgroundColor(Color.TRANSPARENT);
|
|
86
|
+
|
|
87
|
+
ListenableFuture<ProcessCameraProvider> future = ProcessCameraProvider.getInstance(activity);
|
|
88
|
+
future.addListener(
|
|
89
|
+
() -> {
|
|
90
|
+
try {
|
|
91
|
+
cameraProvider = future.get();
|
|
92
|
+
bind();
|
|
93
|
+
callback.onSuccess(null);
|
|
94
|
+
} catch (Exception e) {
|
|
95
|
+
callback.onError("Failed to start camera: " + e.getMessage(), e);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
ContextCompat.getMainExecutor(activity)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private void bind() {
|
|
103
|
+
cameraProvider.unbindAll();
|
|
104
|
+
|
|
105
|
+
Preview preview = new Preview.Builder().build();
|
|
106
|
+
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
107
|
+
|
|
108
|
+
imageCapture = new ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).build();
|
|
109
|
+
|
|
110
|
+
CameraSelector selector = useFront ? CameraSelector.DEFAULT_FRONT_CAMERA : CameraSelector.DEFAULT_BACK_CAMERA;
|
|
111
|
+
camera = cameraProvider.bindToLifecycle(activity, selector, preview, imageCapture);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Must be called on the UI thread. */
|
|
115
|
+
public void stop() {
|
|
116
|
+
if (cameraProvider != null) {
|
|
117
|
+
cameraProvider.unbindAll();
|
|
118
|
+
}
|
|
119
|
+
camera = null;
|
|
120
|
+
imageCapture = null;
|
|
121
|
+
if (previewView != null) {
|
|
122
|
+
((ViewGroup) previewView.getParent()).removeView(previewView);
|
|
123
|
+
previewView = null;
|
|
124
|
+
}
|
|
125
|
+
webView.setBackgroundColor(Color.WHITE);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Must be called on the UI thread. */
|
|
129
|
+
public void flip(Callback<Void> callback) {
|
|
130
|
+
useFront = !useFront;
|
|
131
|
+
try {
|
|
132
|
+
bind();
|
|
133
|
+
callback.onSuccess(null);
|
|
134
|
+
} catch (Exception e) {
|
|
135
|
+
callback.onError("Failed to flip camera: " + e.getMessage(), e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public void setTorch(boolean enabled) {
|
|
140
|
+
camera.getCameraControl().enableTorch(enabled);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public void setZoom(float ratio) {
|
|
144
|
+
ZoomState state = camera.getCameraInfo().getZoomState().getValue();
|
|
145
|
+
if (state != null) {
|
|
146
|
+
ratio = Math.max(state.getMinZoomRatio(), Math.min(state.getMaxZoomRatio(), ratio));
|
|
147
|
+
}
|
|
148
|
+
camera.getCameraControl().setZoomRatio(ratio);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public void capture(int quality, Callback<Photo> callback) {
|
|
152
|
+
imageCapture.takePicture(
|
|
153
|
+
ContextCompat.getMainExecutor(activity),
|
|
154
|
+
new ImageCapture.OnImageCapturedCallback() {
|
|
155
|
+
@Override
|
|
156
|
+
public void onCaptureSuccess(@NonNull ImageProxy image) {
|
|
157
|
+
try {
|
|
158
|
+
callback.onSuccess(toPhoto(image, quality));
|
|
159
|
+
} catch (Exception e) {
|
|
160
|
+
callback.onError("Failed to process image: " + e.getMessage(), e);
|
|
161
|
+
} finally {
|
|
162
|
+
image.close();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@Override
|
|
167
|
+
public void onError(@NonNull ImageCaptureException e) {
|
|
168
|
+
callback.onError("Capture failed: " + e.getMessage(), e);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private Photo toPhoto(ImageProxy image, int quality) {
|
|
175
|
+
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
|
176
|
+
byte[] jpeg = new byte[buffer.remaining()];
|
|
177
|
+
buffer.get(jpeg);
|
|
178
|
+
|
|
179
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
|
|
180
|
+
int rotation = image.getImageInfo().getRotationDegrees();
|
|
181
|
+
if (rotation != 0) {
|
|
182
|
+
Matrix matrix = new Matrix();
|
|
183
|
+
matrix.postRotate(rotation);
|
|
184
|
+
Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
|
185
|
+
bitmap.recycle();
|
|
186
|
+
bitmap = rotated;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
190
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out);
|
|
191
|
+
Photo photo = new Photo(Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP), bitmap.getWidth(), bitmap.getHeight());
|
|
192
|
+
bitmap.recycle();
|
|
193
|
+
return photo;
|
|
194
|
+
}
|
|
195
|
+
}
|