Skip to content

Commit

Permalink
Patch 1.2.0
Browse files Browse the repository at this point in the history
feat:
- Added possibility to change HDR mode (#13)
- Added possibility to change camera exposure (#18)

perf:
- Camera Preview performance improvements
- Capturing Videos performance improvements
  • Loading branch information
FulcrumOne authored Jun 19, 2024
1 parent 15f83d5 commit fc180d4
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 23 deletions.
2 changes: 1 addition & 1 deletion MijickCameraView.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Pod::Spec.new do |s|
CameraView is a free and open-source library dedicated for SwiftUI that allows you to create fully customisable camera view in no time. Keep your code clean.
DESC

s.version = '1.1.0'
s.version = '1.2.0'
s.ios.deployment_target = '14.0'
s.swift_version = '5.10'

Expand Down
26 changes: 26 additions & 0 deletions Sources/Internal/Extensions/AVCaptureDevice++.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// AVCaptureDevice++.swift of MijickCameraView
//
// Created by Tomasz Kurylik
// - Twitter: https://twitter.com/tkurylik
// - Mail: [email protected]
// - GitHub: https://github.com/FulcrumOne
//
// Copyright ©2024 Mijick. Licensed under MIT License.


import AVKit

extension AVCaptureDevice {
var hdrMode: CameraHDRMode {
get {
if automaticallyAdjustsVideoHDREnabled { return .auto }
else if isVideoHDREnabled { return .on }
else { return .off }
}
set {
automaticallyAdjustsVideoHDREnabled = newValue == .auto
if newValue != .auto { isVideoHDREnabled = newValue == .on }
}
}
}
127 changes: 112 additions & 15 deletions Sources/Internal/Managers/CameraManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class CameraManager: NSObject, ObservableObject {
@Published private(set) var zoomFactor: CGFloat = 1.0
@Published private(set) var flashMode: CameraFlashMode = .off
@Published private(set) var torchMode: CameraTorchMode = .off
@Published private(set) var cameraExposure: CameraExposure = .init()
@Published private(set) var hdrMode: CameraHDRMode = .auto
@Published private(set) var mirrorOutput: Bool = false
@Published private(set) var isGridVisible: Bool = true
@Published private(set) var isRecording: Bool = false
Expand Down Expand Up @@ -112,6 +114,7 @@ extension CameraManager {
try setupDeviceInputs()
try setupDeviceOutput()
try setupFrameRecorder()
try setupCameraAttributes()

startCaptureSession()
announceSetupCompletion()
Expand Down Expand Up @@ -166,8 +169,7 @@ private extension CameraManager {
videoOutput = .init()
}
func initializeMotionManager() {
motionManager.accelerometerUpdateInterval = 1
motionManager.gyroUpdateInterval = 1
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates(to: OperationQueue.current ?? .init(), withHandler: handleAccelerometerUpdates)
}
func initialiseObservers() {
Expand All @@ -186,6 +188,13 @@ private extension CameraManager {

if captureSession.canAddOutput(captureVideoOutput) { captureSession?.addOutput(captureVideoOutput) }
}
func setupCameraAttributes() throws { if let device = getDevice(cameraPosition) { DispatchQueue.main.async { [self] in
cameraExposure.duration = device.exposureDuration
cameraExposure.iso = device.iso
cameraExposure.targetBias = device.exposureTargetBias
cameraExposure.mode = device.exposureMode
hdrMode = device.hdrMode
}}}
func startCaptureSession() { DispatchQueue(label: "cameraSession").async { [self] in
captureSession.startRunning()
}}
Expand Down Expand Up @@ -362,12 +371,10 @@ private extension CameraManager {
UIView.animate(withDuration: 0.5, delay: 3.5) { [self] in cameraFocusView.alpha = 0 }
}
}
func configureCameraFocus(_ focusPoint: CGPoint, _ device: AVCaptureDevice) throws {
try device.lockForConfiguration()
func configureCameraFocus(_ focusPoint: CGPoint, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
setFocusPointOfInterest(focusPoint, device)
setExposurePointOfInterest(focusPoint, device)
device.unlockForConfiguration()
}
}}
}
private extension CameraManager {
func setFocusPointOfInterest(_ focusPoint: CGPoint, _ device: AVCaptureDevice) { if device.isFocusPointOfInterestSupported {
Expand Down Expand Up @@ -397,11 +404,9 @@ private extension CameraManager {
func calculateZoomFactor(_ value: CGFloat, _ device: AVCaptureDevice) -> CGFloat {
min(max(value, getMinZoomLevel(device)), getMaxZoomLevel(device))
}
func setVideoZoomFactor(_ zoomFactor: CGFloat, _ device: AVCaptureDevice) throws {
try device.lockForConfiguration()
func setVideoZoomFactor(_ zoomFactor: CGFloat, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.videoZoomFactor = zoomFactor
device.unlockForConfiguration()
}
}}
func updateZoomFactor(_ value: CGFloat) {
zoomFactor = value
}
Expand Down Expand Up @@ -435,16 +440,100 @@ extension CameraManager {
}}
}
private extension CameraManager {
func changeTorchMode(_ device: AVCaptureDevice, _ mode: CameraTorchMode) throws {
try device.lockForConfiguration()
func changeTorchMode(_ device: AVCaptureDevice, _ mode: CameraTorchMode) throws { try withLockingDeviceForConfiguration(device) { device in
device.torchMode = mode.get()
device.unlockForConfiguration()
}
}}
func updateTorchMode(_ value: CameraTorchMode) {
torchMode = value
}
}

// MARK: - Changing Exposure Mode
extension CameraManager {
func changeExposureMode(_ newExposureMode: AVCaptureDevice.ExposureMode) throws { if let device = getDevice(cameraPosition), device.isExposureModeSupported(newExposureMode), newExposureMode != cameraExposure.mode {
try changeExposureMode(newExposureMode, device)
updateExposureMode(newExposureMode)
}}
}
private extension CameraManager {
func changeExposureMode(_ newExposureMode: AVCaptureDevice.ExposureMode, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.exposureMode = newExposureMode
}}
func updateExposureMode(_ newExposureMode: AVCaptureDevice.ExposureMode) {
cameraExposure.mode = newExposureMode
}
}

// MARK: - Changing Exposure Duration
extension CameraManager {
func changeExposureDuration(_ newExposureDuration: CMTime) throws { if let device = getDevice(cameraPosition), device.isExposureModeSupported(.custom), newExposureDuration != cameraExposure.duration {
let newExposureDuration = min(max(newExposureDuration, device.activeFormat.minExposureDuration), device.activeFormat.maxExposureDuration)

try changeExposureDuration(newExposureDuration, device)
updateExposureDuration(newExposureDuration)
}}
}
private extension CameraManager {
func changeExposureDuration(_ newExposureDuration: CMTime, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.setExposureModeCustom(duration: newExposureDuration, iso: cameraExposure.iso)
}}
func updateExposureDuration(_ newExposureDuration: CMTime) {
cameraExposure.duration = newExposureDuration
}
}

// MARK: - Changing ISO
extension CameraManager {
func changeISO(_ newISO: Float) throws { if let device = getDevice(cameraPosition), device.isExposureModeSupported(.custom), newISO != cameraExposure.iso {
let newISO = min(max(newISO, device.activeFormat.minISO), device.activeFormat.maxISO)

try changeISO(newISO, device)
updateISO(newISO)
}}
}
private extension CameraManager {
func changeISO(_ newISO: Float, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.setExposureModeCustom(duration: cameraExposure.duration, iso: newISO)
}}
func updateISO(_ newISO: Float) {
cameraExposure.iso = newISO
}
}

