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
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
implementation "com.google.mlkit:face-detection:16.1.6"
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,15 @@ private void startCamera(final PluginCall call) {
final Boolean disableExifHeaderStripping = call.getBoolean("disableExifHeaderStripping", true);
final Boolean lockOrientation = call.getBoolean("lockAndroidOrientation", false);
final Boolean enableFaceRecognition = call.getBoolean("enableFaceRecognition", false);
final Boolean enableQRCodeRecognition = call.getBoolean("enableQRCodeRecognition", false);
previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();

fragment = new KNewCameraActivity();
fragment.setEventListener(this);
fragment.setDefaultCamera(position.equals("front") ? CameraSelector.DEFAULT_FRONT_CAMERA : CameraSelector.DEFAULT_BACK_CAMERA);
fragment.setStoreToFile(storeToFile);
fragment.setEnableFaceRecognition(enableFaceRecognition);
fragment.setEnableQRCodeRecognition(enableQRCodeRecognition);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

En todos los lugares donde dice QR Code le dejaría genérico barcode, porque QR_CODE es uno de los types,

fragment.setToBack(toBack);

bridge
Expand Down Expand Up @@ -303,6 +305,23 @@ public void onCameraDetected(@NonNull String step, Rect bounds) {
}
notifyListeners("faceRecognized", jsObject);
}
@Override
public void onQRCodeDetected(@NonNull String qrCode, Rect bounds) {
JSObject jsObject = new JSObject();
jsObject.put("qrCodeInformation", qrCode);
if (bounds != null) {
jsObject.put("x", bounds.left);
jsObject.put("y", bounds.top);
jsObject.put("width", bounds.right - bounds.left);
jsObject.put("height", bounds.bottom - bounds.top);
} else {
jsObject.put("x", 0);
jsObject.put("y", 0);
jsObject.put("width", 0);
jsObject.put("height", 0);
}
notifyListeners("qrCodeRecognized", jsObject);
}

