Статьи

Как использовать лучшие функции AVCapturePhotoOutput Photo

Неважно, будет ли главное внимание в вашем приложении запечатлеть удивительный пейзаж или просто невинную селфи. Что важно для разработчиков, так это позволить пользователям максимально использовать возможности камеры и делать это безболезненно. С этой целью Apple проделала замечательную работу, представив AVCapturePhotoOutput в iOS 10 . В этой статье я покажу, как реализовать некоторые его функции. Полный проект можно скачать с GitHub .

Что нового?

Конечно, приложения iOS могли использовать камеру раньше. Тем не менее, фреймворк имеет новые функции и улучшил существующие.

  • Live Photos смешивает картинку с коротким видео. Это заставляет фотографии «оживать» и лучше вспоминать воспоминания.
  • Съемка RAW фото теперь возможна.
  • Миниатюрные изображения доступны при захвате фотографии. Это полезно для предварительного просмотра изображения для пользователя.
  • Также вы можете делать широкоформатные фотографии.

Давайте доберемся до этого.

Короткая настройка

Чтобы сохранить фокус, я не включил шаги для разработки UIViewController . Его можно скопировать из раскадровки в полном проекте или настроить дизайн в соответствии со своими предпочтениями.

Начните с импорта правильных структур :

 import UIKit import AVFoundation import Photos 

Затем объявите следующие переменные и выходы:

 var captureSession = AVCaptureSession() var cameraOutput = AVCapturePhotoOutput() var previewLayer = AVCaptureVideoPreviewLayer() var currentImage: (image: Data, imageName: String)? var previewing = false var highResolutionEnabled = true var rawEnabled = false var live = .Off var flashMode = AVCaptureFlashMode.off var cameraPosition = AVCaptureDevicePosition.back @IBOutlet weak var capturedButton: UIButton! @IBOutlet weak var previewView: UIView! @IBOutlet weak var menuView: UIView! @IBOutlet weak var toastLabel: UILabel! @IBOutlet weak var saveButton: UIButton! 

LiveMode — это пользовательское перечисление.

 enum LiveMode { case On, Off, Unavailable } 

Для отображения сообщения на экране отображается метка с использованием следующей функции.

 func showToast(text: String) { toastLabel.text = text UIView.animate(withDuration: 1.0, animations: { self.toastLabel.alpha = 1.0 }) UIView.animate(withDuration: 1.0, delay: 2.0, options: .curveLinear, animations: { self.toastLabel.alpha = 0.0 }, completion: nil) } 

Теперь мы готовы двигаться дальше.

права доступа

При запросе доступа к конфиденциальным данным в iOS 10 необходимо добавить строку описания в файл info.plist , который будет отображаться для пользователя, чтобы он знал, что вы будете делать с данными. Следующие ключи описывают использование библиотеки фотографий и камеры соответственно. Это должно помочь пользователю лучше понять, почему пытаются получить доступ к этим компонентам.

 <key>NSPhotoLibraryUsageDescription</key> <string>to save photos and videos</string> <key>NSCameraUsageDescription</key> <string>to take photos</string> 

В первый раз сообщение будет отображаться как на следующих скриншотах. Тогда выбор пользователя будет запомнен.

права доступаправа доступа

Загрузка камеры

Прежде чем пользователь сможет взаимодействовать с приложением, камера должна загрузиться. Это делается в методе viewDidLoad

 override func viewDidLoad() { super.viewDidLoad() loadCamera() } 

Мы должны убедиться, что камера доступна в первую очередь. AVCaptureDevice инициализируется с использованием положения камеры, установленного пользователем. Это запускает переднюю или заднюю камеру.

 func loadCamera() { let device = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: cameraPosition) 

Перед использованием captureSession любой предыдущий ввод удаляется. Это необходимо, когда камера переключается спереди назад и наоборот.

 captureSession.removeInput(captureSession.inputs.first as! AVCaptureInput!) 