// MARK: - Changing Exposure Target Bias
extension CameraManager {
func changeExposureTargetBias(_ newExposureTargetBias: Float) throws { if let device = getDevice(cameraPosition), device.isExposureModeSupported(.custom), newExposureTargetBias != cameraExposure.targetBias {
let newExposureTargetBias = min(max(newExposureTargetBias, device.minExposureTargetBias), device.maxExposureTargetBias)

try changeExposureTargetBias(newExposureTargetBias, device)
updateExposureTargetBias(newExposureTargetBias)
}}
}
private extension CameraManager {
func changeExposureTargetBias(_ newExposureTargetBias: Float, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.setExposureTargetBias(newExposureTargetBias)
}}
func updateExposureTargetBias(_ newExposureTargetBias: Float) {
cameraExposure.targetBias = newExposureTargetBias
}
}

// MARK: - Changing Camera HDR Mode
extension CameraManager {
func changeHDRMode(_ newHDRMode: CameraHDRMode) throws { if let device = getDevice(cameraPosition), newHDRMode != hdrMode {
try changeHDRMode(newHDRMode, device)
updateHDRMode(newHDRMode)
}}
}
private extension CameraManager {
func changeHDRMode(_ newHDRMode: CameraHDRMode, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
device.hdrMode = newHDRMode
}}
func updateHDRMode(_ newHDRMode: CameraHDRMode) {
hdrMode = newHDRMode
}
}

