From a4d28aa63b363cd11e782c20240fabf438f166a7 Mon Sep 17 00:00:00 2001 From: william0wang Date: Tue, 2 Nov 2021 19:00:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=99=E7=A8=8B01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/chs/01.md | 258 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 docs/chs/01.md diff --git a/docs/chs/01.md b/docs/chs/01.md new file mode 100644 index 0000000..bfd7642 --- /dev/null +++ b/docs/chs/01.md @@ -0,0 +1,258 @@ +# 01 基础相机功能实现 + + +## SwiftUI 基础 + +首先,如果你是刚刚开始学习SwiftUI的,那么你可以先学习一下基础的SwiftUI。 +这方面的教程网上有很多,这里就不介绍了,我们直接从实现相机功能开始入手。 + + +## 相机拍照 + +### 拍照protocol和Events + +定义相机拍照协议(protocol),所有相机相关的View需要实现该协议来处理结果。 +定义一个ObservableObject类型,用来控制相机拍照和翻转。 + +```swift + +// 拍照协议 +public protocol CameraFxImage { + // 拍照完成方法 + // image: 照片 UIImage 对象 + mutating func didImageOk(_ image: UIImage) + + // 执行特效方法 + // fx:特效名称 + mutating func doFx(_ fx: String) +} + +// 拍照Events +public class UserEvents: ObservableObject { + // 触发相机拍照 + @Published public var didAskToCapturePhoto = false + // 触发相机翻转 + @Published public var didAskToRotateCamera = false + + public init() { + + } +} + +``` + +### BBMetalImage + +需要先使用 CocosPods 安装 BBMetalImage 具体方法加不在这里赘述了。 +通过 BBMetalImage 库的 BBMetalCamera 来实现相机和基础滤镜功能。 + + +### 相机预览 CameraPhotoFilterView + +通过实现 UIViewController 和 UIViewControllerRepresentable 协议,创建相机预览SwiftUI View +详细代码可以参考: ```MagicCamera/SwiftUICam/CameraPhotoFilterVC.swift``` + +#### CameraPhotoFilterVC 实现 UIViewController + +```swift +// 引入依赖的库 +import AVFoundation +import BBMetalImage + +class CameraPhotoFilterVC: UIViewController { + // 相机 + private var camera: BBMetalCamera! + // 预览View + private var metalView: BBMetalView! + // 相机拍照协议 + public var delegateFx: CameraFxImage? + // 相机方向 + public var position: AVCaptureDevice.Position = .front + + override func viewDidLoad() { + super.viewDidLoad() + + // view.backgroundColor = .white + + var smooth = Float(0.5) + var brightness = Float(0.01) + var preset = AVCaptureSession.Preset.photo + ... + + // 创建相机 + camera = BBMetalCamera(sessionPreset: preset, position: position) + if camera == nil { + camera = BBMetalCamera(sessionPreset: .high, position: position) + } + + let x: CGFloat = 0 + let width: CGFloat = view.bounds.width + let height: CGFloat = min(width*4/3, view.bounds.height*2/3) + + // 创建预览 + metalView = BBMetalView(frame: CGRect(x: x, y: 0, width: width, height: height)) + metalView.setContentHuggingPriority(.defaultLow, for: .horizontal) + metalView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + view.addSubview(metalView) + + camera.canTakePhoto = true + camera.photoDelegate = self + + // 将滤镜添加到相机预览 + camera + .add(consumer: BBMetalBeautyFilter(distanceNormalizationFactor: 4, stepOffset: 4, edgeStrength: 1, smoothDegree: smooth)) + .add(consumer: BBMetalBrightnessFilter(brightness: brightness)) + .add(consumer: metalView) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // 启动相机预览 + camera.start() + metalView.isHidden = false + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + // 停止相机预览 + camera.stop() + } + + public func switchCameraPosition() { + // 翻转相机 + camera.switchCameraPosition() + } + + public func takePhoto() { + // 拍照 + metalView.isHidden = true + camera.takePhoto() + } +} + +// 实现 BBMetalCameraPhotoDelegate,处理拍照结果 +extension CameraPhotoFilterVC: BBMetalCameraPhotoDelegate { + func camera(_ camera: BBMetalCamera, didOutput texture: MTLTexture) { + // In main thread + ... + + // 添加滤镜 + let brightness = BBMetalBrightnessFilter(brightness: bright) + let imageSource = BBMetalStaticImageSource(image: texture.bb_image!) + // 设置滤镜链,并设置滤镜同步执行 + imageSource.add(consumer: BBMetalBeautyFilter(distanceNormalizationFactor: 4, stepOffset: 4, edgeStrength: 1, smoothDegree: smooth)) + .add(consumer: brightness) + .runSynchronously = true + + // 开始处理 + imageSource.transmitTexture() + // 返回处理后结果 + guard let filteredImage = brightness.outputTexture?.bb_image else { return } + + metalView.isHidden = false + DispatchQueue.main.async { + // 完成后调用代理方法,返回照片 + self.delegateFx?.didImageOk(filteredImage) + } + } +} + +``` + +#### CameraPhotoFilterView 实现 UIViewControllerRepresentable + +```swift + +struct CameraPhotoFilterView: UIViewControllerRepresentable { + @ObservedObject var events: UserEvents + private var level: Int + private var position: AVCaptureDevice.Position = .front + private var delegateFx: CameraFxImage? + + typealias UIViewControllerType = CameraPhotoFilterVC + func makeUIViewController(context: Context) -> CameraPhotoFilterVC { + // 创建 CameraPhotoFilterVC 实例 + let vc = CameraPhotoFilterVC() + vc.delegateFx = self.delegateFx + vc.level = self.level + vc.position = self.position + return vc + } + + // 更新时调用 + func updateUIViewController(_ cameraController: CameraPhotoFilterVC, context: Context) { + if events.didAskToCapturePhoto { + // 开始拍照 + events.didAskToCapturePhoto = false + cameraController.takePhoto() + } + + if events.didAskToRotateCamera { + // 开始翻转相机 + events.didAskToRotateCamera = false + cameraController.switchCameraPosition() + } + } + + public init(events: UserEvents, delegateFx: CameraFxImage?, level: Int = 1, position: AVCaptureDevice.Position = .front) { + ... + } +} +``` + +### 拍照界面 + +拍照界面代码如下:需要定义拍照Events,拍照协议,相机方向等参数 +使用 ZStack 把 CameraPhotoFilterView 放在底层,在预览界面上方放置拍照按钮等 +详细代码可以参考: ```MagicCamera/SwiftUICam/LiveCameraFilterView.swift``` + +```swift +struct LiveCameraFilterView: View { + // 拍照Events + @ObservedObject var events: UserEvents + // 拍照协议 + var delegateFx: CameraFxImage? + // 相机方向 + var position: AVCaptureDevice.Position = .front + + // 初始化参数 + public init(events: UserEvents, delegateFx: CameraFxImage?, position: AVCaptureDevice.Position = .front) { + ... + } + + var body: some View { + ZStack { + // 相机预览 CameraPhotoFilterView + CameraPhotoFilterView(events:events, delegateFx: delegateFx, level: level, position: position) + // 在预览上方显示其他界面,如拍照按钮等 + VStack { + HStack { + Spacer() + // 拍照按钮 + Button(action: { + weakself.events.didAskToCapturePhoto = true + }, label: { + ... + }).disabled(takeing) + Spacer() + // 翻转按钮 + Button(action: { + weakself.events.didAskToRotateCamera = true + }, label: { + ... + }).disabled(takeing) + Spacer() + } + ... + } + + } + } +} + +``` + +### 使用拍照 + +在需要拍照的页面上使用 LiveCameraFilterView, 并实现 CameraFxImage 协议 +详细代码可以参考: ```MagicCamera/Cameras/BeautyCameraView.swift```