iOS AI相机App开发教程,01 实现基本相机功能

logo

SwiftUI 基础

首先,如果你是刚刚开始学习SwiftUI的,那么你可以先学习一下基础的SwiftUI。
这方面的教程网上有很多,这里就不介绍了,我们直接从实现相机功能开始入手。

相机拍照

拍照protocol和Events

定义相机拍照协议(protocol),所有相机相关的View需要实现该协议来处理结果。
定义一个ObservableObject类型,用来控制相机拍照和翻转。


// 拍照协议
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

// 引入依赖的库
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


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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容