From 340da039495d6b87a8df2061e8d5fc48e2c5e20b Mon Sep 17 00:00:00 2001 From: bachino90 Date: Tue, 13 Feb 2024 22:16:35 +0100 Subject: [PATCH 01/11] Add new camer activity that uses camera x --- android/build.gradle | 17 ++ .../camera/preview/KNewCameraActivity.kt | 253 ++++++++++++++++++ .../main/res/layout/new_camera_activity.xml | 22 ++ 3 files changed, 292 insertions(+) create mode 100644 android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt create mode 100644 android/src/main/res/layout/new_camera_activity.xml diff --git a/android/build.gradle b/android/build.gradle index 4bb62fb9..44c18c8b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,6 +37,13 @@ android { lintOptions { abortOnError false } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } } repositories { @@ -50,6 +57,16 @@ dependencies { implementation project(':capacitor-android') implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + def camerax_version = "1.2.2" + implementation "androidx.camera:camera-core:${camerax_version}" + implementation "androidx.camera:camera-camera2:${camerax_version}" + implementation "androidx.camera:camera-lifecycle:${camerax_version}" + implementation "androidx.camera:camera-video:${camerax_version}" + + implementation "androidx.camera:camera-view:${camerax_version}" + implementation "androidx.camera:camera-extensions:${camerax_version}" + implementation "com.google.mlkit:face-detection:16.1.5" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt new file mode 100644 index 00000000..a72f00d7 --- /dev/null +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt @@ -0,0 +1,253 @@ +package com.ahm.capacitor.camera.preview + +import android.Manifest +import android.content.ContentValues +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Matrix +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.CameraSelector +import androidx.camera.core.ExperimentalGetImage +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +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.common.InputImage +import com.google.mlkit.vision.face.FaceDetection +import com.google.mlkit.vision.face.FaceDetector +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +typealias FaceAnalyzerListener = (analyzer: Double) -> Unit + +class KNewCameraActivity : Fragment() { + private lateinit var viewBinding: NewCameraActivityBinding + + private var imageCapture: ImageCapture? = null + + private lateinit var cameraExecutor: ExecutorService + + private val activityResultLauncher = + registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions()) + { permissions -> + // Handle Permission granted/rejected + var permissionGranted = true + permissions.entries.forEach { + if (it.key in REQUIRED_PERMISSIONS && !it.value) + permissionGranted = false + } + if (!permissionGranted) { + Toast.makeText(requireContext(), "Permission request denied", Toast.LENGTH_SHORT).show() + } else { + startCamera() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + viewBinding = NewCameraActivityBinding.inflate(layoutInflater, container, false) + + // Request camera permissions + if (allPermissionsGranted()) { + startCamera() + } else { + requestPermissions() + } + + cameraExecutor = Executors.newSingleThreadExecutor() + + return viewBinding.root + } + + private fun takePhoto() { + // Get a stable reference of the modifiable image capture use case + val imageCapture = imageCapture ?: return + + // Create time stamped name and MediaStore entry. + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) + .format(System.currentTimeMillis()) + + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") + } + } + + // Create output options object which contains file + metadata + val outputOptions = ImageCapture.OutputFileOptions + .Builder(requireContext().contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + .build() + + // Set up image capture listener, which is triggered after photo has + // been taken + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(requireContext()), + object : ImageCapture.OnImageSavedCallback { + override fun onError(exc: ImageCaptureException) { + Log.e(TAG, "Photo capture failed: ${exc.message}", exc) + } + + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + } + } + ) + } + + private fun captureVideo() {} + + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) + + cameraProviderFuture.addListener({ + // Used to bind the lifecycle of cameras to the lifecycle owner + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + + // Preview + val preview = Preview.Builder() + .build() + .also { + it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) + } + + imageCapture = ImageCapture.Builder() + .build() + + val imageAnalyzer = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { + it.setAnalyzer(cameraExecutor, FaceAnalyzer {eulerY -> + Log.d(TAG, "eulerY: $eulerY") + }) + } + + // Select back camera as a default + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + try { + // Unbind use cases before rebinding + cameraProvider.unbindAll() + + // Bind use cases to camera + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) + + } catch(exc: Exception) { + Log.e(TAG, "Use case binding failed", exc) + } + + }, ContextCompat.getMainExecutor(requireContext())) + } + + private fun requestPermissions() { + activityResultLauncher.launch(REQUIRED_PERMISSIONS) + } + + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED + } + + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + } + + companion object { + private const val TAG = "CameraXApp" + private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" + private val REQUIRED_PERMISSIONS = + mutableListOf ( + Manifest.permission.CAMERA, + Manifest.permission.RECORD_AUDIO + ).apply { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + }.toTypedArray() + } + + private class FaceAnalyzer(listener: FaceAnalyzerListener? = null) : ImageAnalysis.Analyzer { + private val faceDetector: FaceDetector = FaceDetection.getClient() + private val listeners = ArrayList().apply { listener?.let { add(it) } } + + @ExperimentalGetImage + override fun analyze(imageProxy: ImageProxy) { + val bitmap = getBitmapFromImageProxy(imageProxy) + if (bitmap != null) { + val inputImage = InputImage.fromBitmap(bitmap, 0) + faceDetector.process(inputImage) + .addOnSuccessListener { faces -> + if (faces.size == 1) { + val face = faces[0] + val eulerY: Float = face.headEulerAngleY // Euler Y angle represents the rotation around the vertical axis. + Log.d(TAG, "Euler Y: $eulerY") + listeners.forEach { it(eulerY.toDouble()) } + + // Check if face turns right +// if (eulerY > 30) { +// // Face turns right logic here +// Log.d(TAG, "Face turns right!") +// } + } + } + .addOnFailureListener { e -> + Log.e(TAG, "Face detection failed: " + e.message) + } + .addOnCompleteListener { imageProxy.close() } + } else { + imageProxy.close() + } + } + + @ExperimentalGetImage + private fun getBitmapFromImageProxy(imageProxy: ImageProxy): Bitmap? { + if (imageProxy.image != null) { + val bitmap = Bitmap.createBitmap( + imageProxy.width, + imageProxy.height, + Bitmap.Config.ARGB_8888 + ) + val matrix = Matrix() + matrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat()) + val canvas = Canvas(bitmap) + canvas.drawBitmap( + BitmapFactory.decodeByteArray( + imageProxy.planes[0].buffer.array(), + 0, imageProxy.planes[0].buffer.remaining() + ), + matrix, + null + ) + return bitmap + } + return null + } + } +} \ No newline at end of file diff --git a/android/src/main/res/layout/new_camera_activity.xml b/android/src/main/res/layout/new_camera_activity.xml new file mode 100644 index 00000000..580d0af5 --- /dev/null +++ b/android/src/main/res/layout/new_camera_activity.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file From 0dbd1faa0840a50e4c5b7f2cfd58f947c2c6d6fb Mon Sep 17 00:00:00 2001 From: bachino90 Date: Tue, 13 Feb 2024 22:18:56 +0100 Subject: [PATCH 02/11] Fix --- android/src/main/res/layout/new_camera_activity.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/src/main/res/layout/new_camera_activity.xml b/android/src/main/res/layout/new_camera_activity.xml index 580d0af5..bc4c6dc0 100644 --- a/android/src/main/res/layout/new_camera_activity.xml +++ b/android/src/main/res/layout/new_camera_activity.xml @@ -4,8 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".NewCameraActivity"> + android:layout_height="match_parent"> Date: Tue, 13 Feb 2024 22:34:26 +0100 Subject: [PATCH 03/11] Add support for kotlin --- android/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/build.gradle b/android/build.gradle index 44c18c8b..34d392d7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,10 +13,12 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.0.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21" } } apply plugin: 'com.android.library' +apply plugin: plugins.kotlin android { namespace "com.ahm.capacitor.camera.preview.capacitorcamerapreview" From d9ea05d89b46699b706cfba984b2687e2025dd06 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Tue, 13 Feb 2024 22:47:09 +0100 Subject: [PATCH 04/11] Fix gradle --- android/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 34d392d7..db582cb6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,12 +13,12 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.0.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20" } } apply plugin: 'com.android.library' -apply plugin: plugins.kotlin +apply plugin: 'kotlin-android' android { namespace "com.ahm.capacitor.camera.preview.capacitorcamerapreview" @@ -60,6 +60,7 @@ dependencies { implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' def camerax_version = "1.2.2" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" From 1895360867e215091f3ea4b0313eb4b2bb143e2a Mon Sep 17 00:00:00 2001 From: bachino90 Date: Tue, 13 Feb 2024 22:52:18 +0100 Subject: [PATCH 05/11] Update camera version --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index db582cb6..10633a45 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -61,7 +61,7 @@ dependencies { implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' - def camerax_version = "1.2.2" + def camerax_version = "1.3.1" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" From 815ae557af33005cc2ab38eee336d2064b0c5aa0 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Fri, 16 Feb 2024 23:21:46 +0100 Subject: [PATCH 06/11] Continue --- .../camera/preview/CameraPreview.java | 329 +++++++++--------- .../camera/preview/KNewCameraActivity.kt | 306 ++++++++++------ .../main/res/layout/new_camera_activity.xml | 18 +- ios/Plugin/Plugin.swift | 112 +++--- src/definitions.ts | 8 +- 5 files changed, 425 insertions(+), 348 deletions(-) diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java index 784a15b6..f120ce22 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java @@ -7,7 +7,6 @@ import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.Point; -import android.hardware.Camera; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; @@ -15,6 +14,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; + +import androidx.camera.core.CameraSelector; + import com.getcapacitor.JSObject; import com.getcapacitor.Logger; import com.getcapacitor.PermissionState; @@ -26,25 +28,24 @@ import com.getcapacitor.annotation.PermissionCallback; import java.io.File; import java.util.List; -import org.json.JSONArray; @CapacitorPlugin(name = "CameraPreview", permissions = { @Permission(strings = { CAMERA }, alias = CameraPreview.CAMERA_PERMISSION_ALIAS) }) -public class CameraPreview extends Plugin implements CameraActivity.CameraPreviewListener { +public class CameraPreview extends Plugin implements KNewCameraActivity.CameraPreviewListener { static final String CAMERA_PERMISSION_ALIAS = "camera"; - private static String VIDEO_FILE_PATH = ""; - private static String VIDEO_FILE_EXTENSION = ".mp4"; +// private static String VIDEO_FILE_PATH = ""; +// private static String VIDEO_FILE_EXTENSION = ".mp4"; private String captureCallbackId = ""; - private String snapshotCallbackId = ""; - private String recordCallbackId = ""; +// private String snapshotCallbackId = ""; +// private String recordCallbackId = ""; private String cameraStartCallbackId = ""; // keep track of previously specified orientation to support locking orientation: private int previousOrientationRequest = -1; - private CameraActivity fragment; + private KNewCameraActivity fragment; private int containerViewId = 20; @PluginMethod @@ -67,46 +68,45 @@ public void flip(PluginCall call) { } } - @PluginMethod - public void setOpacity(PluginCall call) { - if (this.hasCamera(call) == false) { - call.error("Camera is not running"); - return; - } - - bridge.saveCall(call); - Float opacity = call.getFloat("opacity", 1F); - fragment.setOpacity(opacity); - } +// @PluginMethod +// public void setOpacity(PluginCall call) { +// if (!this.hasCamera(call)) { +// call.error("Camera is not running"); +// return; +// } +// +// bridge.saveCall(call); +// Float opacity = call.getFloat("opacity", 1F); +// fragment.setOpacity(opacity); +// } @PluginMethod public void capture(PluginCall call) { - if (this.hasCamera(call) == false) { + if (!this.hasCamera(call)) { call.reject("Camera is not running"); return; } bridge.saveCall(call); captureCallbackId = call.getCallbackId(); - Integer quality = call.getInt("quality", 85); - // Image Dimensions - Optional - Integer width = call.getInt("width", 0); - Integer height = call.getInt("height", 0); - fragment.takePicture(width, height, quality); +// Integer quality = call.getInt("quality", 85); +// Integer width = call.getInt("width", 0); +// Integer height = call.getInt("height", 0); + fragment.takePicture(); } - @PluginMethod - public void captureSample(PluginCall call) { - if (this.hasCamera(call) == false) { - call.reject("Camera is not running"); - return; - } - bridge.saveCall(call); - snapshotCallbackId = call.getCallbackId(); - - Integer quality = call.getInt("quality", 85); - fragment.takeSnapshot(quality); - } +// @PluginMethod +// public void captureSample(PluginCall call) { +// if (!this.hasCamera(call)) { +// call.reject("Camera is not running"); +// return; +// } +// bridge.saveCall(call); +// snapshotCallbackId = call.getCallbackId(); +// +// Integer quality = call.getInt("quality", 85); +// fragment.takeSnapshot(quality); +// } @PluginMethod public void stop(final PluginCall call) { @@ -141,22 +141,12 @@ public void run() { @PluginMethod public void getSupportedFlashModes(PluginCall call) { - if (this.hasCamera(call) == false) { + if (!this.hasCamera(call)) { call.reject("Camera is not running"); return; } - Camera camera = fragment.getCamera(); - Camera.Parameters params = camera.getParameters(); - List supportedFlashModes; - supportedFlashModes = params.getSupportedFlashModes(); - JSONArray jsonFlashModes = new JSONArray(); - - if (supportedFlashModes != null) { - for (int i = 0; i < supportedFlashModes.size(); i++) { - jsonFlashModes.put(new String(supportedFlashModes.get(i))); - } - } + List jsonFlashModes = fragment.getSupportedFlashModes(getContext()); JSObject jsObject = new JSObject(); jsObject.put("result", jsonFlashModes); @@ -165,89 +155,90 @@ public void getSupportedFlashModes(PluginCall call) { @PluginMethod public void setFlashMode(PluginCall call) { - if (this.hasCamera(call) == false) { + if (!this.hasCamera(call)) { call.reject("Camera is not running"); return; } String flashMode = call.getString("flashMode"); - if (flashMode == null || flashMode.isEmpty() == true) { + if (flashMode == null || flashMode.isEmpty()) { call.reject("flashMode required parameter is missing"); return; } - Camera camera = fragment.getCamera(); - Camera.Parameters params = camera.getParameters(); - - List supportedFlashModes; - supportedFlashModes = camera.getParameters().getSupportedFlashModes(); - if (supportedFlashModes.indexOf(flashMode) > -1) { - params.setFlashMode(flashMode); - } else { - call.reject("Flash mode not recognised: " + flashMode); - return; - } - - fragment.setCameraParameters(params); + fragment.turnOnFlash(); +// Camera camera = fragment.getCamera(); +// Camera.Parameters params = camera.getParameters(); +// +// List supportedFlashModes; +// supportedFlashModes = camera.getParameters().getSupportedFlashModes(); +// if (supportedFlashModes.contains(flashMode)) { +// params.setFlashMode(flashMode); +// } else { +// call.reject("Flash mode not recognised: " + flashMode); +// return; +// } +// +// fragment.setCameraParameters(params); call.resolve(); } - @PluginMethod - public void startRecordVideo(final PluginCall call) { - if (this.hasCamera(call) == false) { - call.reject("Camera is not running"); - return; - } - final String filename = "videoTmp"; - VIDEO_FILE_PATH = getActivity().getCacheDir().toString() + "/"; - - final String position = call.getString("position", "front"); - final Integer width = call.getInt("width", 0); - final Integer height = call.getInt("height", 0); - final Boolean withFlash = call.getBoolean("withFlash", false); - final Integer maxDuration = call.getInt("maxDuration", 0); - // final Integer quality = call.getInt("quality", 0); - bridge.saveCall(call); - recordCallbackId = call.getCallbackId(); - - bridge - .getActivity() - .runOnUiThread( - new Runnable() { - @Override - public void run() { - // fragment.startRecord(getFilePath(filename), position, width, height, quality, withFlash); - fragment.startRecord(getFilePath(filename), position, width, height, 70, withFlash, maxDuration); - } - } - ); - - call.resolve(); - } - - @PluginMethod - public void stopRecordVideo(PluginCall call) { - if (this.hasCamera(call) == false) { - call.reject("Camera is not running"); - return; - } - - System.out.println("stopRecordVideo - Callbackid=" + call.getCallbackId()); - - bridge.saveCall(call); - recordCallbackId = call.getCallbackId(); - - // bridge.getActivity().runOnUiThread(new Runnable() { - // @Override - // public void run() { - // fragment.stopRecord(); - // } - // }); - - fragment.stopRecord(); - // call.resolve(); - } +// @PluginMethod +// public void startRecordVideo(final PluginCall call) { +// if (!this.hasCamera(call)) { +// call.reject("Camera is not running"); +// return; +// } +// final String filename = "videoTmp"; +// VIDEO_FILE_PATH = getActivity().getCacheDir().toString() + "/"; +// +// final String position = call.getString("position", "front"); +// final Integer width = call.getInt("width", 0); +// final Integer height = call.getInt("height", 0); +// final Boolean withFlash = call.getBoolean("withFlash", false); +// final Integer maxDuration = call.getInt("maxDuration", 0); +// // final Integer quality = call.getInt("quality", 0); +// bridge.saveCall(call); +// recordCallbackId = call.getCallbackId(); +// +// bridge +// .getActivity() +// .runOnUiThread( +// new Runnable() { +// @Override +// public void run() { +// // fragment.startRecord(getFilePath(filename), position, width, height, quality, withFlash); +// fragment.startRecord(getFilePath(filename), position, width, height, 70, withFlash, maxDuration); +// } +// } +// ); +// +// call.resolve(); +// } + +// @PluginMethod +// public void stopRecordVideo(PluginCall call) { +// if (!this.hasCamera(call)) { +// call.reject("Camera is not running"); +// return; +// } +// +// System.out.println("stopRecordVideo - Callbackid=" + call.getCallbackId()); +// +// bridge.saveCall(call); +// recordCallbackId = call.getCallbackId(); +// +// // bridge.getActivity().runOnUiThread(new Runnable() { +// // @Override +// // public void run() { +// // fragment.stopRecord(); +// // } +// // }); +// +// fragment.stopRecord(); +// // call.resolve(); +// } @PermissionCallback private void handleCameraPermissionResult(PluginCall call) { @@ -281,17 +272,17 @@ private void startCamera(final PluginCall call) { final Boolean lockOrientation = call.getBoolean("lockAndroidOrientation", false); previousOrientationRequest = getBridge().getActivity().getRequestedOrientation(); - fragment = new CameraActivity(); + fragment = new KNewCameraActivity(); fragment.setEventListener(this); - fragment.defaultCamera = position; - fragment.tapToTakePicture = false; - fragment.dragEnabled = false; - fragment.tapToFocus = true; - fragment.disableExifHeaderStripping = disableExifHeaderStripping; - fragment.storeToFile = storeToFile; - fragment.toBack = toBack; - fragment.enableOpacity = enableOpacity; - fragment.enableZoom = enableZoom; + fragment.setDefaultCamera(position.equals("front") ? CameraSelector.DEFAULT_FRONT_CAMERA : CameraSelector.DEFAULT_BACK_CAMERA); +// fragment.tapToTakePicture = false; +// fragment.dragEnabled = false; +// fragment.tapToFocus = true; +// fragment.disableExifHeaderStripping = disableExifHeaderStripping; +// fragment.storeToFile = storeToFile; +// fragment.toBack = toBack; +// fragment.enableOpacity = enableOpacity; +// fragment.enableZoom = enableZoom; bridge .getActivity() @@ -394,17 +385,17 @@ public void onPictureTakenError(String message) { bridge.getSavedCall(captureCallbackId).reject(message); } - @Override - public void onSnapshotTaken(String originalPicture) { - JSObject jsObject = new JSObject(); - jsObject.put("value", originalPicture); - bridge.getSavedCall(snapshotCallbackId).resolve(jsObject); - } - - @Override - public void onSnapshotTakenError(String message) { - bridge.getSavedCall(snapshotCallbackId).reject(message); - } +// @Override +// public void onSnapshotTaken(String originalPicture) { +// JSObject jsObject = new JSObject(); +// jsObject.put("value", originalPicture); +// bridge.getSavedCall(snapshotCallbackId).resolve(jsObject); +// } +// +// @Override +// public void onSnapshotTakenError(String message) { +// bridge.getSavedCall(snapshotCallbackId).reject(message); +// } @Override public void onFocusSet(int pointX, int pointY) {} @@ -412,8 +403,8 @@ public void onFocusSet(int pointX, int pointY) {} @Override public void onFocusSetError(String message) {} - @Override - public void onBackButton() {} +// @Override +// public void onBackButton() {} @Override public void onCameraStarted() { @@ -422,45 +413,37 @@ public void onCameraStarted() { bridge.releaseCall(pluginCall); } - @Override - public void onStartRecordVideo() {} - - @Override - public void onStartRecordVideoError(String message) { - bridge.getSavedCall(recordCallbackId).reject(message); - } - - @Override - public void onStopRecordVideo(String file) { - PluginCall pluginCall = bridge.getSavedCall(recordCallbackId); - JSObject jsObject = new JSObject(); - jsObject.put("videoFilePath", file); - pluginCall.resolve(jsObject); - } - - @Override - public void onStopRecordVideoError(String error) { - bridge.getSavedCall(recordCallbackId).reject(error); - } +// @Override +// public void onStartRecordVideo() {} +// +// @Override +// public void onStartRecordVideoError(String message) { +// bridge.getSavedCall(recordCallbackId).reject(message); +// } + +// @Override +// public void onStopRecordVideo(String file) { +// PluginCall pluginCall = bridge.getSavedCall(recordCallbackId); +// JSObject jsObject = new JSObject(); +// jsObject.put("videoFilePath", file); +// pluginCall.resolve(jsObject); +// } + +// @Override +// public void onStopRecordVideoError(String error) { +// bridge.getSavedCall(recordCallbackId).reject(error); +// } private boolean hasView(PluginCall call) { - if (fragment == null) { - return false; - } - - return true; + return fragment != null; } private boolean hasCamera(PluginCall call) { - if (this.hasView(call) == false) { - return false; - } - - if (fragment.getCamera() == null) { + if (!this.hasView(call)) { return false; } - return true; + return fragment.hasCamera(); } private String getFilePath(String filename) { diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt index a72f00d7..34af8d40 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt @@ -2,11 +2,14 @@ package com.ahm.capacitor.camera.preview import android.Manifest import android.content.ContentValues +import android.content.Context import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Matrix +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraManager import android.os.Build import android.os.Bundle import android.provider.MediaStore @@ -35,38 +38,87 @@ import java.util.Locale import java.util.concurrent.ExecutorService import java.util.concurrent.Executors - typealias FaceAnalyzerListener = (analyzer: Double) -> Unit class KNewCameraActivity : Fragment() { - private lateinit var viewBinding: NewCameraActivityBinding + interface CameraPreviewListener { + fun onPictureTaken(originalPicture: String?) + fun onPictureTakenError(message: String?) + // fun onSnapshotTaken(originalPicture: String?) + // fun onSnapshotTakenError(message: String?) + fun onFocusSet(pointX: Int, pointY: Int) + fun onFocusSetError(message: String?) + // fun onBackButton() + fun onCameraStarted() + // fun onStartRecordVideo() + // fun onStartRecordVideoError(message: String?) + // fun onStopRecordVideo(file: String?) + // fun onStopRecordVideoError(error: String?) + } + private var eventListener: CameraPreviewListener? = null + private lateinit var viewBinding: NewCameraActivityBinding private var imageCapture: ImageCapture? = null - private lateinit var cameraExecutor: ExecutorService +// private var opacity = 0f + // The first rear facing camera +// private var defaultCameraId = 0 + var defaultCamera: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + // var tapToTakePicture = false + // var dragEnabled = false +// var tapToFocus = false +// var disableExifHeaderStripping = false +// var storeToFile = false + var toBack = false +// var enableOpacity = false + // var enableZoom = false + + private var width = 0 + private var height = 0 + private var x = 0 + private var y = 0 + // fragment.setEventListener(this) + // fragment.defaultCamera = position + // fragment.tapToTakePicture = false + // fragment.dragEnabled = false + // fragment.tapToFocus = true + // fragment.disableExifHeaderStripping = disableExifHeaderStripping + // fragment.storeToFile = storeToFile + // fragment.toBack = toBack + // fragment.enableOpacity = enableOpacity + // fragment.enableZoom = enableZoom + + fun setEventListener(listener: CameraPreviewListener?) { + this.eventListener = listener + } + fun setRect(x: Int, y: Int, width: Int, height: Int) { + this.x = x + this.y = y + this.width = width + this.height = height + } + private val activityResultLauncher = - registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions()) - { permissions -> - // Handle Permission granted/rejected - var permissionGranted = true - permissions.entries.forEach { - if (it.key in REQUIRED_PERMISSIONS && !it.value) - permissionGranted = false - } - if (!permissionGranted) { - Toast.makeText(requireContext(), "Permission request denied", Toast.LENGTH_SHORT).show() - } else { - startCamera() + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + permissions -> + // Handle Permission granted/rejected + var permissionGranted = true + permissions.entries.forEach { + if (it.key in REQUIRED_PERMISSIONS && !it.value) permissionGranted = false + } + if (!permissionGranted) { + Toast.makeText(requireContext(), "Permission request denied", Toast.LENGTH_SHORT).show() + } else { + startCamera() + } } - } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { viewBinding = NewCameraActivityBinding.inflate(layoutInflater, container, false) // Request camera permissions @@ -81,89 +133,138 @@ class KNewCameraActivity : Fragment() { return viewBinding.root } - private fun takePhoto() { + fun hasCamera(): Boolean { + return true + } + + fun switchCamera() { + defaultCamera = + if (defaultCamera == CameraSelector.DEFAULT_BACK_CAMERA) + CameraSelector.DEFAULT_FRONT_CAMERA + else CameraSelector.DEFAULT_BACK_CAMERA + startCamera() + } + + fun getSupportedFlashModes(context: Context): List { + val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager + val cameraIdList = cameraManager.cameraIdList + val supportedFlashModes = mutableListOf() + + for (id in cameraIdList) { + val characteristics = cameraManager.getCameraCharacteristics(id) + val flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) + if (flashAvailable == true) { + supportedFlashModes.add(id) + } + } + + return supportedFlashModes + } + + fun turnOnFlash() { + imageCapture?.flashMode = ImageCapture.FLASH_MODE_ON + } + + fun turnOffFlash() { + imageCapture?.flashMode = ImageCapture.FLASH_MODE_OFF + } + + fun takePicture() { // Get a stable reference of the modifiable image capture use case val imageCapture = imageCapture ?: return // Create time stamped name and MediaStore entry. - val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) - .format(System.currentTimeMillis()) - - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, name) - put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") - if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") - } - } + val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + + val contentValues = + ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CocosCapital-Image") + } + } // Create output options object which contains file + metadata - val outputOptions = ImageCapture.OutputFileOptions - .Builder(requireContext().contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) - .build() + val outputOptions = + ImageCapture.OutputFileOptions.Builder( + requireContext().contentResolver, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + .build() // Set up image capture listener, which is triggered after photo has // been taken imageCapture.takePicture( - outputOptions, - ContextCompat.getMainExecutor(requireContext()), - object : ImageCapture.OnImageSavedCallback { - override fun onError(exc: ImageCaptureException) { - Log.e(TAG, "Photo capture failed: ${exc.message}", exc) - } + outputOptions, + ContextCompat.getMainExecutor(requireContext()), + object : ImageCapture.OnImageSavedCallback { + override fun onError(exc: ImageCaptureException) { + Log.e(TAG, "Photo capture failed: ${exc.message}", exc) + eventListener?.onPictureTakenError(exc.message) + } - override fun onImageSaved(output: ImageCapture.OutputFileResults) { - val msg = "Photo capture succeeded: ${output.savedUri}" - Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() - Log.d(TAG, msg) + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + eventListener?.onPictureTaken(output.savedUri.toString()) + } } - } ) } - private fun captureVideo() {} - private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) - cameraProviderFuture.addListener({ - // Used to bind the lifecycle of cameras to the lifecycle owner - val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() - - // Preview - val preview = Preview.Builder() - .build() - .also { - it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) - } - - imageCapture = ImageCapture.Builder() - .build() - - val imageAnalyzer = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - .also { - it.setAnalyzer(cameraExecutor, FaceAnalyzer {eulerY -> - Log.d(TAG, "eulerY: $eulerY") - }) - } - - // Select back camera as a default - val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - - try { - // Unbind use cases before rebinding - cameraProvider.unbindAll() + cameraProviderFuture.addListener( + { + // Used to bind the lifecycle of cameras to the lifecycle owner + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() - // Bind use cases to camera - cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) + // Preview + val preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) + } - } catch(exc: Exception) { - Log.e(TAG, "Use case binding failed", exc) - } + imageCapture = ImageCapture.Builder() +// .setTargetResolution(Size(1080, 1920)) // Set target resolution here + .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) // Set capture mode here + .build() + + val imageAnalyzer = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { + it.setAnalyzer(cameraExecutor, FaceAnalyzer { eulerY -> + Log.d(TAG, "eulerY: $eulerY") + }) + } - }, ContextCompat.getMainExecutor(requireContext())) + // Select back camera as a default + val cameraSelector = defaultCamera + + try { + // Unbind use cases before rebinding + cameraProvider.unbindAll() + + // Bind use cases to camera + cameraProvider.bindToLifecycle( + this, + cameraSelector, + preview, + imageCapture, + imageAnalyzer + ) + + eventListener?.onCameraStarted() + } catch (exc: Exception) { + Log.e(TAG, "Use case binding failed", exc) + } + }, + ContextCompat.getMainExecutor(requireContext()) + ) } private fun requestPermissions() { @@ -183,10 +284,7 @@ class KNewCameraActivity : Fragment() { private const val TAG = "CameraXApp" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private val REQUIRED_PERMISSIONS = - mutableListOf ( - Manifest.permission.CAMERA, - Manifest.permission.RECORD_AUDIO - ).apply { + mutableListOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).apply { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { add(Manifest.permission.WRITE_EXTERNAL_STORAGE) } @@ -211,10 +309,10 @@ class KNewCameraActivity : Fragment() { listeners.forEach { it(eulerY.toDouble()) } // Check if face turns right -// if (eulerY > 30) { -// // Face turns right logic here -// Log.d(TAG, "Face turns right!") -// } + // if (eulerY > 30) { + // // Face turns right logic here + // Log.d(TAG, "Face turns right!") + // } } } .addOnFailureListener { e -> @@ -229,25 +327,27 @@ class KNewCameraActivity : Fragment() { @ExperimentalGetImage private fun getBitmapFromImageProxy(imageProxy: ImageProxy): Bitmap? { if (imageProxy.image != null) { - val bitmap = Bitmap.createBitmap( - imageProxy.width, - imageProxy.height, - Bitmap.Config.ARGB_8888 - ) + val bitmap = + Bitmap.createBitmap( + imageProxy.width, + imageProxy.height, + Bitmap.Config.ARGB_8888 + ) val matrix = Matrix() matrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat()) val canvas = Canvas(bitmap) canvas.drawBitmap( - BitmapFactory.decodeByteArray( - imageProxy.planes[0].buffer.array(), - 0, imageProxy.planes[0].buffer.remaining() - ), - matrix, - null + BitmapFactory.decodeByteArray( + imageProxy.planes[0].buffer.array(), + 0, + imageProxy.planes[0].buffer.remaining() + ), + matrix, + null ) return bitmap } return null } } -} \ No newline at end of file +} diff --git a/android/src/main/res/layout/new_camera_activity.xml b/android/src/main/res/layout/new_camera_activity.xml index bc4c6dc0..306d9492 100644 --- a/android/src/main/res/layout/new_camera_activity.xml +++ b/android/src/main/res/layout/new_camera_activity.xml @@ -1,21 +1,15 @@ - + android:layout_height="match_parent" + android:layout_gravity="center_horizontal|top" + android:layout_weight=".7"> - - - \ No newline at end of file + \ No newline at end of file diff --git a/ios/Plugin/Plugin.swift b/ios/Plugin/Plugin.swift index dca60503..15c554e6 100644 --- a/ios/Plugin/Plugin.swift +++ b/ios/Plugin/Plugin.swift @@ -194,40 +194,40 @@ public class CameraPreview: CAPPlugin { } } - @objc func captureSample(_ call: CAPPluginCall) { - DispatchQueue.main.async { - let quality: Int? = call.getInt("quality", 85) - - self.cameraController.captureSample { image, error in - guard let image = image else { - print("Image capture error: \(String(describing: error))") - call.reject("Image capture error: \(String(describing: error))") - return - } - - let imageData: Data? - if self.cameraPosition == "front" { - let flippedImage = image.withHorizontallyFlippedOrientation() - imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality!/100)) - } else { - imageData = image.jpegData(compressionQuality: CGFloat(quality!/100)) - } - - if self.storeToFile == false { - let imageBase64 = imageData?.base64EncodedString() - call.resolve(["value": imageBase64!]) - } else { - do { - let fileUrl = self.getTempFilePath() - try imageData?.write(to: fileUrl) - call.resolve(["value": fileUrl.absoluteString]) - } catch { - call.reject("Error writing image to file") - } - } - } - } - } + // @objc func captureSample(_ call: CAPPluginCall) { + // DispatchQueue.main.async { + // let quality: Int? = call.getInt("quality", 85) + + // self.cameraController.captureSample { image, error in + // guard let image = image else { + // print("Image capture error: \(String(describing: error))") + // call.reject("Image capture error: \(String(describing: error))") + // return + // } + + // let imageData: Data? + // if self.cameraPosition == "front" { + // let flippedImage = image.withHorizontallyFlippedOrientation() + // imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality!/100)) + // } else { + // imageData = image.jpegData(compressionQuality: CGFloat(quality!/100)) + // } + + // if self.storeToFile == false { + // let imageBase64 = imageData?.base64EncodedString() + // call.resolve(["value": imageBase64!]) + // } else { + // do { + // let fileUrl = self.getTempFilePath() + // try imageData?.write(to: fileUrl) + // call.resolve(["value": fileUrl.absoluteString]) + // } catch { + // call.reject("Error writing image to file") + // } + // } + // } + // } + // } @objc func getSupportedFlashModes(_ call: CAPPluginCall) { do { @@ -268,36 +268,36 @@ public class CameraPreview: CAPPlugin { } } - @objc func startRecordVideo(_ call: CAPPluginCall) { - DispatchQueue.main.async { + // @objc func startRecordVideo(_ call: CAPPluginCall) { + // DispatchQueue.main.async { - let quality: Int? = call.getInt("quality", 85) + // let quality: Int? = call.getInt("quality", 85) - self.cameraController.captureVideo { (image, error) in + // self.cameraController.captureVideo { (image, error) in - guard let image = image else { - print(error ?? "Image capture error") - guard let error = error else { - call.reject("Image capture error") - return - } - call.reject(error.localizedDescription) - return - } + // guard let image = image else { + // print(error ?? "Image capture error") + // guard let error = error else { + // call.reject("Image capture error") + // return + // } + // call.reject(error.localizedDescription) + // return + // } - // self.videoUrl = image + // // self.videoUrl = image - call.resolve(["value": image.absoluteString]) - } - } - } + // call.resolve(["value": image.absoluteString]) + // } + // } + // } - @objc func stopRecordVideo(_ call: CAPPluginCall) { + // @objc func stopRecordVideo(_ call: CAPPluginCall) { - self.cameraController.stopRecording { (_) in + // self.cameraController.stopRecording { (_) in - } - } + // } + // } } diff --git a/src/definitions.ts b/src/definitions.ts index 51e2dbf2..6863e463 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -62,17 +62,17 @@ export interface CameraOpacityOptions { export interface CameraPreviewPlugin { start(options: CameraPreviewOptions): Promise<{}>; - startRecordVideo(options: CameraPreviewOptions): Promise<{}>; + // startRecordVideo(options: CameraPreviewOptions): Promise<{}>; stop(): Promise<{}>; - stopRecordVideo(): Promise<{}>; + // stopRecordVideo(): Promise<{}>; capture(options: CameraPreviewPictureOptions): Promise<{ value: string }>; - captureSample(options: CameraSampleOptions): Promise<{ value: string }>; + // captureSample(options: CameraSampleOptions): Promise<{ value: string }>; getSupportedFlashModes(): Promise<{ result: CameraPreviewFlashMode[]; }>; setFlashMode(options: { flashMode: CameraPreviewFlashMode | string }): Promise; flip(): Promise; - setOpacity(options: CameraOpacityOptions): Promise<{}>; + // setOpacity(options: CameraOpacityOptions): Promise<{}>; addListener( eventName: 'faceRecognized', listenerFunc: (recognition: { step: string }) => void From 9439210e54ae614447e9cd8e5b401e1ae7ae8d56 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Wed, 6 Mar 2024 17:18:17 -0300 Subject: [PATCH 07/11] Fixes --- android/build.gradle | 2 +- .../camera/preview/CameraPreview.java | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 10633a45..58d2a7c5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -44,7 +44,7 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } buildFeatures { - viewBinding true + viewBinding = true } } diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java index f120ce22..29d658c5 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java @@ -2,8 +2,6 @@ import static android.Manifest.permission.CAMERA; -import android.app.FragmentManager; -import android.app.FragmentTransaction; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.Point; @@ -16,6 +14,8 @@ import android.widget.FrameLayout; import androidx.camera.core.CameraSelector; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import com.getcapacitor.JSObject; import com.getcapacitor.Logger; @@ -43,7 +43,7 @@ public class CameraPreview extends Plugin implements KNewCameraActivity.CameraPr private String cameraStartCallbackId = ""; // keep track of previously specified orientation to support locking orientation: - private int previousOrientationRequest = -1; + private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; private KNewCameraActivity fragment; private int containerViewId = 20; @@ -124,7 +124,7 @@ public void run() { if (containerView != null) { ((ViewGroup) getBridge().getWebView().getParent()).removeView(containerView); getBridge().getWebView().setBackgroundColor(Color.WHITE); - FragmentManager fragmentManager = getActivity().getFragmentManager(); + FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.remove(fragment); fragmentTransaction.commit(); @@ -280,7 +280,7 @@ private void startCamera(final PluginCall call) { // fragment.tapToFocus = true; // fragment.disableExifHeaderStripping = disableExifHeaderStripping; // fragment.storeToFile = storeToFile; -// fragment.toBack = toBack; + fragment.setToBack(toBack); // fragment.enableOpacity = enableOpacity; // fragment.enableZoom = enableZoom; @@ -347,7 +347,7 @@ public void run() { setupBroadcast(); } - FragmentManager fragmentManager = getBridge().getActivity().getFragmentManager(); + FragmentManager fragmentManager = getBridge().getActivity().getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(containerView.getId(), fragment); fragmentTransaction.commit(); @@ -446,19 +446,19 @@ private boolean hasCamera(PluginCall call) { return fragment.hasCamera(); } - private String getFilePath(String filename) { - String fileName = filename; - - int i = 1; - - while (new File(VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION).exists()) { - // Add number suffix if file exists - fileName = filename + '_' + i; - i++; - } - - return VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION; - } +// private String getFilePath(String filename) { +// String fileName = filename; +// +// int i = 1; +// +// while (new File(VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION).exists()) { +// // Add number suffix if file exists +// fileName = filename + '_' + i; +// i++; +// } +// +// return VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION; +// } private void setupBroadcast() { /** When touch event is triggered, relay it to camera view if needed so it can support pinch zoom */ @@ -470,8 +470,8 @@ private void setupBroadcast() { new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - if ((null != fragment) && (fragment.toBack == true)) { - fragment.frameContainerLayout.dispatchTouchEvent(event); + if ((null != fragment) && (fragment.getToBack() == true)) { +// fragment.frameContainerLayout.dispatchTouchEvent(event); } return false; } From 514f65de3f7fda04e44de9757c8afbdc85b4c041 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Wed, 6 Mar 2024 17:18:28 -0300 Subject: [PATCH 08/11] Add face bounds to ios --- ios/Plugin/CameraController.swift | 23 +++++++++++++++-------- ios/Plugin/Plugin.swift | 11 +++++++++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/ios/Plugin/CameraController.swift b/ios/Plugin/CameraController.swift index 9034edd6..b920d358 100644 --- a/ios/Plugin/CameraController.swift +++ b/ios/Plugin/CameraController.swift @@ -10,7 +10,7 @@ import AVFoundation import UIKit protocol CameraControllerDelegate: NSObjectProtocol { - func hasRecognize(step: String) + func hasRecognize(step: String, bounds: CGRect) } class CameraController: NSObject { @@ -509,13 +509,20 @@ extension CameraController: AVCaptureMetadataOutputObjectsDelegate { self.faceMetadataObjects.removeFirst() } if (faceMetadataObjects.count < 14) { return } - 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: "IZQUIERDA") - } else if (yawAvg > -80 && yawAvg < -20) { - delegate?.hasRecognize(step: "DERECHA") - } else if (yawAvg > -10 && yawAvg < 10) { - delegate?.hasRecognize(step: "CENTRO") + + let transformedMetadataObject = previewLayer?.transformedMetadataObject(for: face) + + if let faceBounds = transformedMetadataObject?.bounds, + let viewBounds = previewLayer?.layerRectConverted(fromMetadataOutputRect: faceBounds) { + + 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: "IZQUIERDA", bounds: viewBounds) + } else if (yawAvg > -80 && yawAvg < -20) { + delegate?.hasRecognize(step: "DERECHA", bounds: viewBounds) + } else if (yawAvg > -10 && yawAvg < 10) { + delegate?.hasRecognize(step: "CENTRO", bounds: viewBounds) + } } } diff --git a/ios/Plugin/Plugin.swift b/ios/Plugin/Plugin.swift index 8173ac72..501a157c 100644 --- a/ios/Plugin/Plugin.swift +++ b/ios/Plugin/Plugin.swift @@ -300,7 +300,14 @@ public class CameraPreview: CAPPlugin { } extension CameraPreview: CameraControllerDelegate { - func hasRecognize(step: String) { - notifyListeners("faceRecognized", data: ["step": step]) + func hasRecognize(step: String, bounds: CGRect) { + let data: [String : Any] = [ + "step": step, + "x": bounds.origin.x, + "y": bounds.origin.y, + "width": bounds.width, + "height": bounds.height, + ] + notifyListeners("faceRecognized", data: data) } } From 6548d98bb285a2ef3b6bdea76ea1947950f5b670 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Sat, 9 Mar 2024 17:45:59 -0300 Subject: [PATCH 09/11] Improve new camera activity --- .../camera/preview/CameraPreview.java | 20 +- .../camera/preview/KNewCameraActivity.kt | 271 +++++++++--------- 2 files changed, 143 insertions(+), 148 deletions(-) diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java index 29d658c5..492a2154 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java @@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.Point; +import android.graphics.Rect; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.Display; @@ -13,6 +14,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -397,11 +399,11 @@ public void onPictureTakenError(String message) { // bridge.getSavedCall(snapshotCallbackId).reject(message); // } - @Override - public void onFocusSet(int pointX, int pointY) {} - - @Override - public void onFocusSetError(String message) {} +// @Override +// public void onFocusSet(int pointX, int pointY) {} +// +// @Override +// public void onFocusSetError(String message) {} // @Override // public void onBackButton() {} @@ -413,6 +415,14 @@ public void onCameraStarted() { bridge.releaseCall(pluginCall); } + @Override + public void onCameraDetected(@NonNull String rotation, Rect bounds) { + JSObject jsObject = new JSObject(); + jsObject.put("rotation", rotation); + jsObject.put("bounds", bounds); + notifyListeners("cameraDetected", jsObject); + } + // @Override // public void onStartRecordVideo() {} // diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt index 34af8d40..9bb15465 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/KNewCameraActivity.kt @@ -4,10 +4,7 @@ import android.Manifest import android.content.ContentValues import android.content.Context import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Matrix +import android.graphics.Rect import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.os.Build @@ -31,6 +28,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.ahm.capacitor.camera.preview.capacitorcamerapreview.databinding.NewCameraActivityBinding import com.google.mlkit.vision.common.InputImage +import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.FaceDetection import com.google.mlkit.vision.face.FaceDetector import java.text.SimpleDateFormat @@ -38,22 +36,17 @@ import java.util.Locale import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -typealias FaceAnalyzerListener = (analyzer: Double) -> Unit +typealias FaceRotationAnalyzerListener = (rotationAnalyzer: String, bounds: Rect?) -> Unit class KNewCameraActivity : Fragment() { interface CameraPreviewListener { fun onPictureTaken(originalPicture: String?) fun onPictureTakenError(message: String?) - // fun onSnapshotTaken(originalPicture: String?) - // fun onSnapshotTakenError(message: String?) - fun onFocusSet(pointX: Int, pointY: Int) - fun onFocusSetError(message: String?) - // fun onBackButton() + // fun onFocusSet(pointX: Int, pointY: Int) +// fun onFocusSetError(message: String?) +// fun onBackButton() fun onCameraStarted() - // fun onStartRecordVideo() - // fun onStartRecordVideoError(message: String?) - // fun onStopRecordVideo(file: String?) - // fun onStopRecordVideoError(error: String?) + fun onCameraDetected(rotation: String, bounds: Rect?) } private var eventListener: CameraPreviewListener? = null @@ -61,7 +54,7 @@ class KNewCameraActivity : Fragment() { private var imageCapture: ImageCapture? = null private lateinit var cameraExecutor: ExecutorService -// private var opacity = 0f + // private var opacity = 0f // The first rear facing camera // private var defaultCameraId = 0 var defaultCamera: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA @@ -100,24 +93,24 @@ class KNewCameraActivity : Fragment() { } private val activityResultLauncher = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { - permissions -> - // Handle Permission granted/rejected - var permissionGranted = true - permissions.entries.forEach { - if (it.key in REQUIRED_PERMISSIONS && !it.value) permissionGranted = false - } - if (!permissionGranted) { - Toast.makeText(requireContext(), "Permission request denied", Toast.LENGTH_SHORT).show() - } else { - startCamera() - } + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + permissions -> + // Handle Permission granted/rejected + var permissionGranted = true + permissions.entries.forEach { + if (it.key in REQUIRED_PERMISSIONS && !it.value) permissionGranted = false + } + if (!permissionGranted) { + Toast.makeText(requireContext(), "Permission request denied", Toast.LENGTH_SHORT).show() + } else { + startCamera() } + } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View { viewBinding = NewCameraActivityBinding.inflate(layoutInflater, container, false) @@ -139,9 +132,9 @@ class KNewCameraActivity : Fragment() { fun switchCamera() { defaultCamera = - if (defaultCamera == CameraSelector.DEFAULT_BACK_CAMERA) - CameraSelector.DEFAULT_FRONT_CAMERA - else CameraSelector.DEFAULT_BACK_CAMERA + if (defaultCamera == CameraSelector.DEFAULT_BACK_CAMERA) + CameraSelector.DEFAULT_FRONT_CAMERA + else CameraSelector.DEFAULT_BACK_CAMERA startCamera() } @@ -177,41 +170,41 @@ class KNewCameraActivity : Fragment() { val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) val contentValues = - ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, name) - put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CocosCapital-Image") - } + ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, name) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CocosCapital-Image") } + } // Create output options object which contains file + metadata val outputOptions = - ImageCapture.OutputFileOptions.Builder( - requireContext().contentResolver, - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - contentValues - ) - .build() + ImageCapture.OutputFileOptions.Builder( + requireContext().contentResolver, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + .build() // Set up image capture listener, which is triggered after photo has // been taken imageCapture.takePicture( - outputOptions, - ContextCompat.getMainExecutor(requireContext()), - object : ImageCapture.OnImageSavedCallback { - override fun onError(exc: ImageCaptureException) { - Log.e(TAG, "Photo capture failed: ${exc.message}", exc) - eventListener?.onPictureTakenError(exc.message) - } + outputOptions, + ContextCompat.getMainExecutor(requireContext()), + object : ImageCapture.OnImageSavedCallback { + override fun onError(exc: ImageCaptureException) { + Log.e(TAG, "Photo capture failed: ${exc.message}", exc) + eventListener?.onPictureTakenError(exc.message) + } - override fun onImageSaved(output: ImageCapture.OutputFileResults) { - val msg = "Photo capture succeeded: ${output.savedUri}" - Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() - Log.d(TAG, msg) - eventListener?.onPictureTaken(output.savedUri.toString()) - } + override fun onImageSaved(output: ImageCapture.OutputFileResults) { + val msg = "Photo capture succeeded: ${output.savedUri}" + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + eventListener?.onPictureTaken(output.savedUri.toString()) } + } ) } @@ -219,51 +212,55 @@ class KNewCameraActivity : Fragment() { val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) cameraProviderFuture.addListener( - { - // Used to bind the lifecycle of cameras to the lifecycle owner - val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + { + // Used to bind the lifecycle of cameras to the lifecycle owner + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() - // Preview - val preview = Preview.Builder().build().also { - it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) - } + // Preview + val preview = Preview.Builder().build().also { + it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) + } - imageCapture = ImageCapture.Builder() + imageCapture = ImageCapture.Builder() // .setTargetResolution(Size(1080, 1920)) // Set target resolution here - .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) // Set capture mode here - .build() - - val imageAnalyzer = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - .also { - it.setAnalyzer(cameraExecutor, FaceAnalyzer { eulerY -> - Log.d(TAG, "eulerY: $eulerY") - }) - } - - // Select back camera as a default - val cameraSelector = defaultCamera - - try { - // Unbind use cases before rebinding - cameraProvider.unbindAll() - - // Bind use cases to camera - cameraProvider.bindToLifecycle( - this, - cameraSelector, - preview, - imageCapture, - imageAnalyzer - ) - - eventListener?.onCameraStarted() - } catch (exc: Exception) { - Log.e(TAG, "Use case binding failed", exc) + .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) // Set capture mode here + .build() + + val imageAnalyzer = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { it -> + it.setAnalyzer(cameraExecutor, FaceAnalyzer { rotation, bounds -> + Log.d(TAG, "Rotation: $rotation") + bounds?.let{ rect -> + Log.d(TAG, "Bounds: ${rect.flattenToString()}") + } + eventListener?.onCameraDetected(rotation, bounds) + }) } - }, - ContextCompat.getMainExecutor(requireContext()) + + // Select back camera as a default + val cameraSelector = defaultCamera + + try { + // Unbind use cases before rebinding + cameraProvider.unbindAll() + + // Bind use cases to camera + cameraProvider.bindToLifecycle( + this, + cameraSelector, + preview, + imageCapture, + imageAnalyzer + ) + + eventListener?.onCameraStarted() + } catch (exc: Exception) { + Log.e(TAG, "Use case binding failed", exc) + } + }, + ContextCompat.getMainExecutor(requireContext()) ) } @@ -281,38 +278,51 @@ class KNewCameraActivity : Fragment() { } companion object { - private const val TAG = "CameraXApp" + private const val TAG = "CocosCapCameraPreview" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private val REQUIRED_PERMISSIONS = - mutableListOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).apply { + mutableListOf(Manifest.permission.CAMERA).apply { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { add(Manifest.permission.WRITE_EXTERNAL_STORAGE) } }.toTypedArray() } - private class FaceAnalyzer(listener: FaceAnalyzerListener? = null) : ImageAnalysis.Analyzer { + private class FaceAnalyzer(listener: FaceRotationAnalyzerListener? = null) : ImageAnalysis.Analyzer { private val faceDetector: FaceDetector = FaceDetection.getClient() - private val listeners = ArrayList().apply { listener?.let { add(it) } } + private val listeners = ArrayList().apply { listener?.let { add(it) } } + private var faceObjects: MutableList = ArrayList() @ExperimentalGetImage override fun analyze(imageProxy: ImageProxy) { - val bitmap = getBitmapFromImageProxy(imageProxy) - if (bitmap != null) { - val inputImage = InputImage.fromBitmap(bitmap, 0) - faceDetector.process(inputImage) + val mediaImage = imageProxy.image + if (mediaImage != null) { + val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) + faceDetector.process(image) .addOnSuccessListener { faces -> - if (faces.size == 1) { + if (faces.size == 0) { + faceObjects.clear() + listeners.forEach { it("NO_FACES", null) } + } else if (faces.size > 1) { + faceObjects.clear() + listeners.forEach { it("MORE_THAN_ONE_FACE", null) } + } else { val face = faces[0] - val eulerY: Float = face.headEulerAngleY // Euler Y angle represents the rotation around the vertical axis. - Log.d(TAG, "Euler Y: $eulerY") - listeners.forEach { it(eulerY.toDouble()) } - - // Check if face turns right - // if (eulerY > 30) { - // // Face turns right logic here - // Log.d(TAG, "Face turns right!") - // } + faceObjects.add(face) + if (faceObjects.size > 15) { + faceObjects.removeFirst() + // Euler Y angle represents the rotation around the vertical axis. + val eulerY: Double = (faceObjects.sumOf { it.headEulerAngleY.toDouble() }) / faceObjects.size + val bounds: Rect = face.boundingBox + + var rotation = "CENTER" + if (eulerY > 28) { + rotation = "LEFT" + } else if (eulerY < -28) { + rotation = "RIGHT" + } + listeners.forEach { it(rotation, bounds) } + } } } .addOnFailureListener { e -> @@ -323,31 +333,6 @@ class KNewCameraActivity : Fragment() { imageProxy.close() } } - - @ExperimentalGetImage - private fun getBitmapFromImageProxy(imageProxy: ImageProxy): Bitmap? { - if (imageProxy.image != null) { - val bitmap = - Bitmap.createBitmap( - imageProxy.width, - imageProxy.height, - Bitmap.Config.ARGB_8888 - ) - val matrix = Matrix() - matrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat()) - val canvas = Canvas(bitmap) - canvas.drawBitmap( - BitmapFactory.decodeByteArray( - imageProxy.planes[0].buffer.array(), - 0, - imageProxy.planes[0].buffer.remaining() - ), - matrix, - null - ) - return bitmap - } - return null - } } } + From a4f9cf8f3c7c5a3f5e191b145110d981491c3d6d Mon Sep 17 00:00:00 2001 From: bachino90 Date: Sat, 9 Mar 2024 17:54:16 -0300 Subject: [PATCH 10/11] Improvements --- android/build.gradle | 6 +++--- .../capacitor/camera/preview/CameraPreview.java | 12 +++++++++++- ios/Plugin/CameraController.swift | 16 ++++++++-------- ios/Plugin/Plugin.swift | 10 +++++----- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 58d2a7c5..78029559 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,10 +22,10 @@ apply plugin: 'kotlin-android' android { namespace "com.ahm.capacitor.camera.preview.capacitorcamerapreview" - compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 33 + compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34 defaultConfig { minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 33 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -69,7 +69,7 @@ dependencies { implementation "androidx.camera:camera-view:${camerax_version}" implementation "androidx.camera:camera-extensions:${camerax_version}" - implementation "com.google.mlkit:face-detection:16.1.5" + implementation "com.google.mlkit:face-detection:16.1.6" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java index 492a2154..38a379c6 100644 --- a/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +++ b/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java @@ -419,7 +419,17 @@ public void onCameraStarted() { public void onCameraDetected(@NonNull String rotation, Rect bounds) { JSObject jsObject = new JSObject(); jsObject.put("rotation", rotation); - jsObject.put("bounds", bounds); + 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("cameraDetected", jsObject); } diff --git a/ios/Plugin/CameraController.swift b/ios/Plugin/CameraController.swift index b920d358..3b29f93c 100644 --- a/ios/Plugin/CameraController.swift +++ b/ios/Plugin/CameraController.swift @@ -10,7 +10,7 @@ import AVFoundation import UIKit protocol CameraControllerDelegate: NSObjectProtocol { - func hasRecognize(step: String, bounds: CGRect) + func hasRecognize(step: String, bounds: CGRect?) } class CameraController: NSObject { @@ -517,21 +517,21 @@ 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: "IZQUIERDA", bounds: viewBounds) + delegate?.hasRecognize(step: "LEFT", bounds: viewBounds) } else if (yawAvg > -80 && yawAvg < -20) { - delegate?.hasRecognize(step: "DERECHA", bounds: viewBounds) + delegate?.hasRecognize(step: "RIGHT", bounds: viewBounds) } else if (yawAvg > -10 && yawAvg < 10) { - delegate?.hasRecognize(step: "CENTRO", bounds: viewBounds) + delegate?.hasRecognize(step: "CENTER", bounds: viewBounds) } } } func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { if metadataObjects.count == 0 { - return - } - - if let metadataObj = metadataObjects[0] as? AVMetadataFaceObject { + 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) } } diff --git a/ios/Plugin/Plugin.swift b/ios/Plugin/Plugin.swift index 501a157c..6941406a 100644 --- a/ios/Plugin/Plugin.swift +++ b/ios/Plugin/Plugin.swift @@ -300,13 +300,13 @@ public class CameraPreview: CAPPlugin { } extension CameraPreview: CameraControllerDelegate { - func hasRecognize(step: String, bounds: CGRect) { + func hasRecognize(step: String, bounds: CGRect?) { let data: [String : Any] = [ "step": step, - "x": bounds.origin.x, - "y": bounds.origin.y, - "width": bounds.width, - "height": bounds.height, + "x": bounds?.origin.x ?? 0, + "y": bounds?.origin.y ?? 0, + "width": bounds?.width ?? 0, + "height": bounds?.height ?? 0, ] notifyListeners("faceRecognized", data: data) } From 16db7683d3052273a2e100ed0c0cc6c05f0ca5a5 Mon Sep 17 00:00:00 2001 From: bachino90 Date: Mon, 11 Mar 2024 14:00:12 -0300 Subject: [PATCH 11/11] Update version to 5.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c90645ea..54fc92fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@capacitor-community/camera-preview", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@capacitor-community/camera-preview", - "version": "5.0.0", + "version": "5.1.0", "license": "MIT", "devDependencies": { "@capacitor/android": "^5.0.0", diff --git a/package.json b/package.json index ddb88f59..b789d772 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor-community/camera-preview", - "version": "5.0.0", + "version": "5.1.0", "description": "Camera preview", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js",