Я работаю над индивидуальной реализацией камеры для iOS, используя AvcaptureSession и AvcapturedEvice. Я знаю, как справиться с масштабированием и масштабировать, и как переключаться между камерами (широко, ультра широко и т. Д.). Тем не менее, я хочу достичь плавного анимированного эффекта перехода между широкими и ультра широкими объективами, как и нативное приложение для камеры iPhone, когда вы переключаете линзы. Я хочу воспроизвести бесшовную анимацию Zoom и поведение переключения линз, которое можно увидеть в нативном приложении.//
// CameraManager.swift
// Capture Clip
//
// Created by Lucas Sesti on 20/12/24.
//
import UIKit
import SwiftUI
import AVKit
import Observation
/// Camera permissions
enum CameraPermission: String {
case granted = "Permission granted"
case idle = "Not decided"
case denied = "Permission denied"
}
enum CameraError: Error {
case unableToCapturePhoto(error: String)
case permissionDenied
}
@MainActor
@Observable
class Camera: NSObject, AVCaptureSessionControlsDelegate, @preconcurrency AVCapturePhotoCaptureDelegate {
/// Camera properties
private let queue: DispatchSerialQueue = .init(label: "br.com.lejour-capture.Capture.sessionQueue")
/// Camera output
private var photoContinuation: CheckedContinuation?
/// Camera presets
let presets: [AVCaptureSession.Preset] = [
.hd4K3840x2160,
.hd1920x1080,
.hd1280x720,
.vga640x480,
.cif352x288
]
let session: AVCaptureSession = .init()
var cameraPosition: AVCaptureDevice.Position = .back
let cameraOutput: AVCapturePhotoOutput = .init()
var videoGravity: AVLayerVideoGravity = .resizeAspectFill
var permission: CameraPermission = .idle
var zoomFactor: CGFloat = 1.0 {
didSet {
self.setZoom(to: zoomFactor)
}
}
var zoomLevel: Zoom = .oneX {
didSet {
self.handleZoomAction(progress: zoomLevel.rawValue)
}
}
override init() {
super.init()
checkCameraPermission()
}
/// Checking and asking for camera permission
private func checkCameraPermission() {
Task {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
permission = .granted
setupCamera()
case .notDetermined:
if await AVCaptureDevice.requestAccess(for: .video) {
permission = .granted
setupCamera()
}
case .denied, .restricted:
permission = .denied
@unknown default: break
}
}
}
/// Setting up camera
private func setupCamera() {
guard let device = AVCaptureDevice.DiscoverySession(
deviceTypes: [
// /// With 2 lens
// .builtInDualWideCamera,
// /// With 3 lens
// .builtInTripleCamera,
/// Fallback for all iPhone Models
.builtInWideAngleCamera,
],
mediaType: .video,
position: cameraPosition
).devices.first else {
session.commitConfiguration()
print("Couldn't find any background camera")
return
}
self.setCameraDevice(to: device)
startSession()
}
/// Set specific camera
func setCameraDevice(to device: AVCaptureDevice) {
guard permission == .granted else {
print("Permissão para uso da câmera não concedida.")
return
}
do {
try device.lockForConfiguration()
session.beginConfiguration()
session.inputs.forEach { input in
session.removeInput(input)
}
session.outputs.forEach { output in
session.removeOutput(output)
}
let input = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(input), session.canAddOutput(cameraOutput) else {
session.commitConfiguration()
print("Cannot add camera output")
return
}
session.addInput(input)
session.addOutput(cameraOutput)
setupCameraControl(device)
for preset in presets {
if session.canSetSessionPreset(preset) {
session.sessionPreset = preset
print("Preset configurado para: \(preset)")
break
}
}
session.commitConfiguration()
device.unlockForConfiguration()
} catch {
print(error.localizedDescription)
}
}
func toggleCamera() {
cameraPosition = (cameraPosition == .back) ? .front : .back
guard let device = AVCaptureDevice.DiscoverySession(
deviceTypes: [
.builtInWideAngleCamera,
],
mediaType: .video,
position: cameraPosition
).devices.first else {
print("Couldn't find the \(cameraPosition == .back ? "back" : "front") camera")
return
}
setCameraDevice(to: device)
withAnimation {
self.zoomLevel = .oneX
}
print("Switched to \(cameraPosition == .back ? "back" : "front") camera")
}
/// Camera session
func startSession() {
guard !session.isRunning else { return }
/// Starting in background thread, not in the main thread
Task.detached(priority: .background) {
await self.session.startRunning()
}
}
func stopSession() {
guard session.isRunning else { return }
/// Stopping in background thread, not in the main thread
Task.detached(priority: .background) {
await self.session.stopRunning()
}
}
/// Setting up camera controls actions for iPhone 16+ models
private func setupCameraControl(_ device: AVCaptureDevice) {
if #available(iOS 18.0, *) {
guard session.supportsControls else { return }
session.setControlsDelegate(self, queue: queue)
for control in session.controls {
session.removeControl(control)
}
let zoomControl = AVCaptureSlider("Zoom", symbolName: "", in: 0.5...5, step: 0.5)
zoomControl.value = 1.0
zoomControl.setActionQueue(queue) { progress in
self.handleZoomAction(progress: CGFloat(progress))
if let closestZoom = Zoom.allCases.min(by: { abs($0.rawValue - CGFloat(progress)) < abs($1.rawValue - CGFloat(progress)) }) {
withAnimation {
self.zoomLevel = closestZoom
}
}
}
if session.canAddControl(zoomControl) {
session.addControl(zoomControl)
} else {
print("Couldn't add zoom control")
}
} else {
print("Not available")
}
}
/// Camera control protocols
nonisolated func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
}
/// Camera photo output
func capturePhoto() async throws -> Image {
guard permission == .granted else {
print("Permissão para uso da câmera não concedida.")
throw CameraError.permissionDenied
}
let photoSettings = AVCapturePhotoSettings()
photoSettings.flashMode = .off
photoSettings.photoQualityPrioritization = .balanced
return try await withCheckedThrowingContinuation { continuation in
self.photoContinuation = continuation
cameraOutput.capturePhoto(with: photoSettings, delegate: self)
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let error = error {
photoContinuation?.resume(throwing: error)
return
}
guard let imageData = photo.fileDataRepresentation(),
let uiImage = UIImage(data: imageData) else {
photoContinuation?.resume(throwing: CameraError.unableToCapturePhoto(error: "Não foi possível processar a imagem capturada."))
return
}
var finalUIImage = uiImage
/// Mirroring the image if is in front camera
if cameraPosition == .front {
finalUIImage = mirrorImage(uiImage)
}
let swiftUIImage = Image(uiImage: finalUIImage)
photoContinuation?.resume(returning: swiftUIImage)
}
/// Mirror an image horizontally
private func mirrorImage(_ image: UIImage) -> UIImage {
guard let cgImage = image.cgImage else { return image }
let mirroredOrientation: UIImage.Orientation
switch image.imageOrientation {
case .up:
mirroredOrientation = .upMirrored
case .down:
mirroredOrientation = .downMirrored
case .left:
mirroredOrientation = .rightMirrored
case .right:
mirroredOrientation = .leftMirrored
default:
mirroredOrientation = .upMirrored
}
return UIImage(cgImage: cgImage, scale: image.scale, orientation: mirroredOrientation)
}
/// Camera zoom control
func setZoom(to zoomFactor: CGFloat) {
guard let activeDevice = (session.inputs.first as? AVCaptureDeviceInput)?.device else {
print("No active video input device found.")
return
}
let clampedZoomFactor = max(
activeDevice.minAvailableVideoZoomFactor,
min(
zoomFactor,
activeDevice.maxAvailableVideoZoomFactor
)
)
do {
try activeDevice.lockForConfiguration()
activeDevice.ramp(toVideoZoomFactor: clampedZoomFactor, withRate: 3.3)
activeDevice.unlockForConfiguration()
} catch {
print("Failed to set zoom: \(error.localizedDescription)")
}
}
func setZoomLevel(_ zoom: Zoom?) {
if zoom != nil {
self.zoomLevel = zoom!
} else {
self.zoomLevel = self.zoomLevel.next()
}
}
func handleZoomAction(progress: CGFloat) {
guard let activeDevice = (self.session.inputs.first as? AVCaptureDeviceInput)?.device else {
print("No active video input device found.")
return
}
if progress < 1.0 {
if activeDevice.deviceType == .builtInUltraWideCamera {
return
}
let ultraWideDevices = AVCaptureDevice.DiscoverySession(
deviceTypes: [
/// For iPhone 11+ models,
.builtInUltraWideCamera
],
mediaType: .video,
position: self.cameraPosition
)
guard let ultraWideDevice = ultraWideDevices.devices.first else {
print("Couldn't find any ultra wide camera")
return
}
self.setCameraDevice(to: ultraWideDevice)
return
} else {
if activeDevice.deviceType != .builtInWideAngleCamera {
let wideCamera = AVCaptureDevice.DiscoverySession(
deviceTypes: [
/// For all iPhone models
.builtInWideAngleCamera
],
mediaType: .video,
position: self.cameraPosition
)
guard let device = wideCamera.devices.first else {
print("Couldn't find any wide camera")
return
}
self.setCameraDevice(to: device)
}
}
self.zoomFactor = CGFloat(progress)
}
}
< /code>
Я попытался переключить входы avcapturedevice, когда фактор масштабирования пересекает определенные пороговые значения (например, менее 1x использует Ultra Wide, 1x и выше используется широко), но переход не анимирован плавно. /> Существует ли рекомендуемый подход или Appi Appi для использования, который обрабатывает этот вид бесшовного многокамерного ZooM? код!
Подробнее здесь: https://stackoverflow.com/questions/796 ... enses-like
Как оживить плавный переход камеры между широкими и ультра широкими объективами, такими как приложение для нативного кам ⇐ IOS
Программируем под IOS
1749029843
Anonymous
Я работаю над индивидуальной реализацией камеры для iOS, используя AvcaptureSession и AvcapturedEvice. Я знаю, как справиться с масштабированием и масштабировать, и как переключаться между камерами (широко, ультра широко и т. Д.). Тем не менее, я хочу достичь плавного анимированного эффекта перехода между широкими и ультра широкими объективами, как и нативное приложение для камеры iPhone, когда вы переключаете линзы. Я хочу воспроизвести бесшовную анимацию Zoom и поведение переключения линз, которое можно увидеть в нативном приложении.//
// CameraManager.swift
// Capture Clip
//
// Created by Lucas Sesti on 20/12/24.
//
import UIKit
import SwiftUI
import AVKit
import Observation
/// Camera permissions
enum CameraPermission: String {
case granted = "Permission granted"
case idle = "Not decided"
case denied = "Permission denied"
}
enum CameraError: Error {
case unableToCapturePhoto(error: String)
case permissionDenied
}
@MainActor
@Observable
class Camera: NSObject, AVCaptureSessionControlsDelegate, @preconcurrency AVCapturePhotoCaptureDelegate {
/// Camera properties
private let queue: DispatchSerialQueue = .init(label: "br.com.lejour-capture.Capture.sessionQueue")
/// Camera output
private var photoContinuation: CheckedContinuation?
/// Camera presets
let presets: [AVCaptureSession.Preset] = [
.hd4K3840x2160,
.hd1920x1080,
.hd1280x720,
.vga640x480,
.cif352x288
]
let session: AVCaptureSession = .init()
var cameraPosition: AVCaptureDevice.Position = .back
let cameraOutput: AVCapturePhotoOutput = .init()
var videoGravity: AVLayerVideoGravity = .resizeAspectFill
var permission: CameraPermission = .idle
var zoomFactor: CGFloat = 1.0 {
didSet {
self.setZoom(to: zoomFactor)
}
}
var zoomLevel: Zoom = .oneX {
didSet {
self.handleZoomAction(progress: zoomLevel.rawValue)
}
}
override init() {
super.init()
checkCameraPermission()
}
/// Checking and asking for camera permission
private func checkCameraPermission() {
Task {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
permission = .granted
setupCamera()
case .notDetermined:
if await AVCaptureDevice.requestAccess(for: .video) {
permission = .granted
setupCamera()
}
case .denied, .restricted:
permission = .denied
@unknown default: break
}
}
}
/// Setting up camera
private func setupCamera() {
guard let device = AVCaptureDevice.DiscoverySession(
deviceTypes: [
// /// With 2 lens
// .builtInDualWideCamera,
// /// With 3 lens
// .builtInTripleCamera,
/// Fallback for all iPhone Models
.builtInWideAngleCamera,
],
mediaType: .video,
position: cameraPosition
).devices.first else {
session.commitConfiguration()
print("Couldn't find any background camera")
return
}
self.setCameraDevice(to: device)
startSession()
}
/// Set specific camera
func setCameraDevice(to device: AVCaptureDevice) {
guard permission == .granted else {
print("Permissão para uso da câmera não concedida.")
return
}
do {
try device.lockForConfiguration()
session.beginConfiguration()
session.inputs.forEach { input in
session.removeInput(input)
}
session.outputs.forEach { output in
session.removeOutput(output)
}
let input = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(input), session.canAddOutput(cameraOutput) else {
session.commitConfiguration()
print("Cannot add camera output")
return
}
session.addInput(input)
session.addOutput(cameraOutput)
setupCameraControl(device)
for preset in presets {
if session.canSetSessionPreset(preset) {
session.sessionPreset = preset
print("Preset configurado para: \(preset)")
break
}
}
session.commitConfiguration()
device.unlockForConfiguration()
} catch {
print(error.localizedDescription)
}
}
func toggleCamera() {
cameraPosition = (cameraPosition == .back) ? .front : .back
guard let device = AVCaptureDevice.DiscoverySession(
deviceTypes: [
.builtInWideAngleCamera,
],
mediaType: .video,
position: cameraPosition
).devices.first else {
print("Couldn't find the \(cameraPosition == .back ? "back" : "front") camera")
return
}
setCameraDevice(to: device)
withAnimation {
self.zoomLevel = .oneX
}
print("Switched to \(cameraPosition == .back ? "back" : "front") camera")
}
/// Camera session
func startSession() {
guard !session.isRunning else { return }
/// Starting in background thread, not in the main thread
Task.detached(priority: .background) {
await self.session.startRunning()
}
}
func stopSession() {
guard session.isRunning else { return }
/// Stopping in background thread, not in the main thread
Task.detached(priority: .background) {
await self.session.stopRunning()
}
}
/// Setting up camera controls actions for iPhone 16+ models
private func setupCameraControl(_ device: AVCaptureDevice) {
if #available(iOS 18.0, *) {
guard session.supportsControls else { return }
session.setControlsDelegate(self, queue: queue)
for control in session.controls {
session.removeControl(control)
}
let zoomControl = AVCaptureSlider("Zoom", symbolName: "", in: 0.5...5, step: 0.5)
zoomControl.value = 1.0
zoomControl.setActionQueue(queue) { progress in
self.handleZoomAction(progress: CGFloat(progress))
if let closestZoom = Zoom.allCases.min(by: { abs($0.rawValue - CGFloat(progress)) < abs($1.rawValue - CGFloat(progress)) }) {
withAnimation {
self.zoomLevel = closestZoom
}
}
}
if session.canAddControl(zoomControl) {
session.addControl(zoomControl)
} else {
print("Couldn't add zoom control")
}
} else {
print("Not available")
}
}
/// Camera control protocols
nonisolated func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) {
}
nonisolated func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
}
/// Camera photo output
func capturePhoto() async throws -> Image {
guard permission == .granted else {
print("Permissão para uso da câmera não concedida.")
throw CameraError.permissionDenied
}
let photoSettings = AVCapturePhotoSettings()
photoSettings.flashMode = .off
photoSettings.photoQualityPrioritization = .balanced
return try await withCheckedThrowingContinuation { continuation in
self.photoContinuation = continuation
cameraOutput.capturePhoto(with: photoSettings, delegate: self)
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let error = error {
photoContinuation?.resume(throwing: error)
return
}
guard let imageData = photo.fileDataRepresentation(),
let uiImage = UIImage(data: imageData) else {
photoContinuation?.resume(throwing: CameraError.unableToCapturePhoto(error: "Não foi possível processar a imagem capturada."))
return
}
var finalUIImage = uiImage
/// Mirroring the image if is in front camera
if cameraPosition == .front {
finalUIImage = mirrorImage(uiImage)
}
let swiftUIImage = Image(uiImage: finalUIImage)
photoContinuation?.resume(returning: swiftUIImage)
}
/// Mirror an image horizontally
private func mirrorImage(_ image: UIImage) -> UIImage {
guard let cgImage = image.cgImage else { return image }
let mirroredOrientation: UIImage.Orientation
switch image.imageOrientation {
case .up:
mirroredOrientation = .upMirrored
case .down:
mirroredOrientation = .downMirrored
case .left:
mirroredOrientation = .rightMirrored
case .right:
mirroredOrientation = .leftMirrored
default:
mirroredOrientation = .upMirrored
}
return UIImage(cgImage: cgImage, scale: image.scale, orientation: mirroredOrientation)
}
/// Camera zoom control
func setZoom(to zoomFactor: CGFloat) {
guard let activeDevice = (session.inputs.first as? AVCaptureDeviceInput)?.device else {
print("No active video input device found.")
return
}
let clampedZoomFactor = max(
activeDevice.minAvailableVideoZoomFactor,
min(
zoomFactor,
activeDevice.maxAvailableVideoZoomFactor
)
)
do {
try activeDevice.lockForConfiguration()
activeDevice.ramp(toVideoZoomFactor: clampedZoomFactor, withRate: 3.3)
activeDevice.unlockForConfiguration()
} catch {
print("Failed to set zoom: \(error.localizedDescription)")
}
}
func setZoomLevel(_ zoom: Zoom?) {
if zoom != nil {
self.zoomLevel = zoom!
} else {
self.zoomLevel = self.zoomLevel.next()
}
}
func handleZoomAction(progress: CGFloat) {
guard let activeDevice = (self.session.inputs.first as? AVCaptureDeviceInput)?.device else {
print("No active video input device found.")
return
}
if progress < 1.0 {
if activeDevice.deviceType == .builtInUltraWideCamera {
return
}
let ultraWideDevices = AVCaptureDevice.DiscoverySession(
deviceTypes: [
/// For iPhone 11+ models,
.builtInUltraWideCamera
],
mediaType: .video,
position: self.cameraPosition
)
guard let ultraWideDevice = ultraWideDevices.devices.first else {
print("Couldn't find any ultra wide camera")
return
}
self.setCameraDevice(to: ultraWideDevice)
return
} else {
if activeDevice.deviceType != .builtInWideAngleCamera {
let wideCamera = AVCaptureDevice.DiscoverySession(
deviceTypes: [
/// For all iPhone models
.builtInWideAngleCamera
],
mediaType: .video,
position: self.cameraPosition
)
guard let device = wideCamera.devices.first else {
print("Couldn't find any wide camera")
return
}
self.setCameraDevice(to: device)
}
}
self.zoomFactor = CGFloat(progress)
}
}
< /code>
Я попытался переключить входы avcapturedevice, когда фактор масштабирования пересекает определенные пороговые значения (например, менее 1x использует Ultra Wide, 1x и выше используется широко), но переход не анимирован плавно. /> Существует ли рекомендуемый подход или Appi Appi для использования, который обрабатывает этот вид бесшовного многокамерного ZooM? код!
Подробнее здесь: [url]https://stackoverflow.com/questions/79651637/how-to-animate-smooth-camera-transition-between-wide-and-ultra-wide-lenses-like[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия