feat: High quality mode (enableHighQualityPhotos
) (#194)
* feat: High Quality photo capture * prepare photo output for re-used settings * use high quality captures * Remove `enableVirtualDeviceFusion` as that is enabled by default * Clean up configuration, remove default * format * Update CameraViewManager.kt * rename * Update CameraProps.ts * Fix overriding `photoSettings` * Update CameraView+TakePhoto.swift * Update CameraView+TakePhoto.swift
This commit is contained in:
parent
88a30e5723
commit
0e606affce
@ -49,9 +49,6 @@ suspend fun CameraView.takePhoto(options: ReadableMap): WritableMap = coroutineS
|
|||||||
if (options.hasKey("enableDualCameraFusion")) {
|
if (options.hasKey("enableDualCameraFusion")) {
|
||||||
// TODO enableDualCameraFusion
|
// TODO enableDualCameraFusion
|
||||||
}
|
}
|
||||||
if (options.hasKey("enableVirtualDeviceFusion")) {
|
|
||||||
// TODO enableVirtualDeviceFusion
|
|
||||||
}
|
|
||||||
if (options.hasKey("enableAutoStabilization")) {
|
if (options.hasKey("enableAutoStabilization")) {
|
||||||
// TODO enableAutoStabilization
|
// TODO enableAutoStabilization
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import kotlin.math.min
|
|||||||
// TODO: Configurable FPS higher than 30
|
// TODO: Configurable FPS higher than 30
|
||||||
// TODO: High-speed video recordings (export in CameraViewModule::getAvailableVideoDevices(), and set in CameraView::configurePreview()) (120FPS+)
|
// TODO: High-speed video recordings (export in CameraViewModule::getAvailableVideoDevices(), and set in CameraView::configurePreview()) (120FPS+)
|
||||||
// TODO: configureSession() enableDepthData
|
// TODO: configureSession() enableDepthData
|
||||||
// TODO: configureSession() enableHighResolutionCapture
|
// TODO: configureSession() enableHighQualityPhotos
|
||||||
// TODO: configureSession() enablePortraitEffectsMatteDelivery
|
// TODO: configureSession() enablePortraitEffectsMatteDelivery
|
||||||
// TODO: configureSession() colorSpace
|
// TODO: configureSession() colorSpace
|
||||||
|
|
||||||
@ -55,7 +55,6 @@ import kotlin.math.min
|
|||||||
// TODO: takePhoto() photoCodec ("hevc" | "jpeg" | "raw")
|
// TODO: takePhoto() photoCodec ("hevc" | "jpeg" | "raw")
|
||||||
// TODO: takePhoto() qualityPrioritization
|
// TODO: takePhoto() qualityPrioritization
|
||||||
// TODO: takePhoto() enableAutoRedEyeReduction
|
// TODO: takePhoto() enableAutoRedEyeReduction
|
||||||
// TODO: takePhoto() enableVirtualDeviceFusion
|
|
||||||
// TODO: takePhoto() enableAutoStabilization
|
// TODO: takePhoto() enableAutoStabilization
|
||||||
// TODO: takePhoto() enableAutoDistortionCorrection
|
// TODO: takePhoto() enableAutoDistortionCorrection
|
||||||
// TODO: takePhoto() return with jsi::Value Image reference for faster capture
|
// TODO: takePhoto() return with jsi::Value Image reference for faster capture
|
||||||
@ -66,7 +65,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
|||||||
// props that require reconfiguring
|
// props that require reconfiguring
|
||||||
var cameraId: String? = null // this is actually not a react prop directly, but the result of setting device={}
|
var cameraId: String? = null // this is actually not a react prop directly, but the result of setting device={}
|
||||||
var enableDepthData = false
|
var enableDepthData = false
|
||||||
var enableHighResolutionCapture: Boolean? = null
|
var enableHighQualityPhotos: Boolean? = null
|
||||||
var enablePortraitEffectsMatteDelivery = false
|
var enablePortraitEffectsMatteDelivery = false
|
||||||
// use-cases
|
// use-cases
|
||||||
var photo: Boolean? = null
|
var photo: Boolean? = null
|
||||||
|
@ -51,11 +51,11 @@ class CameraViewManager : SimpleViewManager<CameraView>() {
|
|||||||
view.enableDepthData = enableDepthData
|
view.enableDepthData = enableDepthData
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = "enableHighResolutionCapture")
|
@ReactProp(name = "enableHighQualityPhotos")
|
||||||
fun setEnableHighResolutionCapture(view: CameraView, enableHighResolutionCapture: Boolean?) {
|
fun setEnableHighQualityPhotos(view: CameraView, enableHighQualityPhotos: Boolean?) {
|
||||||
if (view.enableHighResolutionCapture != enableHighResolutionCapture)
|
if (view.enableHighQualityPhotos != enableHighQualityPhotos)
|
||||||
addChangedPropToTransaction(view, "enableHighResolutionCapture")
|
addChangedPropToTransaction(view, "enableHighQualityPhotos")
|
||||||
view.enableHighResolutionCapture = enableHighResolutionCapture
|
view.enableHighQualityPhotos = enableHighQualityPhotos
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = "enablePortraitEffectsMatteDelivery")
|
@ReactProp(name = "enablePortraitEffectsMatteDelivery")
|
||||||
|
@ -94,12 +94,21 @@ extension CameraView {
|
|||||||
if photo?.boolValue == true {
|
if photo?.boolValue == true {
|
||||||
ReactLogger.log(level: .info, message: "Adding Photo output...")
|
ReactLogger.log(level: .info, message: "Adding Photo output...")
|
||||||
photoOutput = AVCapturePhotoOutput()
|
photoOutput = AVCapturePhotoOutput()
|
||||||
photoOutput!.isDepthDataDeliveryEnabled = photoOutput!.isDepthDataDeliverySupported && enableDepthData
|
|
||||||
if let enableHighResolutionCapture = self.enableHighResolutionCapture?.boolValue {
|
if enableHighQualityPhotos?.boolValue == true {
|
||||||
photoOutput!.isHighResolutionCaptureEnabled = enableHighResolutionCapture
|
photoOutput!.isHighResolutionCaptureEnabled = true
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
photoOutput!.isVirtualDeviceConstituentPhotoDeliveryEnabled = photoOutput!.isVirtualDeviceConstituentPhotoDeliverySupported
|
||||||
|
photoOutput!.maxPhotoQualityPrioritization = .quality
|
||||||
|
} else {
|
||||||
|
photoOutput!.isDualCameraDualPhotoDeliveryEnabled = photoOutput!.isDualCameraDualPhotoDeliverySupported
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if #available(iOS 12.0, *) {
|
if enableDepthData {
|
||||||
photoOutput!.isPortraitEffectsMatteDeliveryEnabled = photoOutput!.isPortraitEffectsMatteDeliverySupported && self.enablePortraitEffectsMatteDelivery
|
photoOutput!.isDepthDataDeliveryEnabled = photoOutput!.isDepthDataDeliverySupported
|
||||||
|
}
|
||||||
|
if #available(iOS 12.0, *), enablePortraitEffectsMatteDelivery {
|
||||||
|
photoOutput!.isPortraitEffectsMatteDeliveryEnabled = photoOutput!.isPortraitEffectsMatteDeliverySupported
|
||||||
}
|
}
|
||||||
guard captureSession.canAddOutput(photoOutput!) else {
|
guard captureSession.canAddOutput(photoOutput!) else {
|
||||||
invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "photo-output")))
|
invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "photo-output")))
|
||||||
|
@ -36,19 +36,35 @@ extension CameraView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var photoSettings = AVCapturePhotoSettings()
|
ReactLogger.log(level: .info, message: "Capturing photo...")
|
||||||
|
|
||||||
|
var format: [String: Any]?
|
||||||
|
// photo codec
|
||||||
if let photoCodecString = options["photoCodec"] as? String {
|
if let photoCodecString = options["photoCodec"] as? String {
|
||||||
guard let photoCodec = AVVideoCodecType(withString: photoCodecString) else {
|
guard let photoCodec = AVVideoCodecType(withString: photoCodecString) else {
|
||||||
promise.reject(error: .capture(.invalidPhotoCodec))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if photoOutput.availablePhotoCodecTypes.contains(photoCodec) {
|
|
||||||
photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: photoCodec])
|
|
||||||
} else {
|
|
||||||
promise.reject(error: .parameter(.invalid(unionName: "PhotoCodec", receivedValue: photoCodecString)))
|
promise.reject(error: .parameter(.invalid(unionName: "PhotoCodec", receivedValue: photoCodecString)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if photoOutput.availablePhotoCodecTypes.contains(photoCodec) {
|
||||||
|
format = [AVVideoCodecKey: photoCodec]
|
||||||
|
} else {
|
||||||
|
promise.reject(error: .capture(.invalidPhotoCodec))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create photo settings
|
||||||
|
let photoSettings = AVCapturePhotoSettings(format: format)
|
||||||
|
|
||||||
|
// default, overridable settings if high quality capture was enabled
|
||||||
|
if self.enableHighQualityPhotos?.boolValue == true {
|
||||||
|
photoSettings.isHighResolutionPhotoEnabled = true
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
photoSettings.photoQualityPrioritization = .quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flash
|
||||||
if videoDeviceInput.device.isFlashAvailable, let flash = options["flash"] as? String {
|
if videoDeviceInput.device.isFlashAvailable, let flash = options["flash"] as? String {
|
||||||
guard let flashMode = AVCaptureDevice.FlashMode(withString: flash) else {
|
guard let flashMode = AVCaptureDevice.FlashMode(withString: flash) else {
|
||||||
promise.reject(error: .parameter(.invalid(unionName: "FlashMode", receivedValue: flash)))
|
promise.reject(error: .parameter(.invalid(unionName: "FlashMode", receivedValue: flash)))
|
||||||
@ -56,16 +72,14 @@ extension CameraView {
|
|||||||
}
|
}
|
||||||
photoSettings.flashMode = flashMode
|
photoSettings.flashMode = flashMode
|
||||||
}
|
}
|
||||||
photoSettings.isHighResolutionPhotoEnabled = photoOutput.isHighResolutionCaptureEnabled
|
|
||||||
if !photoSettings.__availablePreviewPhotoPixelFormatTypes.isEmpty {
|
// depth data
|
||||||
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.__availablePreviewPhotoPixelFormatTypes.first!]
|
|
||||||
}
|
|
||||||
photoSettings.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliveryEnabled
|
photoSettings.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliveryEnabled
|
||||||
photoSettings.embedsDepthDataInPhoto = photoSettings.isDepthDataDeliveryEnabled
|
|
||||||
if #available(iOS 12.0, *) {
|
if #available(iOS 12.0, *) {
|
||||||
photoSettings.isPortraitEffectsMatteDeliveryEnabled = photoOutput.isPortraitEffectsMatteDeliveryEnabled
|
photoSettings.isPortraitEffectsMatteDeliveryEnabled = photoOutput.isPortraitEffectsMatteDeliveryEnabled
|
||||||
photoSettings.embedsPortraitEffectsMatteInPhoto = photoSettings.isPortraitEffectsMatteDeliveryEnabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quality prioritization
|
||||||
if #available(iOS 13.0, *), let qualityPrioritization = options["qualityPrioritization"] as? String {
|
if #available(iOS 13.0, *), let qualityPrioritization = options["qualityPrioritization"] as? String {
|
||||||
guard let photoQualityPrioritization = AVCapturePhotoOutput.QualityPrioritization(withString: qualityPrioritization) else {
|
guard let photoQualityPrioritization = AVCapturePhotoOutput.QualityPrioritization(withString: qualityPrioritization) else {
|
||||||
promise.reject(error: .parameter(.invalid(unionName: "QualityPrioritization", receivedValue: qualityPrioritization)))
|
promise.reject(error: .parameter(.invalid(unionName: "QualityPrioritization", receivedValue: qualityPrioritization)))
|
||||||
@ -73,24 +87,26 @@ extension CameraView {
|
|||||||
}
|
}
|
||||||
photoSettings.photoQualityPrioritization = photoQualityPrioritization
|
photoSettings.photoQualityPrioritization = photoQualityPrioritization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// red-eye reduction
|
||||||
if #available(iOS 12.0, *), let autoRedEyeReduction = options["enableAutoRedEyeReduction"] as? Bool {
|
if #available(iOS 12.0, *), let autoRedEyeReduction = options["enableAutoRedEyeReduction"] as? Bool {
|
||||||
photoSettings.isAutoRedEyeReductionEnabled = autoRedEyeReduction
|
photoSettings.isAutoRedEyeReductionEnabled = autoRedEyeReduction
|
||||||
}
|
}
|
||||||
if let enableVirtualDeviceFusion = options["enableVirtualDeviceFusion"] as? Bool {
|
|
||||||
if #available(iOS 13.0, *) {
|
// stabilization
|
||||||
photoSettings.isAutoVirtualDeviceFusionEnabled = enableVirtualDeviceFusion
|
|
||||||
} else {
|
|
||||||
photoSettings.isAutoDualCameraFusionEnabled = enableVirtualDeviceFusion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let enableAutoStabilization = options["enableAutoStabilization"] as? Bool {
|
if let enableAutoStabilization = options["enableAutoStabilization"] as? Bool {
|
||||||
photoSettings.isAutoStillImageStabilizationEnabled = enableAutoStabilization
|
photoSettings.isAutoStillImageStabilizationEnabled = enableAutoStabilization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// distortion correction
|
||||||
if #available(iOS 14.1, *), let enableAutoDistortionCorrection = options["enableAutoDistortionCorrection"] as? Bool {
|
if #available(iOS 14.1, *), let enableAutoDistortionCorrection = options["enableAutoDistortionCorrection"] as? Bool {
|
||||||
photoSettings.isAutoContentAwareDistortionCorrectionEnabled = enableAutoDistortionCorrection
|
photoSettings.isAutoContentAwareDistortionCorrectionEnabled = enableAutoDistortionCorrection
|
||||||
}
|
}
|
||||||
|
|
||||||
photoOutput.capturePhoto(with: photoSettings, delegate: PhotoCaptureDelegate(promise: promise))
|
photoOutput.capturePhoto(with: photoSettings, delegate: PhotoCaptureDelegate(promise: promise))
|
||||||
|
|
||||||
|
// Assume that `takePhoto` is always called with the same parameters, so prepare the next call too.
|
||||||
|
photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import UIKit
|
|||||||
|
|
||||||
private let propsThatRequireReconfiguration = ["cameraId",
|
private let propsThatRequireReconfiguration = ["cameraId",
|
||||||
"enableDepthData",
|
"enableDepthData",
|
||||||
"enableHighResolutionCapture",
|
"enableHighQualityPhotos",
|
||||||
"enablePortraitEffectsMatteDelivery",
|
"enablePortraitEffectsMatteDelivery",
|
||||||
"preset",
|
"preset",
|
||||||
"photo",
|
"photo",
|
||||||
@ -40,7 +40,7 @@ public final class CameraView: UIView {
|
|||||||
// props that require reconfiguring
|
// props that require reconfiguring
|
||||||
@objc var cameraId: NSString?
|
@objc var cameraId: NSString?
|
||||||
@objc var enableDepthData = false
|
@objc var enableDepthData = false
|
||||||
@objc var enableHighResolutionCapture: NSNumber? // nullable bool
|
@objc var enableHighQualityPhotos: NSNumber? // nullable bool
|
||||||
@objc var enablePortraitEffectsMatteDelivery = false
|
@objc var enablePortraitEffectsMatteDelivery = false
|
||||||
@objc var preset: String?
|
@objc var preset: String?
|
||||||
// use cases
|
// use cases
|
||||||
|
@ -25,7 +25,7 @@ RCT_EXTERN_METHOD(getAvailableCameraDevices:(RCTPromiseResolveBlock)resolve reje
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(isActive, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(isActive, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(cameraId, NSString);
|
RCT_EXPORT_VIEW_PROPERTY(cameraId, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(enableDepthData, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(enableDepthData, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(enableHighResolutionCapture, NSNumber); // nullable bool
|
RCT_EXPORT_VIEW_PROPERTY(enableHighQualityPhotos, NSNumber); // nullable bool
|
||||||
RCT_EXPORT_VIEW_PROPERTY(enablePortraitEffectsMatteDelivery, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(enablePortraitEffectsMatteDelivery, BOOL);
|
||||||
// use cases
|
// use cases
|
||||||
RCT_EXPORT_VIEW_PROPERTY(photo, NSNumber); // nullable bool
|
RCT_EXPORT_VIEW_PROPERTY(photo, NSNumber); // nullable bool
|
||||||
|
@ -135,11 +135,17 @@ export interface CameraProps extends ViewProps {
|
|||||||
*/
|
*/
|
||||||
enablePortraitEffectsMatteDelivery?: boolean;
|
enablePortraitEffectsMatteDelivery?: boolean;
|
||||||
/**
|
/**
|
||||||
* Indicates whether the photo render pipeline should be configured to deliver high resolution still images
|
* Indicates whether the Camera should prepare the photo pipeline to provide maximum quality photos.
|
||||||
|
*
|
||||||
|
* This enables:
|
||||||
|
* * High Resolution Capture ([`isHighResolutionCaptureEnabled`](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/1648721-ishighresolutioncaptureenabled))
|
||||||
|
* * Virtual Device fusion for greater detail ([`isVirtualDeviceConstituentPhotoDeliveryEnabled`](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/3192189-isvirtualdeviceconstituentphotod))
|
||||||
|
* * Dual Device fusion for greater detail ([`isDualCameraDualPhotoDeliveryEnabled`](https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/2873917-isdualcameradualphotodeliveryena))
|
||||||
|
* * Sets the maximum quality prioritization to `.quality` ([`maxPhotoQualityPrioritization`](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/3182995-maxphotoqualityprioritization))
|
||||||
*
|
*
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
enableHighResolutionCapture?: boolean;
|
enableHighQualityPhotos?: boolean;
|
||||||
|
|
||||||
//#region Events
|
//#region Events
|
||||||
/**
|
/**
|
||||||
|
@ -24,13 +24,6 @@ export interface TakePhotoOptions {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
enableAutoRedEyeReduction?: boolean;
|
enableAutoRedEyeReduction?: boolean;
|
||||||
/**
|
|
||||||
* Specifies whether a virtual multi-cam device should capture images from all containing physical cameras
|
|
||||||
* to create a combined, higher quality image.
|
|
||||||
*
|
|
||||||
* @see [`isAutoVirtualDeviceFusionEnabled`](https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/3192192-isautovirtualdevicefusionenabled)
|
|
||||||
*/
|
|
||||||
enableVirtualDeviceFusion?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether still image stabilization will be employed when capturing the photo
|
* Indicates whether still image stabilization will be employed when capturing the photo
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user