capacitor-plugin-camera-forked 3.0.103 → 3.0.111
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/CapacitorPluginCameraForked.podspec +4 -1
- package/README.md +75 -9
- package/android/src/main/java/com/tonyxlh/capacitor/camera/BlurDetectionHelper.java +86 -55
- package/android/src/main/java/com/tonyxlh/capacitor/camera/CameraPreviewPlugin.java +55 -0
- package/dist/docs.json +20 -4
- package/dist/esm/definitions.d.ts +12 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +8 -1
- package/dist/esm/web.js +53 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +53 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +53 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/BlurDetectionHelper.swift +112 -37
- package/ios/Plugin/CameraPreviewPlugin.swift +36 -0
- package/ios/Plugin/TestTFLite.swift +2 -2
- package/package.json +1 -1
|
@@ -11,8 +11,11 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
13
|
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.resources = ['ios/Plugin/*.tflite']
|
|
14
15
|
s.ios.deployment_target = '13.0'
|
|
15
16
|
s.dependency 'Capacitor'
|
|
16
|
-
s.dependency 'TensorFlowLiteSwift'
|
|
17
|
+
s.dependency 'TensorFlowLiteSwift', '~> 2.17.0'
|
|
18
|
+
s.dependency 'TensorFlowLiteC', '~> 2.17.0'
|
|
19
|
+
s.static_framework = true
|
|
17
20
|
s.swift_version = '5.1'
|
|
18
21
|
end
|
package/README.md
CHANGED
|
@@ -105,6 +105,7 @@ document.documentElement.style.setProperty('--ion-background-color', 'transparen
|
|
|
105
105
|
* [`startCamera()`](#startcamera)
|
|
106
106
|
* [`stopCamera()`](#stopcamera)
|
|
107
107
|
* [`takeSnapshot(...)`](#takesnapshot)
|
|
108
|
+
* [`detectBlur(...)`](#detectblur)
|
|
108
109
|
* [`saveFrame()`](#saveframe)
|
|
109
110
|
* [`takeSnapshot2(...)`](#takesnapshot2)
|
|
110
111
|
* [`takePhoto(...)`](#takephoto)
|
|
@@ -289,7 +290,7 @@ stopCamera() => Promise<void>
|
|
|
289
290
|
### takeSnapshot(...)
|
|
290
291
|
|
|
291
292
|
```typescript
|
|
292
|
-
takeSnapshot(options: { quality?: number; checkBlur?: boolean; }) => Promise<{ base64: string;
|
|
293
|
+
takeSnapshot(options: { quality?: number; checkBlur?: boolean; }) => Promise<{ base64: string; isBlur?: boolean; }>
|
|
293
294
|
```
|
|
294
295
|
|
|
295
296
|
take a snapshot as base64.
|
|
@@ -298,7 +299,24 @@ take a snapshot as base64.
|
|
|
298
299
|
| ------------- | ------------------------------------------------------- |
|
|
299
300
|
| **`options`** | <code>{ quality?: number; checkBlur?: boolean; }</code> |
|
|
300
301
|
|
|
301
|
-
**Returns:** <code>Promise<{ base64: string;
|
|
302
|
+
**Returns:** <code>Promise<{ base64: string; isBlur?: boolean; }></code>
|
|
303
|
+
|
|
304
|
+
--------------------
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
### detectBlur(...)
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
detectBlur(options: { image: string; }) => Promise<{ isBlur: boolean; blurConfidence: number; sharpConfidence: number; }>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
analyze an image for blur detection with detailed confidence scores.
|
|
314
|
+
|
|
315
|
+
| Param | Type |
|
|
316
|
+
| ------------- | ------------------------------- |
|
|
317
|
+
| **`options`** | <code>{ image: string; }</code> |
|
|
318
|
+
|
|
319
|
+
**Returns:** <code>Promise<{ isBlur: boolean; blurConfidence: number; sharpConfidence: number; }></code>
|
|
302
320
|
|
|
303
321
|
--------------------
|
|
304
322
|
|
|
@@ -336,14 +354,14 @@ take a snapshot on to a canvas. Web Only
|
|
|
336
354
|
### takePhoto(...)
|
|
337
355
|
|
|
338
356
|
```typescript
|
|
339
|
-
takePhoto(options: { pathToSave?: string; includeBase64?: boolean; }) => Promise<{ path?: string; base64?: string; blob?: Blob;
|
|
357
|
+
takePhoto(options: { pathToSave?: string; includeBase64?: boolean; }) => Promise<{ path?: string; base64?: string; blob?: Blob; isBlur?: boolean; }>
|
|
340
358
|
```
|
|
341
359
|
|
|
342
360
|
| Param | Type |
|
|
343
361
|
| ------------- | -------------------------------------------------------------- |
|
|
344
362
|
| **`options`** | <code>{ pathToSave?: string; includeBase64?: boolean; }</code> |
|
|
345
363
|
|
|
346
|
-
**Returns:** <code>Promise<{ path?: string; base64?: string; blob?: any;
|
|
364
|
+
**Returns:** <code>Promise<{ path?: string; base64?: string; blob?: any; isBlur?: boolean; }></code>
|
|
347
365
|
|
|
348
366
|
--------------------
|
|
349
367
|
|
|
@@ -520,9 +538,34 @@ measuredByPercentage: 0 in pixel, 1 in percent
|
|
|
520
538
|
|
|
521
539
|
## Blur Detection
|
|
522
540
|
|
|
523
|
-
The plugin includes blur detection capabilities using
|
|
541
|
+
The plugin includes blur detection capabilities using TensorFlow Lite models with Laplacian variance fallback, providing consistent results across all platforms.
|
|
542
|
+
|
|
543
|
+
#### Analyze Existing Images
|
|
524
544
|
|
|
525
|
-
|
|
545
|
+
Use the `detectBlur` method to analyze any base64 image with detailed confidence scores:
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
// Analyze an existing image (base64 string or data URL)
|
|
549
|
+
const result = await CameraPreview.detectBlur({
|
|
550
|
+
image: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..."
|
|
551
|
+
// or just the base64 string: "/9j/4AAQSkZJRgABAQEASABIAAD..."
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
console.log('Is Blurry:', result.isBlur); // boolean: true/false
|
|
555
|
+
console.log('Blur Confidence:', result.blurConfidence); // 0.0-1.0 (higher = more blurry)
|
|
556
|
+
console.log('Sharp Confidence:', result.sharpConfidence); // 0.0-1.0 (higher = more sharp)
|
|
557
|
+
|
|
558
|
+
// Use confidence scores for advanced logic
|
|
559
|
+
if (result.blurConfidence > 0.7) {
|
|
560
|
+
console.log('High confidence this image is blurry');
|
|
561
|
+
} else if (result.sharpConfidence > 0.8) {
|
|
562
|
+
console.log('High confidence this image is sharp');
|
|
563
|
+
} else {
|
|
564
|
+
console.log('Uncertain blur status - manual review needed');
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### Basic Usage (Capture + Detection)
|
|
526
569
|
|
|
527
570
|
```typescript
|
|
528
571
|
// Take a snapshot with blur detection
|
|
@@ -562,8 +605,15 @@ const resultWithBlur = await CameraPreview.takeSnapshot({
|
|
|
562
605
|
});
|
|
563
606
|
```
|
|
564
607
|
|
|
565
|
-
#### Understanding Blur
|
|
608
|
+
#### Understanding Blur Results
|
|
609
|
+
|
|
610
|
+
**New `detectBlur` Method (Recommended):**
|
|
611
|
+
- Returns standardized **confidence scores** (0.0-1.0 range) across all platforms
|
|
612
|
+
- `blurConfidence`: Higher values indicate more blur (>0.7 = likely blurry)
|
|
613
|
+
- `sharpConfidence`: Higher values indicate more sharpness (>0.8 = likely sharp)
|
|
614
|
+
- `isBlur`: Simple boolean result based on confidence thresholds
|
|
566
615
|
|
|
616
|
+
**Legacy `takeSnapshot` Method:**
|
|
567
617
|
- **Higher values** = Sharper images
|
|
568
618
|
- **Lower values** = Blurrier images
|
|
569
619
|
- **Threshold guidelines**:
|
|
@@ -571,6 +621,20 @@ const resultWithBlur = await CameraPreview.takeSnapshot({
|
|
|
571
621
|
- Android/Web: Consider values below `50-100` as blurry
|
|
572
622
|
- Adjust thresholds based on your specific quality requirements
|
|
573
623
|
|
|
624
|
+
#### When to Use Which Method
|
|
625
|
+
|
|
626
|
+
**Use `detectBlur` for:**
|
|
627
|
+
- Analyzing already captured images
|
|
628
|
+
- Batch processing multiple images
|
|
629
|
+
- Getting detailed confidence scores for advanced decision logic
|
|
630
|
+
- Post-processing workflows
|
|
631
|
+
- When you need consistent confidence values across platforms
|
|
632
|
+
|
|
633
|
+
**Use `takeSnapshot` with `checkBlur: true` for:**
|
|
634
|
+
- Real-time capture with immediate blur feedback
|
|
635
|
+
- Simple blur detection during image capture
|
|
636
|
+
- When you only need a basic blur/sharp indication
|
|
637
|
+
|
|
574
638
|
#### Performance Impact
|
|
575
639
|
|
|
576
640
|
| Platform | Without Blur Detection | With Blur Detection | Overhead |
|
|
@@ -581,8 +645,10 @@ const resultWithBlur = await CameraPreview.takeSnapshot({
|
|
|
581
645
|
|
|
582
646
|
#### Implementation Notes
|
|
583
647
|
|
|
584
|
-
-
|
|
585
|
-
-
|
|
648
|
+
- **TensorFlow Lite models** for advanced blur detection with high accuracy
|
|
649
|
+
- **Laplacian variance fallback** when TFLite models unavailable
|
|
650
|
+
- Pixel sampling for performance optimization
|
|
586
651
|
- Hardware acceleration on iOS with Core Image
|
|
587
652
|
- Client-side threshold logic for maximum flexibility
|
|
588
653
|
- Cross-platform algorithm consistency
|
|
654
|
+
- **Dual API**: `takeSnapshot` for capture+detection, `detectBlur` for analyzing existing images
|
|
@@ -67,9 +67,8 @@ public class BlurDetectionHelper {
|
|
|
67
67
|
// Try to use GPU acceleration if available
|
|
68
68
|
try {
|
|
69
69
|
options.setUseXNNPACK(true);
|
|
70
|
-
Log.d(TAG, "Using XNNPACK acceleration");
|
|
71
70
|
} catch (Exception e) {
|
|
72
|
-
|
|
71
|
+
// XNNPACK not available, using CPU
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
tflite = new Interpreter(tfliteModel, options);
|
|
@@ -82,12 +81,8 @@ public class BlurDetectionHelper {
|
|
|
82
81
|
tflite.getOutputTensor(0).dataType()
|
|
83
82
|
);
|
|
84
83
|
|
|
85
|
-
//
|
|
84
|
+
// Update INPUT_SIZE based on actual model input shape
|
|
86
85
|
int[] inputShape = tflite.getInputTensor(0).shape();
|
|
87
|
-
Log.d(TAG, "Input tensor shape: " + java.util.Arrays.toString(inputShape));
|
|
88
|
-
Log.d(TAG, "Input tensor type: " + tflite.getInputTensor(0).dataType());
|
|
89
|
-
Log.d(TAG, "Output tensor shape: " + java.util.Arrays.toString(tflite.getOutputTensor(0).shape()));
|
|
90
|
-
Log.d(TAG, "Output tensor type: " + tflite.getOutputTensor(0).dataType());
|
|
91
86
|
|
|
92
87
|
// Update INPUT_SIZE based on actual model input shape
|
|
93
88
|
// Expected format: [batch, height, width, channels] or [batch, channels, height, width]
|
|
@@ -95,29 +90,23 @@ public class BlurDetectionHelper {
|
|
|
95
90
|
// Assume format is [batch, height, width, channels]
|
|
96
91
|
int modelInputSize = inputShape[1]; // height dimension
|
|
97
92
|
if (modelInputSize != INPUT_SIZE) {
|
|
98
|
-
Log.w(TAG, "Model expects input size " + modelInputSize + " but code was configured for " + INPUT_SIZE);
|
|
99
93
|
INPUT_SIZE = modelInputSize;
|
|
100
|
-
Log.i(TAG, "Updated INPUT_SIZE to " + INPUT_SIZE + " to match model");
|
|
101
94
|
|
|
102
95
|
// Recreate image processor with correct size
|
|
103
96
|
imageProcessor = new ImageProcessor.Builder()
|
|
104
97
|
.add(new ResizeWithCropOrPadOp(INPUT_SIZE, INPUT_SIZE))
|
|
105
98
|
.add(new ResizeOp(INPUT_SIZE, INPUT_SIZE, ResizeOp.ResizeMethod.BILINEAR))
|
|
106
99
|
.build();
|
|
107
|
-
Log.d(TAG, "Recreated ImageProcessor with INPUT_SIZE = " + INPUT_SIZE);
|
|
108
100
|
}
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
isInitialized = true;
|
|
112
|
-
Log.d(TAG, "TFLite blur detection model initialized successfully");
|
|
113
104
|
return true;
|
|
114
105
|
|
|
115
106
|
} catch (IOException e) {
|
|
116
|
-
Log.e(TAG, "Error loading TFLite model: " + e.getMessage());
|
|
117
107
|
isInitialized = false;
|
|
118
108
|
return false;
|
|
119
109
|
} catch (Exception e) {
|
|
120
|
-
Log.e(TAG, "Error initializing TFLite model: " + e.getMessage());
|
|
121
110
|
isInitialized = false;
|
|
122
111
|
return false;
|
|
123
112
|
}
|
|
@@ -142,27 +131,17 @@ public class BlurDetectionHelper {
|
|
|
142
131
|
inputImageBuffer.load(processedBitmap);
|
|
143
132
|
inputImageBuffer = imageProcessor.process(inputImageBuffer);
|
|
144
133
|
|
|
145
|
-
//
|
|
134
|
+
// Get tensor buffer
|
|
146
135
|
ByteBuffer tensorBuffer = inputImageBuffer.getBuffer();
|
|
147
|
-
Log.d(TAG, "TensorImage buffer size: " + tensorBuffer.remaining() + " bytes");
|
|
148
|
-
Log.d(TAG, "TensorImage buffer capacity: " + tensorBuffer.capacity() + " bytes");
|
|
149
|
-
Log.d(TAG, "TensorImage data type: " + inputImageBuffer.getDataType());
|
|
150
|
-
Log.d(TAG, "Model input tensor type: " + tflite.getInputTensor(0).dataType());
|
|
151
|
-
Log.d(TAG, "Expected buffer size: " + (INPUT_SIZE * INPUT_SIZE * 3 * 4) + " bytes");
|
|
152
136
|
|
|
153
137
|
// Check if we need normalization based on data types
|
|
154
138
|
ByteBuffer inferenceBuffer;
|
|
155
139
|
if (inputImageBuffer.getDataType() == DataType.UINT8 && tflite.getInputTensor(0).dataType() == DataType.FLOAT32) {
|
|
156
|
-
Log.d(TAG, "Converting UINT8 to FLOAT32 with normalization");
|
|
157
140
|
inferenceBuffer = normalizeImageBuffer(tensorBuffer);
|
|
158
|
-
Log.d(TAG, "Normalized buffer size: " + inferenceBuffer.remaining() + " bytes");
|
|
159
141
|
} else if (inputImageBuffer.getDataType() == DataType.FLOAT32) {
|
|
160
|
-
Log.d(TAG, "Using FLOAT32 buffer directly (may need normalization check)");
|
|
161
142
|
// Check if values are in [0,1] range or [0,255] range
|
|
162
143
|
inferenceBuffer = checkAndNormalizeFloat32Buffer(tensorBuffer);
|
|
163
|
-
Log.d(TAG, "Final buffer size: " + inferenceBuffer.remaining() + " bytes");
|
|
164
144
|
} else {
|
|
165
|
-
Log.d(TAG, "Using buffer directly");
|
|
166
145
|
inferenceBuffer = tensorBuffer;
|
|
167
146
|
}
|
|
168
147
|
|
|
@@ -172,24 +151,12 @@ public class BlurDetectionHelper {
|
|
|
172
151
|
// Get output probabilities
|
|
173
152
|
float[] probabilities = outputProbabilityBuffer.getFloatArray();
|
|
174
153
|
|
|
175
|
-
// Log probabilities for debugging
|
|
176
|
-
Log.d(TAG, "Output probabilities length: " + probabilities.length);
|
|
177
|
-
for (int i = 0; i < probabilities.length && i < 5; i++) {
|
|
178
|
-
Log.d(TAG, "probabilities[" + i + "] = " + probabilities[i]);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
154
|
// probabilities[0] = blur probability, probabilities[1] = sharp probability
|
|
182
155
|
double blurConfidence = probabilities.length > 0 ? probabilities[0] : 0.0;
|
|
183
156
|
double sharpConfidence = probabilities.length > 1 ? probabilities[1] : 0.0;
|
|
184
157
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Determine if image is blurry TFLite confidence or Laplacian score < 50
|
|
189
|
-
boolean isBlur = (blurConfidence > sharpConfidence && blurConfidence > 0.99) || (laplacianScore < 50 && sharpConfidence < 0.1);
|
|
190
|
-
|
|
191
|
-
Log.d(TAG, String.format("TFLite Blur Detection - Blur: %.6f, Sharp: %.6f, Label: %s",
|
|
192
|
-
blurConfidence, sharpConfidence, isBlur ? "blur" : "sharp"));
|
|
158
|
+
// Determine if image is blurry using TFLite confidence
|
|
159
|
+
boolean isBlur = (blurConfidence >= 0.99 || sharpConfidence < 0.1);
|
|
193
160
|
|
|
194
161
|
// Return 1.0 for blur, 0.0 for sharp (to maintain double return type)
|
|
195
162
|
return isBlur ? 1.0 : 0.0;
|
|
@@ -199,16 +166,10 @@ public class BlurDetectionHelper {
|
|
|
199
166
|
// Fallback to Laplacian algorithm
|
|
200
167
|
double laplacianScore = calculateLaplacianBlurScore(bitmap);
|
|
201
168
|
boolean isBlur = laplacianScore < 150;
|
|
202
|
-
Log.d(TAG, String.format("Laplacian Fallback - Score: %.2f, Label: %s",
|
|
203
|
-
laplacianScore, isBlur ? "blur" : "sharp"));
|
|
204
169
|
return isBlur ? 1.0 : 0.0;
|
|
205
170
|
}
|
|
206
171
|
}
|
|
207
172
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
173
|
/**
|
|
213
174
|
* Check if float32 buffer needs normalization and normalize if needed
|
|
214
175
|
* @param float32Buffer Input buffer with float32 pixel values
|
|
@@ -227,14 +188,10 @@ public class BlurDetectionHelper {
|
|
|
227
188
|
maxSample = Math.max(maxSample, value);
|
|
228
189
|
}
|
|
229
190
|
|
|
230
|
-
Log.d(TAG, "Sample max value from buffer: " + maxSample);
|
|
231
|
-
|
|
232
191
|
// If max value is > 1.5, assume it's in [0,255] range and needs normalization
|
|
233
192
|
if (maxSample > 1.5f) {
|
|
234
|
-
Log.d(TAG, "Buffer appears to be in [0,255] range, normalizing to [0,1]");
|
|
235
193
|
return normalizeFloat32Buffer(float32Buffer);
|
|
236
194
|
} else {
|
|
237
|
-
Log.d(TAG, "Buffer appears to be already normalized [0,1]");
|
|
238
195
|
float32Buffer.rewind();
|
|
239
196
|
return float32Buffer;
|
|
240
197
|
}
|
|
@@ -254,8 +211,6 @@ public class BlurDetectionHelper {
|
|
|
254
211
|
float32Buffer.rewind();
|
|
255
212
|
FloatBuffer sourceFloats = float32Buffer.asFloatBuffer();
|
|
256
213
|
|
|
257
|
-
Log.d(TAG, "Normalizing float32 buffer: " + sourceFloats.remaining() + " values, target: " + pixelCount);
|
|
258
|
-
|
|
259
214
|
while (sourceFloats.hasRemaining() && normalizedFloats.hasRemaining()) {
|
|
260
215
|
float pixelValue = sourceFloats.get();
|
|
261
216
|
float normalizedValue = pixelValue / 255.0f;
|
|
@@ -263,7 +218,6 @@ public class BlurDetectionHelper {
|
|
|
263
218
|
}
|
|
264
219
|
|
|
265
220
|
normalizedBuffer.rewind();
|
|
266
|
-
Log.d(TAG, "Normalized buffer created with " + normalizedBuffer.remaining() + " bytes");
|
|
267
221
|
return normalizedBuffer;
|
|
268
222
|
}
|
|
269
223
|
|
|
@@ -282,8 +236,6 @@ public class BlurDetectionHelper {
|
|
|
282
236
|
// Reset uint8 buffer position
|
|
283
237
|
uint8Buffer.rewind();
|
|
284
238
|
|
|
285
|
-
Log.d(TAG, "Converting UINT8 to FLOAT32: " + uint8Buffer.remaining() + " uint8 values to " + pixelCount + " float values");
|
|
286
|
-
|
|
287
239
|
// Convert each uint8 pixel to normalized float32
|
|
288
240
|
while (uint8Buffer.hasRemaining() && floatBuffer.hasRemaining()) {
|
|
289
241
|
int pixelValue = uint8Buffer.get() & 0xFF; // Convert to unsigned int
|
|
@@ -292,7 +244,6 @@ public class BlurDetectionHelper {
|
|
|
292
244
|
}
|
|
293
245
|
|
|
294
246
|
float32Buffer.rewind();
|
|
295
|
-
Log.d(TAG, "UINT8 to FLOAT32 conversion complete, buffer size: " + float32Buffer.remaining() + " bytes");
|
|
296
247
|
return float32Buffer;
|
|
297
248
|
}
|
|
298
249
|
|
|
@@ -342,6 +293,87 @@ public class BlurDetectionHelper {
|
|
|
342
293
|
return count > 0 ? variance / count : 0.0;
|
|
343
294
|
}
|
|
344
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Detect blur with detailed confidence scores
|
|
298
|
+
* @param bitmap Input image bitmap
|
|
299
|
+
* @return Map with isBlur, blurConfidence, and sharpConfidence
|
|
300
|
+
*/
|
|
301
|
+
public java.util.Map<String, Object> detectBlurWithConfidence(Bitmap bitmap) {
|
|
302
|
+
java.util.Map<String, Object> result = new java.util.HashMap<>();
|
|
303
|
+
|
|
304
|
+
if (!isInitialized || tflite == null) {
|
|
305
|
+
Log.w(TAG, "TFLite model not initialized, falling back to Laplacian");
|
|
306
|
+
double laplacianScore = calculateLaplacianBlurScore(bitmap);
|
|
307
|
+
boolean isBlur = laplacianScore < 150;
|
|
308
|
+
double normalizedScore = Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
|
|
309
|
+
double sharpConfidence = normalizedScore;
|
|
310
|
+
double blurConfidence = 1.0 - normalizedScore;
|
|
311
|
+
|
|
312
|
+
result.put("isBlur", isBlur);
|
|
313
|
+
result.put("blurConfidence", blurConfidence);
|
|
314
|
+
result.put("sharpConfidence", sharpConfidence);
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Use the original bitmap directly (no image enhancement)
|
|
320
|
+
Bitmap processedBitmap = bitmap;
|
|
321
|
+
|
|
322
|
+
// Preprocess image for model (resize and potential enhancement)
|
|
323
|
+
inputImageBuffer.load(processedBitmap);
|
|
324
|
+
inputImageBuffer = imageProcessor.process(inputImageBuffer);
|
|
325
|
+
|
|
326
|
+
// Get tensor buffer
|
|
327
|
+
ByteBuffer tensorBuffer = inputImageBuffer.getBuffer();
|
|
328
|
+
|
|
329
|
+
// Check if we need normalization based on data types
|
|
330
|
+
ByteBuffer inferenceBuffer;
|
|
331
|
+
if (inputImageBuffer.getDataType() == DataType.UINT8 && tflite.getInputTensor(0).dataType() == DataType.FLOAT32) {
|
|
332
|
+
inferenceBuffer = normalizeImageBuffer(tensorBuffer);
|
|
333
|
+
} else if (inputImageBuffer.getDataType() == DataType.FLOAT32) {
|
|
334
|
+
// Check if values are in [0,1] range or [0,255] range
|
|
335
|
+
inferenceBuffer = checkAndNormalizeFloat32Buffer(tensorBuffer);
|
|
336
|
+
} else {
|
|
337
|
+
inferenceBuffer = tensorBuffer;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Run inference
|
|
341
|
+
tflite.run(inferenceBuffer, outputProbabilityBuffer.getBuffer().rewind());
|
|
342
|
+
|
|
343
|
+
// Get output probabilities
|
|
344
|
+
float[] probabilities = outputProbabilityBuffer.getFloatArray();
|
|
345
|
+
|
|
346
|
+
// probabilities[0] = blur probability, probabilities[1] = sharp probability
|
|
347
|
+
double blurConfidence = probabilities.length > 0 ? probabilities[0] : 0.0;
|
|
348
|
+
double sharpConfidence = probabilities.length > 1 ? probabilities[1] : 0.0;
|
|
349
|
+
|
|
350
|
+
// Determine if image is blurry using TFLite confidence
|
|
351
|
+
boolean isBlur = (blurConfidence >= 0.99 || sharpConfidence < 0.1);
|
|
352
|
+
|
|
353
|
+
Log.d(TAG, String.format("TFLite Blur Detection with Confidence - Blur: %.6f, Sharp: %.6f, Label: %s",
|
|
354
|
+
blurConfidence, sharpConfidence, isBlur ? "blur" : "sharp"));
|
|
355
|
+
|
|
356
|
+
result.put("isBlur", isBlur);
|
|
357
|
+
result.put("blurConfidence", blurConfidence);
|
|
358
|
+
result.put("sharpConfidence", sharpConfidence);
|
|
359
|
+
return result;
|
|
360
|
+
|
|
361
|
+
} catch (Exception e) {
|
|
362
|
+
Log.e(TAG, "Error during TFLite inference: " + e.getMessage(), e);
|
|
363
|
+
// Fallback to Laplacian algorithm
|
|
364
|
+
double laplacianScore = calculateLaplacianBlurScore(bitmap);
|
|
365
|
+
boolean isBlur = laplacianScore < 150;
|
|
366
|
+
double normalizedScore = Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
|
|
367
|
+
double sharpConfidence = normalizedScore;
|
|
368
|
+
double blurConfidence = 1.0 - normalizedScore;
|
|
369
|
+
|
|
370
|
+
result.put("isBlur", isBlur);
|
|
371
|
+
result.put("blurConfidence", blurConfidence);
|
|
372
|
+
result.put("sharpConfidence", sharpConfidence);
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
345
377
|
/**
|
|
346
378
|
* Check if image is blurry
|
|
347
379
|
* @param bitmap Input image
|
|
@@ -372,7 +404,6 @@ public class BlurDetectionHelper {
|
|
|
372
404
|
tflite = null;
|
|
373
405
|
}
|
|
374
406
|
isInitialized = false;
|
|
375
|
-
Log.d(TAG, "TFLite blur detection model closed");
|
|
376
407
|
}
|
|
377
408
|
|
|
378
409
|
/**
|
|
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager;
|
|
|
7
7
|
import android.content.res.Configuration;
|
|
8
8
|
import android.content.res.Resources;
|
|
9
9
|
import android.graphics.Bitmap;
|
|
10
|
+
import android.graphics.BitmapFactory;
|
|
10
11
|
import android.graphics.Color;
|
|
11
12
|
import android.graphics.drawable.Drawable;
|
|
12
13
|
import android.net.Uri;
|
|
@@ -1372,6 +1373,60 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
1372
1373
|
}
|
|
1373
1374
|
}
|
|
1374
1375
|
|
|
1376
|
+
@PluginMethod
|
|
1377
|
+
public void detectBlur(PluginCall call) {
|
|
1378
|
+
String imageString = call.getString("image");
|
|
1379
|
+
if (imageString == null) {
|
|
1380
|
+
call.reject("Image parameter is required");
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
try {
|
|
1385
|
+
// Convert base64 string to Bitmap
|
|
1386
|
+
String base64String = imageString;
|
|
1387
|
+
if (imageString.startsWith("data:")) {
|
|
1388
|
+
base64String = imageString.substring(imageString.indexOf(",") + 1);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
byte[] decodedBytes = Base64.decode(base64String, Base64.DEFAULT);
|
|
1392
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
|
|
1393
|
+
|
|
1394
|
+
if (bitmap == null) {
|
|
1395
|
+
call.reject("Invalid image data");
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Use the new confidence detection method
|
|
1400
|
+
if (blurDetectionHelper != null && blurDetectionHelper.isInitialized()) {
|
|
1401
|
+
java.util.Map<String, Object> result = blurDetectionHelper.detectBlurWithConfidence(bitmap);
|
|
1402
|
+
|
|
1403
|
+
JSObject jsResult = new JSObject();
|
|
1404
|
+
jsResult.put("isBlur", result.get("isBlur"));
|
|
1405
|
+
jsResult.put("blurConfidence", result.get("blurConfidence"));
|
|
1406
|
+
jsResult.put("sharpConfidence", result.get("sharpConfidence"));
|
|
1407
|
+
|
|
1408
|
+
call.resolve(jsResult);
|
|
1409
|
+
} else {
|
|
1410
|
+
// Fallback to Laplacian algorithm with confidence scores
|
|
1411
|
+
double laplacianScore = calculateLaplacianBlurScore(bitmap);
|
|
1412
|
+
boolean isBlur = laplacianScore < 150;
|
|
1413
|
+
double normalizedScore = Math.max(0.0, Math.min(1.0, laplacianScore / 300.0));
|
|
1414
|
+
double sharpConfidence = normalizedScore;
|
|
1415
|
+
double blurConfidence = 1.0 - normalizedScore;
|
|
1416
|
+
|
|
1417
|
+
JSObject result = new JSObject();
|
|
1418
|
+
result.put("isBlur", isBlur);
|
|
1419
|
+
result.put("blurConfidence", blurConfidence);
|
|
1420
|
+
result.put("sharpConfidence", sharpConfidence);
|
|
1421
|
+
|
|
1422
|
+
call.resolve(result);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
} catch (Exception e) {
|
|
1426
|
+
call.reject("Failed to process image: " + e.getMessage());
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1375
1430
|
@PluginMethod
|
|
1376
1431
|
public void getOrientation(PluginCall call) {
|
|
1377
1432
|
int orientation = getContext().getResources().getConfiguration().orientation;
|
package/dist/docs.json
CHANGED
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
"name": "takeSnapshot",
|
|
192
|
-
"signature": "(options: { quality?: number; checkBlur?: boolean; }) => Promise<{ base64: string;
|
|
192
|
+
"signature": "(options: { quality?: number; checkBlur?: boolean; }) => Promise<{ base64: string; isBlur?: boolean; }>",
|
|
193
193
|
"parameters": [
|
|
194
194
|
{
|
|
195
195
|
"name": "options",
|
|
@@ -197,12 +197,28 @@
|
|
|
197
197
|
"type": "{ quality?: number | undefined; checkBlur?: boolean | undefined; }"
|
|
198
198
|
}
|
|
199
199
|
],
|
|
200
|
-
"returns": "Promise<{ base64: string;
|
|
200
|
+
"returns": "Promise<{ base64: string; isBlur?: boolean | undefined; }>",
|
|
201
201
|
"tags": [],
|
|
202
202
|
"docs": "take a snapshot as base64.",
|
|
203
203
|
"complexTypes": [],
|
|
204
204
|
"slug": "takesnapshot"
|
|
205
205
|
},
|
|
206
|
+
{
|
|
207
|
+
"name": "detectBlur",
|
|
208
|
+
"signature": "(options: { image: string; }) => Promise<{ isBlur: boolean; blurConfidence: number; sharpConfidence: number; }>",
|
|
209
|
+
"parameters": [
|
|
210
|
+
{
|
|
211
|
+
"name": "options",
|
|
212
|
+
"docs": "",
|
|
213
|
+
"type": "{ image: string; }"
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"returns": "Promise<{ isBlur: boolean; blurConfidence: number; sharpConfidence: number; }>",
|
|
217
|
+
"tags": [],
|
|
218
|
+
"docs": "analyze an image for blur detection with detailed confidence scores.",
|
|
219
|
+
"complexTypes": [],
|
|
220
|
+
"slug": "detectblur"
|
|
221
|
+
},
|
|
206
222
|
{
|
|
207
223
|
"name": "saveFrame",
|
|
208
224
|
"signature": "() => Promise<{ success: boolean; }>",
|
|
@@ -233,7 +249,7 @@
|
|
|
233
249
|
},
|
|
234
250
|
{
|
|
235
251
|
"name": "takePhoto",
|
|
236
|
-
"signature": "(options: { pathToSave?: string; includeBase64?: boolean; }) => Promise<{ path?: string; base64?: string; blob?: Blob;
|
|
252
|
+
"signature": "(options: { pathToSave?: string; includeBase64?: boolean; }) => Promise<{ path?: string; base64?: string; blob?: Blob; isBlur?: boolean; }>",
|
|
237
253
|
"parameters": [
|
|
238
254
|
{
|
|
239
255
|
"name": "options",
|
|
@@ -241,7 +257,7 @@
|
|
|
241
257
|
"type": "{ pathToSave?: string | undefined; includeBase64?: boolean | undefined; }"
|
|
242
258
|
}
|
|
243
259
|
],
|
|
244
|
-
"returns": "Promise<{ path?: string | undefined; base64?: string | undefined; blob?: any;
|
|
260
|
+
"returns": "Promise<{ path?: string | undefined; base64?: string | undefined; blob?: any; isBlur?: boolean | undefined; }>",
|
|
245
261
|
"tags": [],
|
|
246
262
|
"docs": "",
|
|
247
263
|
"complexTypes": [
|
|
@@ -46,7 +46,17 @@ export interface CameraPreviewPlugin {
|
|
|
46
46
|
checkBlur?: boolean;
|
|
47
47
|
}): Promise<{
|
|
48
48
|
base64: string;
|
|
49
|
-
|
|
49
|
+
isBlur?: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* analyze an image for blur detection with detailed confidence scores.
|
|
53
|
+
*/
|
|
54
|
+
detectBlur(options: {
|
|
55
|
+
image: string;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
isBlur: boolean;
|
|
58
|
+
blurConfidence: number;
|
|
59
|
+
sharpConfidence: number;
|
|
50
60
|
}>;
|
|
51
61
|
/**
|
|
52
62
|
* save a frame internally. Android and iOS only.
|
|
@@ -70,7 +80,7 @@ export interface CameraPreviewPlugin {
|
|
|
70
80
|
path?: string;
|
|
71
81
|
base64?: string;
|
|
72
82
|
blob?: Blob;
|
|
73
|
-
|
|
83
|
+
isBlur?: boolean;
|
|
74
84
|
}>;
|
|
75
85
|
toggleTorch(options: {
|
|
76
86
|
on: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AA2EA,MAAM,CAAN,IAAY,cAOX;AAPD,WAAY,cAAc;IACxB,yEAAmB,CAAA;IACnB,yEAAmB,CAAA;IACnB,yEAAmB,CAAA;IACnB,2EAAoB,CAAA;IACpB,qEAAiB,CAAA;IACjB,qEAAiB,CAAA;AACnB,CAAC,EAPW,cAAc,KAAd,cAAc,QAOzB","sourcesContent":["import { PluginListenerHandle } from \"@capacitor/core\";\n\nexport interface CameraPreviewPlugin {\n initialize(options?: { quality?: number }): Promise<void>;\n getResolution(): Promise<{resolution: string}>;\n setResolution(options: {resolution: number}): Promise<void>;\n getAllCameras(): Promise<{cameras: string[]}>;\n getSelectedCamera(): Promise<{selectedCamera: string}>;\n selectCamera(options: {cameraID: string; }): Promise<void>;\n setScanRegion(options: {region:ScanRegion}): Promise<void>;\n setZoom(options: {factor: number}): Promise<void>;\n setFocus(options: {x: number, y: number}): Promise<void>;\n /**\n * Web Only\n */\n setDefaultUIElementURL(url:string): Promise<void>;\n /**\n * Web Only\n */\n setElement(ele:HTMLElement): Promise<void>;\n startCamera(): Promise<void>;\n stopCamera(): Promise<void>;\n /**\n * take a snapshot as base64.\n */\n takeSnapshot(options:{quality?:number, checkBlur?:boolean}): Promise<{base64:string, isBlur?: boolean}>;\n /**\n * analyze an image for blur detection with detailed confidence scores.\n */\n detectBlur(options:{image: string}): Promise<{isBlur: boolean, blurConfidence: number, sharpConfidence: number}>;\n /**\n * save a frame internally. Android and iOS only.\n */\n saveFrame(): Promise<{success:boolean}>;\n /**\n * take a snapshot on to a canvas. Web Only\n */\n takeSnapshot2(options:{canvas:HTMLCanvasElement,maxLength?:number}): Promise<{scaleRatio?:number}>;\n takePhoto(options: {pathToSave?:string,includeBase64?: boolean}): Promise<{path?:string,base64?:string,blob?:Blob, isBlur?: boolean}>;\n toggleTorch(options: {on: boolean}): Promise<void>;\n /**\n * get the orientation of the device.\n */\n getOrientation(): Promise<{\"orientation\":\"PORTRAIT\"|\"LANDSCAPE\"}>;\n startRecording(): Promise<void>;\n stopRecording(options:{includeBase64?:boolean}): Promise<{path?:string,base64?:string,blob?:Blob}>;\n setLayout(options: {top: string, left:string, width:string, height:string}): Promise<void>;\n requestCameraPermission(): Promise<void>;\n requestMicroPhonePermission(): Promise<void>;\n isOpen():Promise<{isOpen:boolean}>;\n addListener(\n eventName: 'onPlayed',\n listenerFunc: onPlayedListener,\n ): Promise<PluginListenerHandle>;\n addListener(\n eventName: 'onOrientationChanged',\n listenerFunc: onOrientationChangedListener,\n ): Promise<PluginListenerHandle>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type onPlayedListener = (result:{resolution:string}) => void;\nexport type onOrientationChangedListener = () => void;\n\n/**\n * measuredByPercentage: 0 in pixel, 1 in percent\n */\nexport interface ScanRegion{\n left: number;\n top: number;\n right: number;\n bottom: number;\n measuredByPercentage: number;\n}\n\nexport enum EnumResolution {\n RESOLUTION_AUTO = 0,\n RESOLUTION_480P = 1,\n RESOLUTION_720P = 2,\n RESOLUTION_1080P = 3,\n RESOLUTION_2K = 4,\n RESOLUTION_4K = 5\n}\n"]}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -58,7 +58,14 @@ export declare class CameraPreviewWeb extends WebPlugin implements CameraPreview
|
|
|
58
58
|
checkBlur?: boolean;
|
|
59
59
|
}): Promise<{
|
|
60
60
|
base64: string;
|
|
61
|
-
|
|
61
|
+
isBlur?: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
detectBlur(options: {
|
|
64
|
+
image: string;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
isBlur: boolean;
|
|
67
|
+
blurConfidence: number;
|
|
68
|
+
sharpConfidence: number;
|
|
62
69
|
}>;
|
|
63
70
|
takeSnapshot2(options: {
|
|
64
71
|
canvas: HTMLCanvasElement;
|