private boolean hasView(PluginCall call) {
return fragment != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.ahm.capacitor.camera.preview.capacitorcamerapreview.databinding.NewCameraActivityBinding
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.Face
import com.google.mlkit.vision.face.FaceDetection
Expand All @@ -44,13 +48,15 @@ import java.util.concurrent.Executors


typealias FaceRotationAnalyzerListener = (rotationAnalyzer: String, bounds: Rect?) -> Unit
typealias QRCodeAnalyzerListener = (qrCode: String, bounds: Rect?) -> Unit

class KNewCameraActivity : Fragment() {
interface CameraPreviewListener {
fun onPictureTaken(originalPicture: String?)
fun onPictureTakenError(message: String?)
fun onCameraStarted()
fun onCameraDetected(step: String, bounds: Rect?)
fun onQRCodeDetected(qrCode: String, bounds: Rect?)
}

private var eventListener: CameraPreviewListener? = null
Expand All @@ -62,6 +68,7 @@ class KNewCameraActivity : Fragment() {
var toBack = false
var storeToFile = false
var enableFaceRecognition = false
var enableQRCodeRecognition = false
var width = 0
var height = 0

Expand Down Expand Up @@ -210,13 +217,23 @@ class KNewCameraActivity : Fragment() {
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { it ->
it.setAnalyzer(cameraExecutor, FaceAnalyzer { step, bounds ->
Log.d(TAG, "Step: $step")
bounds?.let { rect ->
Log.d(TAG, "Bounds: ${rect.flattenToString()}")
}
eventListener?.onCameraDetected(step, bounds)
})
if (enableQRCodeRecognition) {
it.setAnalyzer(cameraExecutor, QRCodeAnalyzer { qrCode, bounds ->
Log.d(TAG, "QRCode: $qrCode")
bounds?.let { rect ->
Log.d(TAG, "Bounds: ${rect.flattenToString()}")
}
eventListener?.onQRCodeDetected(qrCode, bounds)
})
} else if (enableFaceRecognition) {
it.setAnalyzer(cameraExecutor, FaceAnalyzer { step, bounds ->
Log.d(TAG, "Step: $step")
bounds?.let { rect ->
Log.d(TAG, "Bounds: ${rect.flattenToString()}")
}
eventListener?.onCameraDetected(step, bounds)
})
}
}

// Select back camera as a default
Expand All @@ -227,13 +244,13 @@ class KNewCameraActivity : Fragment() {
cameraProvider.unbindAll()

// Bind use cases to camera
if (enableFaceRecognition) {
if (enableFaceRecognition || enableQRCodeRecognition) {
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageCapture,
imageAnalyzer
imageAnalyzer,
)
} else {
cameraProvider.bindToLifecycle(
Expand Down Expand Up @@ -323,5 +340,42 @@ class KNewCameraActivity : Fragment() {
}
}
}

private class QRCodeAnalyzer(listener: QRCodeAnalyzerListener? = null) : ImageAnalysis.Analyzer {
private val barCodeScannerOptions = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_PDF417, Barcode.FORMAT_QR_CODE)
.build()
private val qrCodeDetector: BarcodeScanner = BarcodeScanning.getClient(barCodeScannerOptions)
private val listeners = ArrayList<QRCodeAnalyzerListener>().apply { listener?.let { add(it) } }
private var qrCodeObjects: MutableList<Barcode> = ArrayList()

@ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
qrCodeDetector.process(image)
.addOnSuccessListener { qrCodes ->
if (qrCodes.size == 0) {
qrCodes.clear()
listeners.forEach { it("NO_QRCODE", null) }
} else if (qrCodes.size > 1) {
qrCodes.clear()
listeners.forEach { it("MORE_THAN_ONE_QRCODE", null) }
} else {
val qrCode = qrCodes[0]
qrCodeObjects.add(qrCode)
listeners.forEach { it(qrCode.rawValue ?: "", qrCode.boundingBox) }
}
}
.addOnFailureListener { e ->
Log.e(TAG, "QRCode detection failed: " + e.message)
}
.addOnCompleteListener { imageProxy.close() }
} else {
imageProxy.close()
}
}
}
}

43 changes: 31 additions & 12 deletions ios/Plugin/CameraController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import AVFoundation
import UIKit

protocol CameraControllerDelegate: NSObjectProtocol {
func hasRecognize(step: String, bounds: CGRect?)
func hasRecognizeFace(step: String, bounds: CGRect?)
func hasRecognizeQRCode(qrCodeInformation: String, bounds: CGRect?)
}

extension UIWindow {
Expand Down Expand Up @@ -53,12 +54,12 @@ class CameraController: NSObject {

var highResolutionOutput: Bool = false
var enableFaceRecognition: Bool = false
var enableQRCodeRecognition: Bool = false

var audioDevice: AVCaptureDevice?
var audioInput: AVCaptureDeviceInput?

var zoomFactor: CGFloat = 1.0
var hasRecognizeFace: Bool = false

var faceMetadataObjects = [AVMetadataFaceObject]()
}
Expand Down Expand Up @@ -151,9 +152,12 @@ extension CameraController {
}

self.metadataOutput = AVCaptureMetadataOutput()
if (enableFaceRecognition && captureSession.canAddOutput(self.metadataOutput!)) {
if ((enableFaceRecognition || enableQRCodeRecognition) && captureSession.canAddOutput(self.metadataOutput!)) {
captureSession.addOutput(self.metadataOutput!)
self.metadataOutput!.metadataObjectTypes = [.face]
var objTypes: [AVMetadataObject.ObjectType] = []
if (enableFaceRecognition) { objTypes.append(.face) }
if (enableQRCodeRecognition) { objTypes.append(contentsOf: [.pdf417, .qr]) }
self.metadataOutput!.metadataObjectTypes = objTypes
self.metadataOutput!.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
}

Expand Down Expand Up @@ -531,22 +535,37 @@ extension CameraController: AVCaptureMetadataOutputObjectsDelegate {

let yawAvg = self.faceMetadataObjects.reduce(0, { $0 + ($1.yawAngle >= 180 ? $1.yawAngle - 360 : $1.yawAngle) }) / CGFloat(self.faceMetadataObjects.count)
if (yawAvg > 20 && yawAvg < 80) {
delegate?.hasRecognize(step: "LEFT", bounds: viewBounds)
delegate?.hasRecognizeFace(step: "LEFT", bounds: viewBounds)
} else if (yawAvg > -80 && yawAvg < -20) {
delegate?.hasRecognize(step: "RIGHT", bounds: viewBounds)
delegate?.hasRecognizeFace(step: "RIGHT", bounds: viewBounds)
} else if (yawAvg > -10 && yawAvg < 10) {
delegate?.hasRecognize(step: "CENTER", bounds: viewBounds)
delegate?.hasRecognizeFace(step: "CENTER", bounds: viewBounds)
}
}
}

private func analyzeQRCode(_ qrCode: AVMetadataMachineReadableCodeObject) {
if let code = qrCode.stringValue {
delegate?.hasRecognizeQRCode(qrCodeInformation: code, bounds: nil)
}
}

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if metadataObjects.count == 0 {
delegate?.hasRecognize(step: "NO_FACES", bounds: nil)
} else if metadataObjects.count > 1 {
delegate?.hasRecognize(step: "MORE_THAN_ONE_FACE", bounds: nil)
} else if let metadataObj = metadataObjects[0] as? AVMetadataFaceObject {
analyzeFace(metadataObj)
if (enableFaceRecognition) { delegate?.hasRecognizeFace(step: "NO_FACE", bounds: nil) }
if (enableQRCodeRecognition) { delegate?.hasRecognizeQRCode(qrCodeInformation: "NO_QRCODE", bounds: nil) }
} else if metadataObjects.count > 1 && enableFaceRecognition && !enableQRCodeRecognition {
delegate?.hasRecognizeFace(step: "MORE_THAN_ONE_FACE", bounds: nil)
} else if metadataObjects.count > 1 && !enableFaceRecognition && enableQRCodeRecognition {
delegate?.hasRecognizeQRCode(qrCodeInformation: "MORE_THAN_ONE_QRCODE", bounds: nil)
} else {
metadataObjects.forEach { obj in
if let face = obj as? AVMetadataFaceObject, enableFaceRecognition {
analyzeFace(face)
} else if let qrCode = obj as? AVMetadataMachineReadableCodeObject, enableQRCodeRecognition {
analyzeQRCode(qrCode)
}
}
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion ios/Plugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class CameraPreview: CAPPlugin {
self.highResolutionOutput = call.getBool("enableHighResolution") ?? false
self.cameraController.highResolutionOutput = self.highResolutionOutput
self.cameraController.enableFaceRecognition = call.getBool("enableFaceRecognition", false)
self.cameraController.enableQRCodeRecognition = call.getBool("enableQRCodeRecognition", false)
self.cameraController.delegate = self

if call.getInt("width") != nil {
Expand Down Expand Up @@ -227,7 +228,7 @@ public class CameraPreview: CAPPlugin {
}

extension CameraPreview: CameraControllerDelegate {
func hasRecognize(step: String, bounds: CGRect?) {
func hasRecognizeFace(step: String, bounds: CGRect?) {
let data: [String : Any] = [
"step": step,
"x": bounds?.origin.x ?? 0,
Expand All @@ -237,4 +238,15 @@ extension CameraPreview: CameraControllerDelegate {
]
notifyListeners("faceRecognized", data: data)
}

func hasRecognizeQRCode(qrCodeInformation: String, bounds: CGRect?) {
let data: [String : Any] = [
"qrCodeInformation": qrCodeInformation,
"x": bounds?.origin.x ?? 0,
"y": bounds?.origin.y ?? 0,
"width": bounds?.width ?? 0,
"height": bounds?.height ?? 0,
]
notifyListeners("qrCodeRecognized", data: data)
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@capacitor-community/camera-preview",
"version": "6.1.0",
"version": "6.2.0",
"description": "Camera preview",
"main": "dist/plugin.cjs.js",
"module": "dist/esm/index.js",
Expand Down
4 changes: 4 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,9 @@ export interface CameraPreviewPlugin {
eventName: 'faceRecognized',
listenerFunc: (recognition: { step: string }) => void
): Promise<PluginListenerHandle>;
addListener(
eventName: 'qrCodeRecognized',
listenerFunc: (recognition: { qrCodeInformation: string }) => void
): Promise<PluginListenerHandle>;
removeAllListeners(): Promise<void>;
}