@scr2em/capacitor-scanner 6.0.7 → 6.0.9

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 CHANGED
@@ -16,7 +16,7 @@ npx cap sync
16
16
  * [`startScanning(...)`](#startscanning)
17
17
  * [`stopScanning()`](#stopscanning)
18
18
  * [`openSettings()`](#opensettings)
19
- * [`capturePhoto()`](#capturephoto)
19
+ * [`capturePhoto(...)`](#capturephoto)
20
20
  * [`checkPermissions()`](#checkpermissions)
21
21
  * [`requestPermissions()`](#requestpermissions)
22
22
  * [`flipCamera()`](#flipcamera)
@@ -62,12 +62,16 @@ openSettings() => Promise<void>
62
62
  --------------------
63
63
 
64
64
 
65
- ### capturePhoto()
65
+ ### capturePhoto(...)
66
66
 
67
67
  ```typescript
68
- capturePhoto() => Promise<CapturePhotoResult>
68
+ capturePhoto(options?: CapturePhotoOptions | undefined) => Promise<CapturePhotoResult>
69
69
  ```
70
70
 
71
+ | Param | Type |
72
+ | ------------- | ------------------------------------------------------------------- |
73
+ | **`options`** | <code><a href="#capturephotooptions">CapturePhotoOptions</a></code> |
74
+
71
75
  **Returns:** <code>Promise&lt;<a href="#capturephotoresult">CapturePhotoResult</a>&gt;</code>
72
76
 
73
77
  --------------------
@@ -151,6 +155,11 @@ removeAllListeners() => Promise<void>
151
155
  <code>{ imageBase64: string }</code>
152
156
 
153
157
 
158
+ #### CapturePhotoOptions
159
+
160
+ <code>{ /** * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size) * and 1.0 (highest quality, largest file size). Defaults to 1.0. * This parameter directly influences the compression level of the resulting JPEG image. */ qualityRatio?: number; }</code>
161
+
162
+
154
163
  #### PermissionsResult
155
164
 
156
165
  <code>{ camera: 'prompt' | 'denied' | 'granted' }</code>
@@ -3,8 +3,13 @@ package com.leadliaion.capacitorscanner;
3
3
  import android.Manifest;
4
4
  import android.content.Intent;
5
5
  import android.content.pm.PackageManager;
6
+ import android.graphics.Bitmap;
7
+ import android.graphics.BitmapFactory;
6
8
  import android.graphics.Color;
9
+ import android.graphics.Matrix;
10
+ import androidx.exifinterface.media.ExifInterface;
7
11
  import android.net.Uri;
12
+ import android.os.Build;
8
13
  import android.provider.Settings;
9
14
  import android.util.Base64;
10
15
  import android.util.DisplayMetrics;
@@ -49,8 +54,10 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
49
54
  import com.google.mlkit.vision.barcode.BarcodeScanning;
50
55
  import com.google.mlkit.vision.common.InputImage;
51
56
 
57
+ import java.io.ByteArrayInputStream;
52
58
  import java.io.ByteArrayOutputStream;
53
59
  import java.io.IOException;
60
+ import java.io.InputStream;
54
61
  import java.util.concurrent.ExecutionException;
55
62
  import java.util.concurrent.Executor;
56
63
  import java.util.concurrent.Executors;
@@ -390,6 +397,10 @@ public class CapacitorScannerPlugin extends Plugin {
390
397
  return;
391
398
  }
392
399
 
400
+ Double qualityRatioObj = call.getDouble("qualityRatio", 1.0);
401
+ double qualityRatio = qualityRatioObj != null ? qualityRatioObj : 1.0;
402
+
403
+ // Temporary stream to receive the captured image data.
393
404
  ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
394
405
 
395
406
  ImageCapture.OutputFileOptions outputOptions =
@@ -400,17 +411,112 @@ public class CapacitorScannerPlugin extends Plugin {
400
411
  @Override
401
412
  public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
402
413
  try {
403
- byte[] bytes = outputStream.toByteArray();
414
+ // Get the original image bytes.
415
+ byte[] originalBytes = outputStream.toByteArray();
416
+ int originalSize = originalBytes.length; // in bytes
417
+
418
+ BitmapFactory.Options options = new BitmapFactory.Options();
419
+ options.inJustDecodeBounds = true;
420
+ BitmapFactory.decodeByteArray(originalBytes, 0, originalSize, options);
421
+
422
+ // If qualityRatio is between 0 and 1 exclusively, perform quality reduction.
423
+ if (qualityRatio > 0 && qualityRatio < 1) {
424
+ // Calculate the target size in bytes.
425
+ int targetSize = (int) (originalSize * qualityRatio);
426
+
427
+ // Read EXIF orientation data from original image
428
+ int orientation = ExifInterface.ORIENTATION_UNDEFINED;
429
+
430
+ try {
431
+ InputStream inputStream = new ByteArrayInputStream(originalBytes);
432
+ ExifInterface exif = null;
433
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
434
+ exif = new ExifInterface(inputStream);
435
+ orientation = exif.getAttributeInt(
436
+ ExifInterface.TAG_ORIENTATION,
437
+ ExifInterface.ORIENTATION_UNDEFINED);
438
+ }
439
+ inputStream.close();
440
+ } catch (IOException e) {
441
+ echo("Warning: Could not read EXIF orientation: " + e.getMessage());
442
+ }
443
+
444
+
445
+ // Decode the captured image into a Bitmap.
446
+ options.inJustDecodeBounds = false;
447
+ options.inSampleSize = calculateInSampleSize(options, 1024, 1024); // Max size of 1024x1024
448
+ Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalBytes, 0, originalSize, options);
449
+ if (originalBitmap == null) {
450
+ call.reject("Failed to decode captured image.");
451
+ return;
452
+ }
453
+
454
+ // Apply rotation based on EXIF orientation
455
+ Matrix matrix = new Matrix();
456
+ switch (orientation) {
457
+ case ExifInterface.ORIENTATION_ROTATE_90:
458
+ matrix.postRotate(90);
459
+ break;
460
+ case ExifInterface.ORIENTATION_ROTATE_180:
461
+ matrix.postRotate(180);
462
+ break;
463
+ case ExifInterface.ORIENTATION_ROTATE_270:
464
+ matrix.postRotate(270);
465
+ break;
466
+ case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
467
+ matrix.postScale(-1, 1);
468
+ break;
469
+ case ExifInterface.ORIENTATION_FLIP_VERTICAL:
470
+ matrix.postScale(1, -1);
471
+ break;
472
+ }
473
+
474
+ // Apply the rotation if needed
475
+ if (!matrix.isIdentity()) {
476
+ Bitmap rotatedBitmap = Bitmap.createBitmap(
477
+ originalBitmap, 0, 0,
478
+ originalBitmap.getWidth(), originalBitmap.getHeight(),
479
+ matrix, true);
480
+ originalBitmap.recycle(); // Recycle the old bitmap to free memory.
481
+ originalBitmap = rotatedBitmap;
482
+ }
483
+
484
+ // Prepare a stream to hold the compressed bitmap.
485
+ ByteArrayOutputStream compressedStream = new ByteArrayOutputStream();
486
+ int quality = 100; // Start with maximum quality (100)
487
+
488
+ // Iteratively compress the bitmap until it reaches the target file size or quality drops to 10%.
489
+ int maxIterations = 10; // Limit iterations to prevent excessive looping.
490
+ int iterations = 0;
491
+ do {
492
+ compressedStream.reset();
493
+ originalBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressedStream);
494
+ quality -= 5; // Reduce quality step by step.
495
+ iterations++;
496
+ } while (compressedStream.toByteArray().length > targetSize && quality > 10 && iterations < maxIterations);
497
+
498
+ // Use the compressed image bytes.
499
+ originalBytes = compressedStream.toByteArray();
500
+
501
+ Bitmap finalBitmap = BitmapFactory.decodeByteArray(originalBytes, 0, originalBytes.length);
502
+ if (finalBitmap != null) {
503
+ finalBitmap.recycle();
504
+ }
404
505
 
405
- String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
506
+ // Cleanup: recycle the bitmap and close the compressed stream.
507
+ originalBitmap.recycle();
508
+ compressedStream.close();
509
+ }
406
510
 
511
+ // Convert the final image bytes to a Base64 string.
512
+ String base64 = Base64.encodeToString(originalBytes, Base64.NO_WRAP);
407
513
  JSObject ret = new JSObject();
408
514
  ret.put("imageBase64", "data:image/jpeg;base64," + base64);
409
-
410
515
  call.resolve(ret);
411
516
  } catch (Exception e) {
412
517
  call.reject("Failed to process image: " + e.getMessage());
413
518
  } finally {
519
+ // Ensure resources are cleaned up.
414
520
  try {
415
521
  outputStream.close();
416
522
  } catch (IOException e) {
@@ -426,12 +532,32 @@ public class CapacitorScannerPlugin extends Plugin {
426
532
  try {
427
533
  outputStream.close();
428
534
  } catch (IOException e) {
429
- echo("Failed to close output stream: " + e.getMessage());
535
+ echo("Failed to close output stream: " + e.getMessage());
430
536
  }
431
537
  }
432
538
  });
433
539
  }
434
540
 
541
+ private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
542
+ // Calculate the largest inSampleSize that is a power of 2 and keeps the image smaller than the requested width/height.
543
+ final int height = options.outHeight;
544
+ final int width = options.outWidth;
545
+ int inSampleSize = 1;
546
+
547
+ if (height > reqHeight || width > reqWidth) {
548
+ final int halfHeight = height / 2;
549
+ final int halfWidth = width / 2;
550
+
551
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps the image smaller than the requested width/height.
552
+ while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
553
+ inSampleSize *= 2;
554
+ }
555
+ }
556
+ return inSampleSize;
557
+ }
558
+
559
+
560
+
435
561
 
436
562
  @PluginMethod
437
563
  public void checkPermissions(PluginCall call) {
package/dist/docs.json CHANGED
@@ -45,13 +45,20 @@
45
45
  },
46
46
  {
47
47
  "name": "capturePhoto",
48
- "signature": "() => Promise<CapturePhotoResult>",
49
- "parameters": [],
48
+ "signature": "(options?: CapturePhotoOptions | undefined) => Promise<CapturePhotoResult>",
49
+ "parameters": [
50
+ {
51
+ "name": "options",
52
+ "docs": "",
53
+ "type": "CapturePhotoOptions | undefined"
54
+ }
55
+ ],
50
56
  "returns": "Promise<CapturePhotoResult>",
51
57
  "tags": [],
52
58
  "docs": "",
53
59
  "complexTypes": [
54
- "CapturePhotoResult"
60
+ "CapturePhotoResult",
61
+ "CapturePhotoOptions"
55
62
  ],
56
63
  "slug": "capturephoto"
57
64
  },
@@ -237,6 +244,17 @@
237
244
  }
238
245
  ]
239
246
  },
