From dbe3bdac0812ada95259c657e29d93db066b1457 Mon Sep 17 00:00:00 2001 From: Hugo Gresse Date: Tue, 12 Aug 2025 15:17:51 +0200 Subject: [PATCH] feat: add focusDepth on iOS and android --- bun.lockb | Bin 866644 -> 875228 bytes docs/docs/guides/FOCUSING.mdx | 35 +++++++++++------ .../camera/core/CameraSession+FocusDepth.kt | 29 ++++++++++++++ .../camera/react/CameraView+FocusDepth.kt | 9 +++++ .../mrousavy/camera/react/CameraViewModule.kt | 13 ++++++ package/ios/Core/CameraConfiguration.swift | 2 +- package/ios/Core/CameraError.swift | 5 +++ .../ios/Core/CameraSession+FocusDepth.swift | 37 ++++++++++++++++++ package/ios/Core/CameraSession.swift | 2 +- .../AVCaptureDevice+toDictionary.swift | 1 + package/ios/Core/PreviewView.swift | 2 +- package/ios/Core/Recording/Track.swift | 2 +- .../ios/Core/Recording/TrackTimeline.swift | 8 ++-- package/ios/React/CameraView+FocusDepth.swift | 18 +++++++++ package/ios/React/CameraViewManager.m | 2 + package/ios/React/CameraViewManager.swift | 7 ++++ package/ios/React/Utils/Promise.swift | 2 +- package/src/Camera.tsx | 21 ++++++++++ package/src/CameraError.ts | 1 + 19 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 package/android/src/main/java/com/mrousavy/camera/core/CameraSession+FocusDepth.kt create mode 100644 package/android/src/main/java/com/mrousavy/camera/react/CameraView+FocusDepth.kt create mode 100644 package/ios/Core/CameraSession+FocusDepth.swift create mode 100644 package/ios/React/CameraView+FocusDepth.swift diff --git a/bun.lockb b/bun.lockb index 72547bfba57dcd82cbc747ec336f1f8e037a2682..86ec8caa81b5d8977783af7792a9d2c3daa821d2 100755 GIT binary patch delta 31735 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 Jyi~}X{|DCLfY<;4 delta 23435 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 diff --git a/docs/docs/guides/FOCUSING.mdx b/docs/docs/guides/FOCUSING.mdx index f523956344..04d761021c 100644 --- a/docs/docs/guides/FOCUSING.mdx +++ b/docs/docs/guides/FOCUSING.mdx @@ -27,7 +27,7 @@ This is an Example on how to use [react-native-gesture-handler](https://github.c ```tsx import { Camera, useCameraDevice } from 'react-native-vision-camera' import { Gesture, GestureDetector } from 'react-native-gesture-handler' -import { runOnJS } from 'react-native-reanimated'; +import { runOnJS } from 'react-native-reanimated' export function App() { const camera = useRef(null) @@ -39,25 +39,38 @@ export function App() { c.focus(point) }, []) - const gesture = Gesture.Tap() - .onEnd(({ x, y }) => { - runOnJS(focus)({ x, y }) - }) + const gesture = Gesture.Tap().onEnd(({ x, y }) => { + runOnJS(focus)({ x, y }) + }) if (device == null) return return ( - + ) } ``` +### Focus depth (focus on a fixed manual distance) + +You can programmatically set the distance of the focus, or the depth. + +```ts +await camera.current.focusDepth(0.5) +``` + +The value between Android and iOS is reversed: +| distance | Android | iOS | +|---------------|--------------------------------|-----| +| macro (close) | 10 | 0 | +| infinite | 0.2 | 1 | + +In practice, you will probably use these values to prevent any issues on **Android**: + +- macro: `device.minFocusDistance + 0.1` (0.1 prevents some weird focus changes) +- infinite: `Math.min(0.2, device.minFocusDistance)` (the lowest value for infinite focus, but setting it too low also produces focus artifacts) +
#### 🚀 Next section: [Orientation](orientation) diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+FocusDepth.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+FocusDepth.kt new file mode 100644 index 0000000000..9d8a743987 --- /dev/null +++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+FocusDepth.kt @@ -0,0 +1,29 @@ +package com.mrousavy.camera.core + +import android.annotation.SuppressLint +import android.hardware.camera2.CameraMetadata +import android.hardware.camera2.CaptureRequest +import androidx.camera.camera2.interop.Camera2CameraControl +import androidx.camera.camera2.interop.CaptureRequestOptions +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.camera.core.CameraControl + +@ExperimentalCamera2Interop +@SuppressLint("RestrictedApi") +suspend fun CameraSession.focusDepth(depth: Double) { + val camera = camera ?: throw CameraNotReadyError() + + try { + Camera2CameraControl.from(camera.cameraControl).let { + CaptureRequestOptions.Builder().apply { + val distance = depth.toFloat() + setCaptureRequestOption(CaptureRequest.LENS_FOCUS_DISTANCE, distance) + setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF) + }.let { builder -> + it.addCaptureRequestOptions(builder.build()) + } + } + } catch (e: CameraControl.OperationCanceledException) { + throw FocusCanceledError() + } +} diff --git a/package/android/src/main/java/com/mrousavy/camera/react/CameraView+FocusDepth.kt b/package/android/src/main/java/com/mrousavy/camera/react/CameraView+FocusDepth.kt new file mode 100644 index 0000000000..054de59eec --- /dev/null +++ b/package/android/src/main/java/com/mrousavy/camera/react/CameraView+FocusDepth.kt @@ -0,0 +1,9 @@ +package com.mrousavy.camera.react + +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import com.mrousavy.camera.core.focusDepth + +@ExperimentalCamera2Interop +suspend fun CameraView.focusDepth(distance: Double) { + cameraSession.focusDepth(distance) +} diff --git a/package/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt b/package/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt index 059f677a44..2851ba8a0d 100644 --- a/package/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt +++ b/package/android/src/main/java/com/mrousavy/camera/react/CameraViewModule.kt @@ -3,6 +3,7 @@ package com.mrousavy.camera.react import android.Manifest import android.content.pm.PackageManager import android.util.Log +import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.core.content.ContextCompat import com.facebook.react.bridge.Callback import com.facebook.react.bridge.Promise @@ -188,6 +189,18 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase } } + @ExperimentalCamera2Interop + @ReactMethod + fun focusDepth(viewTag: Int, distance: Double, promise: Promise) { + backgroundCoroutineScope.launch { + val view = findCameraView(viewTag) + withPromise(promise) { + view.focusDepth(distance) + return@withPromise null + } + } + } + private fun canRequestPermission(permission: String): Boolean { val activity = reactApplicationContext.currentActivity as? PermissionAwareActivity return activity?.shouldShowRequestPermissionRationale(permission) ?: false diff --git a/package/ios/Core/CameraConfiguration.swift b/package/ios/Core/CameraConfiguration.swift index a4b94dd377..58d6f6080b 100644 --- a/package/ios/Core/CameraConfiguration.swift +++ b/package/ios/Core/CameraConfiguration.swift @@ -153,7 +153,7 @@ final class CameraConfiguration { case disabled case enabled(config: T) - public static func == (lhs: OutputConfiguration, rhs: OutputConfiguration) -> Bool { + static func == (lhs: OutputConfiguration, rhs: OutputConfiguration) -> Bool { switch (lhs, rhs) { case (.disabled, .disabled): return true diff --git a/package/ios/Core/CameraError.swift b/package/ios/Core/CameraError.swift index 983568929c..f5f742c649 100644 --- a/package/ios/Core/CameraError.swift +++ b/package/ios/Core/CameraError.swift @@ -76,6 +76,7 @@ enum DeviceError { case microphoneUnavailable case lowLightBoostNotSupported case focusNotSupported + case focusDepthNotSupported case notAvailableOnSimulator case pixelFormatNotSupported(targetFormats: [FourCharCode], availableFormats: [FourCharCode]) @@ -95,6 +96,8 @@ enum DeviceError { return "low-light-boost-not-supported" case .focusNotSupported: return "focus-not-supported" + case .focusDepthNotSupported: + return "focus-depth-not-supported" case .notAvailableOnSimulator: return "camera-not-available-on-simulator" case .pixelFormatNotSupported: @@ -116,6 +119,8 @@ enum DeviceError { return "The currently selected camera device does not support low-light boost! Select a device where `device.supportsLowLightBoost` is true." case .focusNotSupported: return "The currently selected camera device does not support focusing!" + case .focusDepthNotSupported: + return "The currently selected camera device does not support manual depth-of-field focusing!" case .microphoneUnavailable: return "The microphone was unavailable." case .notAvailableOnSimulator: diff --git a/package/ios/Core/CameraSession+FocusDepth.swift b/package/ios/Core/CameraSession+FocusDepth.swift new file mode 100644 index 0000000000..9f86a0da81 --- /dev/null +++ b/package/ios/Core/CameraSession+FocusDepth.swift @@ -0,0 +1,37 @@ +// +// CameraSession+FocusDepth.swift +// VisionCamera +// +// Created by Hugo Gresse on 12.08.25. +// + +import AVFoundation +import Foundation + +extension CameraSession { + /** + Focuses the Camera to the specified distance. The distance must be within 0.001f and device.minFocusDistance + */ + func focusDepth(distance: Float) throws { + guard let device = videoDeviceInput?.device else { + throw CameraError.session(SessionError.cameraNotReady) + } + if !device.isLockingFocusWithCustomLensPositionSupported { + throw CameraError.device(DeviceError.focusDepthNotSupported) + } + + VisionLogger.log(level: .info, message: "Focusing depth (\(distance))...") + + do { + try device.lockForConfiguration() + defer { + device.unlockForConfiguration() + } + + // Set Focus depth + device.setFocusModeLocked(lensPosition: distance, completionHandler: nil) + } catch { + throw CameraError.device(DeviceError.configureError) + } + } +} diff --git a/package/ios/Core/CameraSession.swift b/package/ios/Core/CameraSession.swift index 10b0f3399c..03bffe87d3 100644 --- a/package/ios/Core/CameraSession.swift +++ b/package/ios/Core/CameraSession.swift @@ -265,7 +265,7 @@ final class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegat } } - public final func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + final func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { switch captureOutput { case is AVCaptureVideoDataOutput: onVideoFrame(sampleBuffer: sampleBuffer, orientation: connection.orientation, isMirrored: connection.isVideoMirrored) diff --git a/package/ios/Core/Extensions/AVCaptureDevice+toDictionary.swift b/package/ios/Core/Extensions/AVCaptureDevice+toDictionary.swift index 4d8bc38fad..120f0decc7 100644 --- a/package/ios/Core/Extensions/AVCaptureDevice+toDictionary.swift +++ b/package/ios/Core/Extensions/AVCaptureDevice+toDictionary.swift @@ -29,6 +29,7 @@ extension AVCaptureDevice { "supportsRawCapture": false, // TODO: supportsRawCapture "supportsLowLightBoost": isLowLightBoostSupported, "supportsFocus": isFocusPointOfInterestSupported, + "supportsFocusDepth": isLockingFocusWithCustomLensPositionSupported, "hardwareLevel": HardwareLevel.full.jsValue, "sensorOrientation": sensorOrientation.jsValue, "formats": formats.map { $0.toJSValue() }, diff --git a/package/ios/Core/PreviewView.swift b/package/ios/Core/PreviewView.swift index 9c6ff25cce..81a8d7cba0 100644 --- a/package/ios/Core/PreviewView.swift +++ b/package/ios/Core/PreviewView.swift @@ -48,7 +48,7 @@ final class PreviewView: UIView { } } - override public static var layerClass: AnyClass { + override static var layerClass: AnyClass { return AVCaptureVideoPreviewLayer.self } diff --git a/package/ios/Core/Recording/Track.swift b/package/ios/Core/Recording/Track.swift index 8dec5ce89a..25c51dc346 100644 --- a/package/ios/Core/Recording/Track.swift +++ b/package/ios/Core/Recording/Track.swift @@ -51,7 +51,7 @@ final class Track { /** Returns the last timestamp that was actually written to the track. */ - public private(set) var lastTimestamp: CMTime? + private(set) var lastTimestamp: CMTime? /** Gets the natural size of the asset writer, or zero if it is not a visual track. diff --git a/package/ios/Core/Recording/TrackTimeline.swift b/package/ios/Core/Recording/TrackTimeline.swift index 345322fdd6..5e33e6af9e 100644 --- a/package/ios/Core/Recording/TrackTimeline.swift +++ b/package/ios/Core/Recording/TrackTimeline.swift @@ -25,22 +25,22 @@ final class TrackTimeline { Represents whether the timeline has been marked as finished or not. A timeline will automatically be marked as finished when a timestamp arrives that appears after a stop(). */ - public private(set) var isFinished = false + private(set) var isFinished = false /** Gets the latency of the buffers in this timeline. This is computed by (currentTime - mostRecentBuffer.timestamp) */ - public private(set) var latency: CMTime = .zero + private(set) var latency: CMTime = .zero /** Get the first actually written timestamp of this timeline */ - public private(set) var firstTimestamp: CMTime? + private(set) var firstTimestamp: CMTime? /** Get the last actually written timestamp of this timeline. */ - public private(set) var lastTimestamp: CMTime? + private(set) var lastTimestamp: CMTime? init(ofTrackType type: TrackType, withClock clock: CMClock) { trackType = type diff --git a/package/ios/React/CameraView+FocusDepth.swift b/package/ios/React/CameraView+FocusDepth.swift new file mode 100644 index 0000000000..fef1bfab25 --- /dev/null +++ b/package/ios/React/CameraView+FocusDepth.swift @@ -0,0 +1,18 @@ +// +// CameraView+FocusDepth.swift +// VisionCamera +// +// Created by Hugo Gresse on 12.08.25. +// + +import AVFoundation +import Foundation + +extension CameraView { + func focusDepth(distance: Float, promise: Promise) { + withPromise(promise) { + try cameraSession.focusDepth(distance: distance) + return nil + } + } +} diff --git a/package/ios/React/CameraViewManager.m b/package/ios/React/CameraViewManager.m index 527c9bc0fd..819088b91c 100644 --- a/package/ios/React/CameraViewManager.m +++ b/package/ios/React/CameraViewManager.m @@ -88,5 +88,7 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManage resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(focus : (nonnull NSNumber*)node point : (NSDictionary*)point resolve : (RCTPromiseResolveBlock) resolve reject : (RCTPromiseRejectBlock)reject); +RCT_EXTERN_METHOD(focusDepth : (nonnull NSNumber*)node distance : (NSNumber*)distance resolve : (RCTPromiseResolveBlock) + resolve reject : (RCTPromiseRejectBlock)reject); @end diff --git a/package/ios/React/CameraViewManager.swift b/package/ios/React/CameraViewManager.swift index f7bd9e8d0e..5b345ba6e0 100644 --- a/package/ios/React/CameraViewManager.swift +++ b/package/ios/React/CameraViewManager.swift @@ -96,6 +96,13 @@ final class CameraViewManager: RCTViewManager { component.focus(point: CGPoint(x: x.doubleValue, y: y.doubleValue), promise: promise) } + @objc + final func focusDepth(_ node: NSNumber, distance: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + let promise = Promise(resolver: resolve, rejecter: reject) + let component = getCameraView(withTag: node) + component.focusDepth(distance: distance.floatValue, promise: promise) + } + @objc final func getCameraPermissionStatus() -> String { let status = AVCaptureDevice.authorizationStatus(for: .video) diff --git a/package/ios/React/Utils/Promise.swift b/package/ios/React/Utils/Promise.swift index d1a7a132a2..946ea5cce1 100644 --- a/package/ios/React/Utils/Promise.swift +++ b/package/ios/React/Utils/Promise.swift @@ -14,7 +14,7 @@ import Foundation * Represents a JavaScript Promise instance. `reject()` and `resolve()` should only be called once. */ class Promise { - public private(set) var didResolve = false + private(set) var didResolve = false init(resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { self.resolver = resolver diff --git a/package/src/Camera.tsx b/package/src/Camera.tsx index afe056a761..6ae377e242 100644 --- a/package/src/Camera.tsx +++ b/package/src/Camera.tsx @@ -380,6 +380,27 @@ export class Camera extends React.PureComponent { throw tryParseNativeCameraError(e) } } + + /** + * Focus the camera to a specific distance. + * @param {number} distance The distance to focus to. It should be lower than the minFocusDistance. Lower the value (closer to 0.001f), further the distance, higher the value (closer to the minFocusDistance), more macro the focus will be. But reversed on iOS: 0.0 is macro, 1.0 is infinite. + * + * Make sure the value doesn't exceed the device.minFocusDistance. + * + * @throws {@linkcode CameraRuntimeError} When any kind of error occured while focussing. + * Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error + * @example + * ```ts + * await camera.current.focusDepth(5) + * ``` + */ + public async focusDepth(distance: number): Promise { + try { + return await CameraModule.focusDepth(this.handle, distance) + } catch (e) { + throw tryParseNativeCameraError(e) + } + } //#endregion //#region Static Functions (NativeModule) diff --git a/package/src/CameraError.ts b/package/src/CameraError.ts index 01ad3f17bb..4fa825595f 100644 --- a/package/src/CameraError.ts +++ b/package/src/CameraError.ts @@ -12,6 +12,7 @@ export type DeviceError = | 'device/pixel-format-not-supported' | 'device/low-light-boost-not-supported' | 'device/focus-not-supported' + | 'device/focus-depth-not-supported' | 'device/camera-not-available-on-simulator' | 'device/camera-already-in-use' export type FormatError =