Добавьте выход cameraOutput к AVCaptureSession . Это когда мы проверяем, доступен ли захват живого фото , и устанавливаем захват высокого разрешения на выбор пользователя.

 if let input = try? AVCaptureDeviceInput(device: device) { if (captureSession.canAddInput(input)) { captureSession.addInput(input) if (captureSession.canAddOutput(cameraOutput)) { cameraOutput.isHighResolutionCaptureEnabled = self.highResolutionEnabled captureSession.addOutput(cameraOutput) if !cameraOutput.isLivePhotoCaptureSupported { self.live = .Unavailable } // Next, the previewLayer is setup to show the camera content with the size of the view. previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = previewView.bounds previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill previewView.clipsToBounds = true previewView.layer.addSublayer(previewLayer) captureSession.startRunning() } } else { print("Cannot add output") } } 

Хорошей практикой было бы остановить captureSession когда представление больше не видно:

 override func viewDidDisappear(_ animated: Bool) { if captureSession.isRunning { captureSession.stopRunning() } else { captureSession.startRunning() } } 

Теперь у нас работает камера. Время сделать снимок.

Первый захват

Чтобы сделать снимок, просто добавьте следующий IBAction . Это действие запускается при нажатии кнопки со значком камеры. За ним следует классический звуковой эффект затвора камеры.

 @IBAction func didPressTakePhoto(_ sender: UIButton) { var settings = AVCapturePhotoSettings() cameraOutput.capturePhoto(with: settings, delegate: self) } 

В параметрах запроса следует отметить две вещи. Настройки — это объект, который мы только что создали (свойства будут добавлены на следующем шаге). Делегатом является протокол AVCapturePhotoCaptureDelegate , которому должен соответствовать класс. Это сделано в extension класса.

 extension ViewController : AVCapturePhotoCaptureDelegate { func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { if let error = error { print("Capture failed: \(error.localizedDescription)") } } } 

Этот метод вызывается, когда все процессы завершены и фотография готова.

Thumbnail

При съемке фотографии естественным следующим шагом будет предварительный просмотр. AVFoundation делает это легко. Он предлагает возможность указать размеры изображения и получить предварительный просмотр, когда обработка будет завершена.

Нам просто нужно изменить функцию capture выше, чтобы показать изображение в левом нижнем углу, как показано на фотографии.

Thumbnail

 if let error = error { print("Capture failed: \(error.localizedDescription)") } if let sampleBuffer = photoSampleBuffer, let previewBuffer = previewPhotoSampleBuffer, let dataImage = AVCapturePhotoOutput .jpegPhotoDataRepresentation(forJPEGSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewBuffer) { self.currentImage = (dataImage, "\(resolvedSettings.uniqueID).jpg") showImage() } 

Здесь буфер преобразуется в объект Data и устанавливается в качестве текущего изображения. Функция showImage просто берет этот объект и устанавливает его как изображение capturedButton .

 func showImage() { let dataProvider = CGDataProvider(data: self.currentImage!.image as CFData) let cgImageRef: CGImage! = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent) let image = UIImage(cgImage: cgImageRef, scale: 1.0, orientation: UIImageOrientation.right) self.capturedButton.imageView?.contentMode = .scaleAspectFill self.capturedButton.setImage(image, for: .normal) self.capturedButton.isHidden = false } 

Чтобы установить размеры предварительного просмотра, нам нужно добавить формат предварительного просмотра в объект AVCapturePhotoSettings .

 var settings = AVCapturePhotoSettings() let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first! let previewFormat = [ kCVPixelBufferPixelFormatTypeKey as String: previewPixelType, kCVPixelBufferWidthKey as String: self.capturedButton.frame.width, kCVPixelBufferHeightKey as String: self.capturedButton.frame.height ] as [String : Any] settings.previewPhotoFormat = previewFormat 

Сохранение данных

В предварительном просмотре миниатюры в этом проекте доступна возможность сохранить захваченное фото в библиотеке. Для перехода от миниатюры к полноэкранному режиму и наоборот используется простая анимация. Это также показывает и скрывает кнопку Сохранить .

 @IBAction func previewClicked() { UIView.animate(withDuration: 0.5, animations: { if !self.previewing { self.capturedButton.frame = self.view.frame self.saveButton.isHidden = false } else { let x: CGFloat = 20.0 let y: CGFloat = self.view.frame.height - 100.0 - 20.0 self.capturedButton.frame = CGRect(x: x, y: y, width: 75.0, height: 100.0) self.saveButton.isHidden = true } }) previewing = !previewing } 

При нажатии кнопки « Save у пользователя запрашивается разрешение на доступ к библиотеке фотографий. Мы продолжаем пытаться сохранить фотографию только тогда, когда это разрешение предоставлено. Функция showToast вызывается в основном потоке, так как она изменяет пользовательский интерфейс.

 @IBAction func saveClicked() { if let image = currentImage { PHPhotoLibrary.requestAuthorization({ (status) in if status == .authorized { do { try self.save(image: image.image, withName: image.imageName) } catch let error { print(error.localizedDescription) } } else { DispatchQueue.main.async { self.showToast(text: "Not authorized!") } } }) } } 

Наконец, настоящая работа выполняется в функции save . Сначала изображение сохраняется во временный файл.

 func save(image: Data, withName: String) throws { let url = URL(fileURLWithPath: NSTemporaryDirectory().appending(withName)) try image.write(to: url, options: .atomicWrite) 

Затем PHPhotoLibrary используется для добавления изображения в качестве ресурса. Функция performChanges также имеет completionHandler для изменения пользовательского интерфейса или другой работы.

  PHPhotoLibrary.shared().performChanges({ let request = PHAssetCreationRequest.forAsset() request.addResource(with: .photo, data: image, options: nil) let creationOptions = PHAssetResourceCreationOptions() creationOptions.shouldMoveFile = true request.addResource(with: .alternatePhoto, fileURL: url, options: nil) }, completionHandler: { (success, error) in if let error = error { print(error.localizedDescription) return } 

Убедившись, что все в порядке, временный файл удаляется и отображается сообщение об успехе.

  if FileManager.default.fileExists(atPath: url.absoluteString) { do { try FileManager.default.removeItem(at: url) } catch let err { print(err.localizedDescription) } } DispatchQueue.main.async { self.saveButton.isHidden = true self.showToast(text: "Image saved") } }) } 

Сохранить

RAW

Запись изображений в формате RAW , а также Live Photos доступна на iPhone 6s, 6s +, SE и 9,7 ″ iPad Pro. RAW- фотографии несжаты и имеют больше битов на пиксель, что дает больше возможностей для редактирования.

Для захвата RAW фото AVCaptureSessionPresetPhoto формат AVCaptureSessionPresetPhoto и камера заднего вида. Код очень похож на то, что нормальный захват изображения. AVCapturePhotoSettings в этом случае инициализируются с использованием необработанного пиксельного формата.

  if rawEnabled { if let rawFormat = cameraOutput.availableRawPhotoPixelFormatTypes.first { settings = AVCapturePhotoSettings(rawPixelFormatType: OSType(rawFormat)) } } 

Теперь следующий обратный вызов добавлен к расширению класса. Формат файла в этом случае — .dng .

 func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingRawPhotoSampleBuffer rawSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { if let error = error { print("Capture failed: \(error.localizedDescription)") } if let sampleBuffer = rawSampleBuffer, let previewBuffer = previewPhotoSampleBuffer, let dataImage = AVCapturePhotoOutput .dngPhotoDataRepresentation(forRawSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewBuffer) { self.currentImage = (dataImage, "\(resolvedSettings.uniqueID).dng") showImage() } } 

Больше опций

Чтобы изменить некоторые настройки, такие как вспышка или положение камеры, я добавил меню в правом нижнем углу. Это делается видимым через эту функцию.

 @IBAction func moreClicked() { UIView.animate(withDuration: 1.0, animations: { self.menuView.alpha = 1.0 - self.menuView.alpha }) } 

Меню

Каждая из кнопок имеет свой IBAction который срабатывает.

Высокое разрешение

Изменяет логическое значение и шрифт текста кнопки.

 @IBAction func toggleHR(button: UIButton) { highResolutionEnabled = !highResolutionEnabled button.titleLabel?.font = highResolutionEnabled ? UIFont (name: "System-Heavy", size: 15) : UIFont (name: "System-Thin", size: 15) showToast(text: "High resolution: \(highResolutionEnabled)") } 

Затем следующий параметр устанавливается в AVCapturePhotoSettings .

 settings.isHighResolutionPhotoEnabled = self.highResolutionEnabled 

Положение камеры

Меняет положение и перезагружает камеру.

 @IBAction func toggleCamera() { if cameraPosition == .back { cameraPosition = .front showToast(text: "Camera: front") } else { cameraPosition = .back showToast(text: "Camera: back") } loadCamera() } 

вспышка

Поскольку режим вспышки имеет три возможных значения, для переключения между вариантами используется регистр переключателя. Изображение на кнопке настроено соответственно.

 @IBAction func toggleFlash(button: UIButton) { switch self.flashMode { case .off: self.flashMode = .on showToast(text: "Flash mode: on") button.setImage(UIImage(named: "FlashOn"), for: .normal) break case .on: self.flashMode = .auto showToast(text: "Flash mode: auto") button.setImage(UIImage(named: "FlashAuto"), for: .normal) break case .auto: self.flashMode = .off showToast(text: "Flash mode: off") button.setImage(UIImage(named: "FlashOff"), for: .normal) break } } 

Перед съемкой изображения настройки обновляются в режиме вспышки, если он доступен.

 if cameraOutput.supportedFlashModes.contains(NSNumber(value: self.flashMode.rawValue)) { settings.flashMode = self.flashMode } 

RAW

Ранее мы видели, как сделать снимок в формате RAW , но для переключения между RAW и обычным фото нам нужна следующая функция. Сначала мы проверяем, доступно ли это для захвата изображения RAW, а затем действуем аналогично функции переключения высокого разрешения.

 @IBAction func toggleRAW(button: UIButton) { if cameraOutput.availableRawPhotoPixelFormatTypes.count == 0 { showToast(text: "RAW not available") return } rawEnabled = !rawEnabled button.titleLabel?.font = rawEnabled ? UIFont (name: "System-Heavy", size: 15) : UIFont (name: "System-Thin", size: 15) showToast(text: "RAW: \(rawEnabled)") } 

Прямой эфир

Значение для переменной live устанавливается при загрузке камеры. Это -1, когда он недоступен, и 0 или 1, когда он включен или выключен.

 @IBAction func toggleLive(button: UIButton) { switch live { case .Unavailable: showToast(text: "Live photo not supported") break case .On: live = .Off showToast(text: "Live: off") button.titleLabel?.font = UIFont (name: "System-Thin", size: 15) break case .Off: live = .On showToast(text: "Live on") button.titleLabel?.font = UIFont (name: "System-Heavy", size: 15) break } } 

Нам также нужно установить URL для видеофайла в настройках перед тем, как сделать снимок.

 if self.live == .On { let path = "\(NSTemporaryDirectory())/Photoshot_\(settings.uniqueID)" settings.livePhotoMovieFileURL = URL(fileURLWithPath: path) } 

Вывод

AVCapturePhotoOutput реализацию функций камеры в вашем приложении. Эта статья была кратким введением в то, как использовать некоторые из этих функций.

Я надеюсь, что вы найдете это интересным и полезным.

Пусть Кодекс будет с вами.

Ссылки