Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/odd-rice-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capacitor-mlkit/barcode-scanning': minor
---

feat: add `setFocusPoint(...)` method
28 changes: 28 additions & 0 deletions packages/barcode-scanning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ If you can't see the camera view, make sure all elements in the DOM are not visi
* [`getZoomRatio()`](#getzoomratio)
* [`getMinZoomRatio()`](#getminzoomratio)
* [`getMaxZoomRatio()`](#getmaxzoomratio)
* [`setFocusPoint(...)`](#setfocuspoint)
* [`openSettings()`](#opensettings)
* [`isGoogleBarcodeScannerModuleAvailable()`](#isgooglebarcodescannermoduleavailable)
* [`installGoogleBarcodeScannerModule()`](#installgooglebarcodescannermodule)
Expand Down Expand Up @@ -557,6 +558,25 @@ Only available on Android and iOS.
--------------------


### setFocusPoint(...)

```typescript
setFocusPoint(options: SetFocusPointOptions) => Promise<void>
```

Set the camera's focus point using normalized coordinates.

Only available on Android and iOS.

| Param | Type |
| ------------- | --------------------------------------------------------------------- |
| **`options`** | <code><a href="#setfocuspointoptions">SetFocusPointOptions</a></code> |

**Since:** 8.0.0

--------------------


### openSettings()

```typescript
Expand Down Expand Up @@ -947,6 +967,14 @@ Remove all listeners for this plugin.
| **`zoomRatio`** | <code>number</code> | The maximum zoom ratio. | 5.4.0 |


#### SetFocusPointOptions

| Prop | Type | Description | Since |
| ------- | ------------------- | -------------------------------------- | ----- |
| **`x`** | <code>number</code> | X coordinate (0.0 = left, 1.0 = right) | 8.0.0 |
| **`y`** | <code>number</code> | Y coordinate (0.0 = top, 1.0 = bottom) | 8.0.0 |


#### IsGoogleBarcodeScannerModuleAvailableResult

| Prop | Type | Description | Since |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
import androidx.camera.core.Camera;
import androidx.camera.core.CameraControl;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
Expand All @@ -43,6 +46,7 @@
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions;
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning;
import com.google.mlkit.vision.common.InputImage;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.options.SetFocusPointOptions;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.options.SetZoomRatioOptions;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.results.GetMaxZoomRatioResult;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.results.GetMinZoomRatioResult;
Expand Down Expand Up @@ -315,6 +319,25 @@ public GetMaxZoomRatioResult getMaxZoomRatio() {
return new GetMaxZoomRatioResult(maxZoomRatio);
}

public void setFocusPoint(SetFocusPointOptions options) {
if (camera == null || previewView == null) {
return;
}

float x = options.getX();
float y = options.getY();

// Clamp coordinates to valid range [0.0, 1.0]
x = Math.max(0.0f, Math.min(1.0f, x));
y = Math.max(0.0f, Math.min(1.0f, y));

MeteringPointFactory factory = previewView.getMeteringPointFactory();
MeteringPoint point = factory.createPoint(x * previewView.getWidth(), y * previewView.getHeight());
FocusMeteringAction action = new FocusMeteringAction.Builder(point).build();

camera.getCameraControl().startFocusAndMetering(action);
}

public void openSettings(PluginCall call) {
Uri uri = Uri.fromParts("package", plugin.getAppId(), null);
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;
import com.google.mlkit.vision.barcode.common.Barcode;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.options.SetFocusPointOptions;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.options.SetZoomRatioOptions;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.results.GetMaxZoomRatioResult;
import io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.results.GetMinZoomRatioResult;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class BarcodeScannerPlugin extends Plugin {
public static final String ERROR_GOOGLE_BARCODE_SCANNER_MODULE_ALREADY_INSTALLED =
"The Google Barcode Scanner Module is already installed.";
public static final String ERROR_PERMISSION_DENIED = "User denied access to camera.";
public static final String ERROR_FOCUS_POINT_MISSING = "x and y coordinates must be provided.";

private BarcodeScanner implementation;

Expand Down Expand Up @@ -367,6 +369,35 @@ public void getMaxZoomRatio(PluginCall call) {
}
}

@PluginMethod
public void setFocusPoint(PluginCall call) {
try {
boolean isCameraActive = implementation.isCameraActive();
if (!isCameraActive) {
call.reject(ERROR_NO_ACTIVE_SCAN_SESSION);
return;
}

Float x = call.getFloat("x");
Float y = call.getFloat("y");

if (x == null || y == null) {
call.reject(ERROR_FOCUS_POINT_MISSING);
return;
}

SetFocusPointOptions options = new SetFocusPointOptions(x, y);
getActivity()
.runOnUiThread(() -> {
implementation.setFocusPoint(options);
call.resolve();
});
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}

@PluginMethod
public void openSettings(PluginCall call) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.classes.options;

public class SetFocusPointOptions {

private float x;
private float y;

public SetFocusPointOptions(float x, float y) {
this.x = x;
this.y = y;
}

public float getX() {
return x;
}

public float getY() {
return y;
}
}
34 changes: 34 additions & 0 deletions packages/barcode-scanning/ios/Plugin/BarcodeScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,40 @@ typealias MLKitBarcodeScanner = MLKitBarcodeScanning.BarcodeScanner
return GetMaxZoomRatioResult(zoomRatio: device.maxAvailableVideoZoomFactor)
}

@objc public func setFocusPoint(_ options: SetFocusPointOptions) throws {
guard let device = cameraView?.getCaptureDevice(),
let previewLayer = cameraView?.getPreviewLayer() else {
throw RuntimeError(plugin.errorNoCaptureDeviceAvailable)
}

guard device.isFocusPointOfInterestSupported else {
throw RuntimeError(plugin.errorFocusPointNotSupported)
}

let x = CGFloat(options.getX())
let y = CGFloat(options.getY())

// Clamp coordinates to valid range [0.0, 1.0]
let clampedX = max(0.0, min(1.0, x))
let clampedY = max(0.0, min(1.0, y))

// Convert normalized coordinates to layer coordinates
let layerBounds = previewLayer.bounds
let layerPoint = CGPoint(
x: clampedX * layerBounds.width,
y: clampedY * layerBounds.height
)

let devicePoint = previewLayer.captureDevicePointConverted(fromLayerPoint: layerPoint)

try device.lockForConfiguration()

device.focusPointOfInterest = devicePoint
device.focusMode = .autoFocus

device.unlockForConfiguration()
}

@objc func openSettings(completion: @escaping (Error?) -> Void) {
let url = URL(string: UIApplication.openSettingsURLString)
DispatchQueue.main.async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CAP_PLUGIN_METHOD(getZoomRatio, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getMinZoomRatio, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getMaxZoomRatio, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(setFocusPoint, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(openSettings, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(isGoogleBarcodeScannerModuleAvailable, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(installGoogleBarcodeScannerModule, CAPPluginReturnPromise);
Expand Down
19 changes: 19 additions & 0 deletions packages/barcode-scanning/ios/Plugin/BarcodeScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class BarcodeScannerPlugin: CAPPlugin {
public let errorZoomRatioMissing = "zoomRatio must be provided."
public let errorPermissionDenied = "User denied access to camera."
public let errorOpenSettingsFailed = "Cannot open settings."
public let errorFocusPointNotSupported = "Focus point not supported."
public let errorFocusPointMissing = "x and y coordinates must be provided."
public let barcodeScannedEvent = "barcodeScanned"
public let barcodesScannedEvent = "barcodesScanned"

Expand Down Expand Up @@ -208,6 +210,23 @@ public class BarcodeScannerPlugin: CAPPlugin {
}
}

@objc func setFocusPoint(_ call: CAPPluginCall) {
guard let x = call.getFloat("x"), let y = call.getFloat("y") else {
call.reject(errorFocusPointMissing)
return
}

let options = SetFocusPointOptions(x: x, y: y)

do {
try implementation?.setFocusPoint(options)
call.resolve()
} catch {
CAPLog.print("[", self.tag, "] ", error)
call.reject(error.localizedDescription)
}
}

@objc func openSettings(_ call: CAPPluginCall) {
implementation?.openSettings(completion: { error in
if let error = error {
Expand Down
4 changes: 4 additions & 0 deletions packages/barcode-scanning/ios/Plugin/BarcodeScannerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ public protocol BarcodeScannerViewDelegate {
return self.captureDevice
}

public func getPreviewLayer() -> AVCaptureVideoPreviewLayer? {
return self.videoPreviewLayer
}

private func configureCaptureDevice(_ device: AVCaptureDevice) {
do {
try device.lockForConfiguration()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2023 Robin Genz
*/
import Foundation

@objc public class SetFocusPointOptions: NSObject {
private let x: Float
private let y: Float

init(x: Float, y: Float) {
self.x = x
self.y = y
}

func getX() -> Float {
return x
}

func getY() -> Float {
return y
}
}
26 changes: 26 additions & 0 deletions packages/barcode-scanning/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ export interface BarcodeScannerPlugin {
* @since 5.4.0
*/
getMaxZoomRatio(): Promise<GetMaxZoomRatioResult>;
/**
* Set the camera's focus point using normalized coordinates.
*
* Only available on Android and iOS.
*
* @since 8.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 8.0.0
* @since 7.3.0

Please run npm run docgen again.

*/
setFocusPoint(options: SetFocusPointOptions): Promise<void>;
/**
* Open the settings of the app so that the user can grant the camera permission.
*
Expand Down Expand Up @@ -382,6 +390,24 @@ export interface GetMaxZoomRatioResult {
zoomRatio: number;
}

/**
* @since 8.0.0
*/
export interface SetFocusPointOptions {
/**
* X coordinate (0.0 = left, 1.0 = right)
*
* @since 8.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

*/
x: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this property optional and the default value should be 0.5. What do you think? Then we can remove a few checks and exceptions in the native layer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a great idea - will do

/**
* Y coordinate (0.0 = top, 1.0 = bottom)
*
* @since 8.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

*/
y: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this property optional and the default value should be 0.5. What do you think? Then we can remove a few checks and exceptions in the native layer.

}

/**
* @since 5.1.0
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/barcode-scanning/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ReadBarcodesFromImageOptions,
ReadBarcodesFromImageResult,
ScanResult,
SetFocusPointOptions,
SetZoomRatioOptions,
StartScanOptions,
IsTorchEnabledResult,
Expand Down Expand Up @@ -139,6 +140,10 @@ export class BarcodeScannerWeb
throw this.createUnavailableException();
}

async setFocusPoint(_options: SetFocusPointOptions): Promise<void> {
throw this.createUnavailableException();
}

async openSettings(): Promise<void> {
throw this.createUnavailableException();
}
Expand Down