From 58ef21ebfd46fed6bf102b4bb2ac00e3527f2479 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Mon, 15 Jan 2024 19:30:20 +0100 Subject: [PATCH] feat: Add `minFocusDistance` prop to `CameraDevice` (#2392) * docs: Link `videoHdr` * Update PERFORMANCE.mdx * docs: Add isActive to perf * docs: Update errors * feat: Add `minFocusDistance` prop * Format * Update Podfile.lock * fix: To Double * fix: Import AVFoundation * fix: Move from format -> device * fix: Use centi-meters (cm) instead of meters * Fix deadloop * fix: Avoid -1 values --- docs/docs/guides/ERRORS.mdx | 4 +-- docs/docs/guides/PERFORMANCE.mdx | 8 ++++-- .../camera/core/CameraDeviceDetails.kt | 9 +++++++ package/example/ios/Podfile.lock | 2 +- .../AVCaptureDevice+minFocusDistance.swift | 25 +++++++++++++++++++ .../AVCaptureDevice+toDictionary.swift | 1 + .../VisionCamera.xcodeproj/project.pbxproj | 4 +++ package/src/CameraDevice.ts | 4 +++ 8 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 package/ios/Extensions/AVCaptureDevice+minFocusDistance.swift diff --git a/docs/docs/guides/ERRORS.mdx b/docs/docs/guides/ERRORS.mdx index aea1d5a..6f48548 100644 --- a/docs/docs/guides/ERRORS.mdx +++ b/docs/docs/guides/ERRORS.mdx @@ -55,7 +55,7 @@ See [the `CameraError.ts` file](https://github.com/mrousavy/react-native-vision- ### Runtime Errors -The `CameraRuntimeError` represents any kind of error that occured while mounting the Camera view, or an error that occured during the runtime. +The [`CameraRuntimeError`](/docs/api/classes/CameraRuntimeError) represents any kind of error that occured while mounting the Camera view, or an error that occured during the runtime. The `` UI Component provides an `onError` function that will be invoked every time an unexpected runtime error occured. @@ -71,7 +71,7 @@ function App() { ### Capture Errors -The `CameraCaptureError` represents any kind of error that occured only while taking a photo or recording a video. +The [`CameraCaptureError`](/docs/api/classes/CameraCaptureError) represents any kind of error that occured only while taking a photo or recording a video. ```tsx function App() { diff --git a/docs/docs/guides/PERFORMANCE.mdx b/docs/docs/guides/PERFORMANCE.mdx index 4d55c4e..c943f97 100644 --- a/docs/docs/guides/PERFORMANCE.mdx +++ b/docs/docs/guides/PERFORMANCE.mdx @@ -62,7 +62,7 @@ Note: By default (when not passing the options object), a simpler device is alre ### No Video HDR -Video HDR uses 10-bit formats and/or additional processing steps that come with additional computation overhead. Disable Video HDR (don't pass `videoHdr` to the ``) for higher efficiency. +Video HDR uses 10-bit formats and/or additional processing steps that come with additional computation overhead. Disable [`videoHdr`](/docs/api/interfaces/CameraProps#videohdr) for higher efficiency. ### Buffer Compression @@ -83,7 +83,11 @@ By default, the `native` [`PixelFormat`](/docs/api#pixelformat) is used, which i ### Disable unneeded pipelines -Only enable [`photo`](/docs/api/interfaces/CameraProps#photo) and [`video`](/docs/api/interfaces/CameraProps#video) if needed. +Only enable [`photo`](/docs/api/interfaces/CameraProps#photo), [`video`](/docs/api/interfaces/CameraProps#video), [`codeScanner`](/docs/api/interfaces/CameraProps#codescanner) or [`frameProcessor`](/docs/api/interfaces/CameraProps#frameprocessor) if needed. + +### Using `isActive` + +The [`isActive`](/docs/api/interfaces/CameraProps#isactive) prop controls whether the Camera should actively stream frames. Instead of fully unmounting the `` component and remounting it again, keep it mounted and just switch `isActive` on or off. This makes the Camera resume much faster as it internally keeps the session warmed up. ### Fast Photos diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt index 7738a72..82a9711 100644 --- a/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt +++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt @@ -42,6 +42,7 @@ class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String ?: floatArrayOf(35f) val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!! val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! + val minFocusDistance = getMinFocusDistanceCm() val name = ( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { characteristics.get(CameraCharacteristics.INFO_VERSION) @@ -97,6 +98,13 @@ class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String return false } + private fun getMinFocusDistanceCm(): Double { + val distance = characteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) + if (distance == null || distance == 0f) return 0.0 + // distance is in "diopters", meaning 1/meter. Convert to meters, then centi-meters + return 1.0 / distance * 100.0 + } + private fun createStabilizationModes(): ReadableArray { val array = Arguments.createArray() digitalStabilizationModes.forEach { videoStabilizationMode -> @@ -198,6 +206,7 @@ class CameraDeviceDetails(val cameraManager: CameraManager, val cameraId: String map.putString("name", name) map.putBoolean("hasFlash", hasFlash) map.putBoolean("hasTorch", hasFlash) + map.putDouble("minFocusDistance", minFocusDistance) map.putBoolean("isMultiCam", isMultiCam) map.putBoolean("supportsRawCapture", supportsRawCapture) map.putBoolean("supportsLowLightBoost", supportsLowLightBoost) diff --git a/package/example/ios/Podfile.lock b/package/example/ios/Podfile.lock index b489dc0..fb57137 100644 --- a/package/example/ios/Podfile.lock +++ b/package/example/ios/Podfile.lock @@ -729,4 +729,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb -COCOAPODS: 1.14.3 +COCOAPODS: 1.11.3 diff --git a/package/ios/Extensions/AVCaptureDevice+minFocusDistance.swift b/package/ios/Extensions/AVCaptureDevice+minFocusDistance.swift new file mode 100644 index 0000000..490af78 --- /dev/null +++ b/package/ios/Extensions/AVCaptureDevice+minFocusDistance.swift @@ -0,0 +1,25 @@ +// +// AVCaptureDevice+minFocusDistance.swift +// VisionCamera +// +// Created by Marc Rousavy on 15.01.24. +// Copyright © 2024 mrousavy. All rights reserved. +// + +import AVFoundation +import Foundation + +extension AVCaptureDevice { + /** + * The minimum distance this device can focus to, in centi-meters. + */ + var minFocusDistance: Double { + guard #available(iOS 15.0, *), minimumFocusDistance > 0 else { + // focus distance is unknown/unavailable + return 0 + } + + // convert from millimeters to centimeters + return Double(minimumFocusDistance) / 10 + } +} diff --git a/package/ios/Extensions/AVCaptureDevice+toDictionary.swift b/package/ios/Extensions/AVCaptureDevice+toDictionary.swift index 69e94d8..063e5f9 100644 --- a/package/ios/Extensions/AVCaptureDevice+toDictionary.swift +++ b/package/ios/Extensions/AVCaptureDevice+toDictionary.swift @@ -19,6 +19,7 @@ extension AVCaptureDevice { "name": localizedName, "hasFlash": hasFlash, "hasTorch": hasTorch, + "minFocusDistance": minFocusDistance, "minZoom": minAvailableVideoZoomFactor, "maxZoom": maxAvailableVideoZoomFactor, "neutralZoom": neutralZoomFactor, diff --git a/package/ios/VisionCamera.xcodeproj/project.pbxproj b/package/ios/VisionCamera.xcodeproj/project.pbxproj index 6a89879..6a73e7f 100644 --- a/package/ios/VisionCamera.xcodeproj/project.pbxproj +++ b/package/ios/VisionCamera.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ B88751A725E0102000DB86D6 /* CameraView+Zoom.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887518225E0102000DB86D6 /* CameraView+Zoom.swift */; }; B88751A825E0102000DB86D6 /* CameraError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887518325E0102000DB86D6 /* CameraError.swift */; }; B88751A925E0102000DB86D6 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887518425E0102000DB86D6 /* CameraView.swift */; }; + B88977BE2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88977BD2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift */; }; B8994E6C263F03E100069589 /* JSINSObjectConversion.mm in Sources */ = {isa = PBXBuildFile; fileRef = B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */; }; B8A1AEC42AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A1AEC32AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift */; }; B8A1AEC62AD7F08E00169C0D /* CameraView+Focus.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A1AEC52AD7F08E00169C0D /* CameraView+Focus.swift */; }; @@ -161,6 +162,7 @@ B887518325E0102000DB86D6 /* CameraError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraError.swift; sourceTree = ""; }; B887518425E0102000DB86D6 /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; B88873E5263D46C7008B1D0E /* FrameProcessorPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrameProcessorPlugin.h; sourceTree = ""; }; + B88977BD2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice+minFocusDistance.swift"; sourceTree = ""; }; B8994E6B263F03E100069589 /* JSINSObjectConversion.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = JSINSObjectConversion.mm; sourceTree = ""; }; B89A79692B3EF60F005E0357 /* UIImageOrientation+descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImageOrientation+descriptor.h"; sourceTree = ""; }; B8A1AEC32AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoDataOutput+pixelFormat.swift"; sourceTree = ""; }; @@ -284,6 +286,7 @@ B8A1AEC32AD7EDE800169C0D /* AVCaptureVideoDataOutput+pixelFormat.swift */, B8207AAC2B0E5DD70002990F /* AVCaptureSession+synchronizeBuffer.swift */, B8207AAE2B0E67460002990F /* AVCaptureVideoDataOutput+recommendedVideoSettings.swift */, + B88977BD2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift */, ); path = Extensions; sourceTree = ""; @@ -488,6 +491,7 @@ B881D35E2ABC775E009B21C8 /* AVCaptureDevice+toDictionary.swift in Sources */, B87B11BF2A8E63B700732EBF /* PixelFormat.swift in Sources */, B88751A625E0102000DB86D6 /* CameraViewManager.swift in Sources */, + B88977BE2B556DBA0095C92C /* AVCaptureDevice+minFocusDistance.swift in Sources */, B80175EC2ABDEBD000E7DE90 /* ResizeMode.swift in Sources */, B887519F25E0102000DB86D6 /* AVCaptureDevice.DeviceType+physicalDeviceDescriptor.swift in Sources */, B88685ED2AD6A5E600E93869 /* CameraSession+CodeScanner.swift in Sources */, diff --git a/package/src/CameraDevice.ts b/package/src/CameraDevice.ts index 62eeb30..9f8554d 100644 --- a/package/src/CameraDevice.ts +++ b/package/src/CameraDevice.ts @@ -164,6 +164,10 @@ export interface CameraDevice { * Specifies whether this camera supports continuously enabling the flash to act like a torch (flash with video capture) */ hasTorch: boolean + /** + * The minimum distance this device can properly focus to (in centimeters/cm) or `0` if unknown. + */ + minFocusDistance: number /** * A property indicating whether the device is a virtual multi-camera consisting of multiple combined physical cameras. *