iOS 一个简单的滤镜相机
2023-04-05 本文已影响0人
一天天的啊哈哈
CIImage做滤镜,Metal显示。
格式不太好,当做记录吧...
整体代码如下:
// capture
var captureSession : AVCaptureSession!
var backCamera : AVCaptureDevice!
var backInput : AVCaptureInput!
var videoOutput : AVCaptureVideoDataOutput!
var photoOutput : AVCapturePhotoOutput!
var captureImage : UIImage!
// metal
var metalDevice : MTLDevice!
var metalCommandQueue : MTLCommandQueue!
// core image
var ciContext : CIContext!
var currentCIImage : CIImage?
let transferFilter = CIFilter(name: "CIPhotoEffectTransfer")
// view
let captureImageButton : UIButton = {
let button = UIButton()
button.backgroundColor = .white
button.tintColor = .white
button.layer.cornerRadius = 25
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
let mtkView = MTKView()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
checkPermissions()
checkAlbum()
setupMetal()
setupCoreImage()
setupAndStartCaptureSession()
}
界面初始化
func setupView() {
view.backgroundColor = .black
mtkView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mtkView)
view.addSubview(captureImageButton)
NSLayoutConstraint.activate([
captureImageButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
captureImageButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
captureImageButton.widthAnchor.constraint(equalToConstant: 50),
captureImageButton.heightAnchor.constraint(equalToConstant: 50),
mtkView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mtkView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mtkView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mtkView.topAnchor.constraint(equalTo: view.topAnchor)
])
captureImageButton.addTarget(self, action: #selector(captureImageClick(_:)), for: .touchUpInside)
}
相机权限
// 相机权限
func checkPermissions() {
let cameraAuthStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
switch cameraAuthStatus {
case .notDetermined:
AVCaptureDevice.requestAccess(for: AVMediaType.video) { (authorized) in
if (!authorized) {
abort()
}
}
case .restricted:
abort()
case .denied:
abort()
case .authorized:
return
@unknown default:
fatalError()
}
}
相册权限
// 相册权限
func checkAlbum() {
let authorStatus:PHAuthorizationStatus? = PHPhotoLibrary.authorizationStatus(for: PHAccessLevel.readWrite)
switch authorStatus {
case .notDetermined:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
}
break
case .authorized:
break
case .restricted:
break
case .denied:
break
case .limited:
break
default:
break
}
}
Metal初始化
// metal
func setupMetal() {
metalDevice = MTLCreateSystemDefaultDevice()
mtkView.device = metalDevice
mtkView.isPaused = true
mtkView.delegate = self
mtkView.framebufferOnly = false
metalCommandQueue = metalDevice.makeCommandQueue()
}
CoreImage
// core image
func setupCoreImage() {
ciContext = CIContext(mtlDevice: metalDevice)
}
func applyFilters(inputImage image: CIImage) -> CIImage? {
var filteredImage : CIImage?
transferFilter?.setValue(image, forKeyPath: kCIInputImageKey)
filteredImage = transferFilter?.outputImage
return filteredImage
}
CaptureSession
// camera
func setupAndStartCaptureSession() {
DispatchQueue.global(qos: .userInitiated).async {
self.captureSession = AVCaptureSession()
self.captureSession.beginConfiguration()
if self.captureSession.canSetSessionPreset(.photo) {
self.captureSession.sessionPreset = .photo
}
self.captureSession.automaticallyConfiguresCaptureDeviceForWideColor = true
self.setupInputs()
self.setupOutput()
self.captureSession.commitConfiguration()
self.captureSession.startRunning()
}
}
func setupInputs() {
if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
backCamera = device
} else {
fatalError("no back camera")
}
guard let bInput = try? AVCaptureDeviceInput(device: backCamera) else {
fatalError()
}
backInput = bInput
if !captureSession.canAddInput(backInput) {
fatalError()
}
captureSession.addInput(backInput)
}
func setupOutput() {
videoOutput = AVCaptureVideoDataOutput()
let videoQueue = DispatchQueue(label: "videoQueue")
videoOutput.setSampleBufferDelegate(self, queue: videoQueue)
if captureSession.canAddOutput(videoOutput) {
captureSession.addOutput(videoOutput)
} else {
fatalError()
}
videoOutput.connections.first?.videoOrientation = .portrait
photoOutput = AVCapturePhotoOutput()
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
}
}
画面渲染
extension ViewController : AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let cvBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let ciImage = CIImage(cvImageBuffer: cvBuffer)
guard let filteredCIImage = applyFilters(inputImage: ciImage) else {
return
}
self.currentCIImage = filteredCIImage
mtkView.draw()
}
}
extension ViewController : MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let commandBuffer = metalCommandQueue.makeCommandBuffer() else {
return
}
guard let ciImage = currentCIImage else {
return
}
guard let currentDrawable = view.currentDrawable else {
return
}
let heightOfciImage = ciImage.extent.height
let heightOfDrawable = view.drawableSize.height
let yOffsetFromBottom = (heightOfDrawable - heightOfciImage)/2
self.ciContext.render(ciImage, to: currentDrawable.texture, commandBuffer: commandBuffer, bounds: CGRect(origin: CGPoint(x: 0, y: -yOffsetFromBottom), size: view.drawableSize), colorSpace: CGColorSpaceCreateDeviceRGB())
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
}
图片输出
extension ViewController : AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
// you can do something
guard let photoData = photo.fileDataRepresentation() else {
return
}
guard let ciImage = CIImage(data: photoData) else {
return
}
guard let filteredCIImage = applyFilters(inputImage: ciImage) else {
return
}
self.captureImage = UIImage(ciImage: filteredCIImage)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if self.captureImage == nil {
return
}
// save
}
}
Click
@objc func captureImageClick(_ sender: UIButton?) {
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey:AVVideoCodecType.jpeg])
settings.flashMode = .off
photoOutput.capturePhoto(with: settings, delegate: self)
}