// MARK: - Changing Mirror Mode
extension CameraManager {
func changeMirrorMode(_ shouldMirror: Bool) { if !isChanging {
Expand Down Expand Up @@ -583,7 +672,7 @@ extension CameraManager: AVCaptureFileOutputRecordingDelegate {
private extension CameraManager {
func handleAccelerometerUpdates(_ data: CMAccelerometerData?, _ error: Swift.Error?) { if let data, error == nil {
let newDeviceOrientation = fetchDeviceOrientation(data.acceleration)
deviceOrientation = newDeviceOrientation
updateDeviceOrientation(newDeviceOrientation)
}}
}
private extension CameraManager {
Expand All @@ -594,6 +683,9 @@ private extension CameraManager {
case let acceleration where acceleration.y >= 0.75: return .portraitUpsideDown
default: return deviceOrientation
}}
func updateDeviceOrientation(_ newDeviceOrientation: AVCaptureVideoOrientation) { if newDeviceOrientation != deviceOrientation {
deviceOrientation = newDeviceOrientation
}}
}

// MARK: - Handling Observers
Expand Down Expand Up @@ -718,6 +810,11 @@ private extension CameraManager {
connection.isVideoMirrored = mirrorOutput ? cameraPosition != .front : cameraPosition == .front
connection.videoOrientation = deviceOrientation
}}
func withLockingDeviceForConfiguration(_ device: AVCaptureDevice, _ action: (AVCaptureDevice) -> ()) throws {
try device.lockForConfiguration()
action(device)
device.unlockForConfiguration()
}
}
private extension CameraManager {
var cameraView: UIView { cameraLayer.superview ?? .init() }
Expand Down
19 changes: 19 additions & 0 deletions Sources/Internal/Models/CameraExposure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// CameraExposure.swift of MijickCameraView
//
// Created by Tomasz Kurylik
// - Twitter: https://twitter.com/tkurylik
// - Mail: [email protected]
// - GitHub: https://github.com/FulcrumOne
//
// Copyright ©2024 Mijick. Licensed under MIT License.


import AVKit

struct CameraExposure {
var duration: CMTime = .invalid
var targetBias: Float = 0
var iso: Float = 0
var mode: AVCaptureDevice.ExposureMode = .autoExpose
}
11 changes: 4 additions & 7 deletions Sources/Public/Config/Public+MCameraController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public extension MCameraController {
/// Changes the size of Focus Image that is displayed when the camera screen is tapped
func focusImageSize(_ size: CGFloat) -> Self { setAndReturnSelf { $0.cameraManager.change(focusImageSize: size) } }

/// Changes the camera filters. Applies to both camera live preview and camera output.
/// For more information, see the project documentation (https://github.com/Mijick/CameraView)
func changeCameraFilters(_ cameraFilters: [CIFilter]) -> Self { setAndReturnSelf { $0.cameraManager.change(cameraFilters: cameraFilters) } }

/// Locks the camera interface in portrait orientation (even if device screen rotation is enabled).
/// For more information, see the project documentation (https://github.com/Mijick/CameraView)
func lockOrientation(_ appDelegate: MApplicationDelegate.Type) -> Self { setAndReturnSelf { $0.config.appDelegate = appDelegate; $0.cameraManager.lockOrientation() } }
Expand All @@ -54,13 +58,6 @@ public extension MCameraController {
func errorScreen(_ builder: @escaping ErrorViewBuilder) -> Self { setAndReturnSelf { $0.config.cameraErrorView = builder } }
}

// MARK: - Changing Camera Filters
public extension MCameraController {
/// Changes the camera filters. Applies to both camera live preview and camera output.
/// For more information, see the project documentation (https://github.com/Mijick/CameraView)
func changeCameraFilters(_ cameraFilters: [CIFilter]) -> Self { setAndReturnSelf { $0.cameraManager.change(cameraFilters: cameraFilters) } }
}

// MARK: - Actions
public extension MCameraController {
/// Sets the action to be triggered when the photo is taken. Passes the captured content as an argument
Expand Down
6 changes: 6 additions & 0 deletions Sources/Public/Utilities/Public+CameraUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public enum CameraTorchMode: CaseIterable {
case on
}

// MARK: Camera HDR Mode
public enum CameraHDRMode: CaseIterable {
case off
case on
case auto
}

// MARK: - Typealiases
public typealias CameraViewBuilder = (CameraManager, Namespace.ID, _ closeControllerAction: @escaping () -> ()) -> any MCameraView
Expand Down
10 changes: 10 additions & 0 deletions Sources/Public/View Protocols/Public+MCameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public extension MCameraView {
func changeZoomFactor(_ value: CGFloat) throws { try cameraManager.changeZoomFactor(value) }
func changeFlashMode(_ mode: CameraFlashMode) throws { try cameraManager.changeFlashMode(mode) }
func changeTorchMode(_ mode: CameraTorchMode) throws { try cameraManager.changeTorchMode(mode) }
func changeExposureMode(_ exposureMode: AVCaptureDevice.ExposureMode) throws { try cameraManager.changeExposureMode(exposureMode) }
func changeExposureDuration(_ value: CMTime) throws { try cameraManager.changeExposureDuration(value) }
func changeISO(_ value: Float) throws { try cameraManager.changeISO(value) }
func changeExposureTargetBias(_ value: Float) throws { try cameraManager.changeExposureTargetBias(value) }
func changeHDRMode(_ mode: CameraHDRMode) throws { try cameraManager.changeHDRMode(mode) }
func changeMirrorOutputMode(_ shouldMirror: Bool) { cameraManager.changeMirrorMode(shouldMirror) }
func changeGridVisibility(_ shouldShowGrid: Bool) { cameraManager.changeGridVisibility(shouldShowGrid) }
}
Expand All @@ -43,6 +48,11 @@ public extension MCameraView {
var cameraPosition: CameraPosition { cameraManager.cameraPosition }
var torchMode: CameraTorchMode { cameraManager.torchMode }
var flashMode: CameraFlashMode { cameraManager.flashMode }
var exposureMode: AVCaptureDevice.ExposureMode { cameraManager.cameraExposure.mode }
var exposureDuration: CMTime { cameraManager.cameraExposure.duration }
var iso: Float { cameraManager.cameraExposure.iso }
var exposureTargetBias: Float { cameraManager.cameraExposure.targetBias }
var hdrMode: CameraHDRMode { cameraManager.hdrMode }
var mirrorOutput: Bool { cameraManager.mirrorOutput }
var showGrid: Bool { cameraManager.isGridVisible }
var deviceOrientation: AVCaptureVideoOrientation { cameraManager.deviceOrientation }
Expand Down

0 comments on commit fc180d4

Please sign in to comment.