247
+ {
248
+ "name": "CapturePhotoOptions",
249
+ "slug": "capturephotooptions",
250
+ "docs": "",
251
+ "types": [
252
+ {
253
+ "text": "{\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n}",
254
+ "complexTypes": []
255
+ }
256
+ ]
257
+ },
240
258
  {
241
259
  "name": "PermissionsResult",
242
260
  "slug": "permissionsresult",
@@ -2,7 +2,7 @@ export interface CapacitorScannerPlugin {
2
2
  startScanning(options?: ScannerOptions): Promise<void>;
3
3
  stopScanning(): Promise<void>;
4
4
  openSettings(): Promise<void>;
5
- capturePhoto(): Promise<CapturePhotoResult>;
5
+ capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;
6
6
  checkPermissions(): Promise<PermissionsResult>;
7
7
  requestPermissions(): Promise<PermissionsResult>;
8
8
  flipCamera(): Promise<void>;
@@ -19,6 +19,14 @@ export declare type BarcodeScannedEvent = {
19
19
  scannedCode: string;
20
20
  format: string;
21
21
  };
22
+ export declare type CapturePhotoOptions = {
23
+ /**
24
+ * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)
25
+ * and 1.0 (highest quality, largest file size). Defaults to 1.0.
26
+ * This parameter directly influences the compression level of the resulting JPEG image.
27
+ */
28
+ qualityRatio?: number;
29
+ };
22
30
  export declare type PermissionsResult = {
23
31
  camera: 'prompt' | 'denied' | 'granted';
24
32
  };
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AA2BA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n startScanning(options?: ScannerOptions): Promise<void>;\n stopScanning(): Promise<void>;\n openSettings(): Promise<void>;\n capturePhoto(): Promise<CapturePhotoResult>;\n checkPermissions(): Promise<PermissionsResult>;\n requestPermissions(): Promise<PermissionsResult>;\n flipCamera(): Promise<void>;\n toggleFlash(): Promise<FlashResult>;\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type ScannerOptions = {\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n startScanning(options?: ScannerOptions): Promise<void>;\n stopScanning(): Promise<void>;\n openSettings(): Promise<void>;\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n checkPermissions(): Promise<PermissionsResult>;\n requestPermissions(): Promise<PermissionsResult>;\n flipCamera(): Promise<void>;\n toggleFlash(): Promise<FlashResult>;\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type ScannerOptions = {\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\nexport type CapturePhotoOptions = {\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n};\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
@@ -56,7 +56,24 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
56
56
  return
57
57
  }
58
58
 
59
- let photoSettings = AVCapturePhotoSettings()
59
+ // Get quality ratio from options, default to 1.0, clamp between 0.0 and 1.0
60
+ let quality = call.getFloat("qualityRatio", 1.0)
61
+ let clampedQuality = max(0.0, min(1.0, quality))
62
+ let qualityNumber = NSNumber(value: clampedQuality) // Convert to NSNumber
63
+
64
+ var photoSettings = AVCapturePhotoSettings() // Initialize with default settings
65
+
66
+ // Check if JPEG is supported and apply compression settings if it is
67
+ if photoOutput.supportedPhotoCodecTypes(for: .jpg).contains(.jpeg) {
68
+ // Create settings specific for JPEG with quality
69
+ photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg, // Use the type directly
70
+ AVVideoCompressionPropertiesKey: [AVVideoQualityKey: qualityNumber]])
71
+ photoSettings.photoQualityPrioritization = .quality // Prioritize quality
72
+ } else {
73
+ print("JPEG codec not supported for photo capture. Using default settings.")
74
+ // photoSettings remains the default initialized one
75
+ }
76
+
60
77
  self.capturePhotoCall = call
61
78
  photoOutput.capturePhoto(with: photoSettings, delegate: self)
62
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.7",
3
+ "version": "6.0.9",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",