performance - 核心图像:当从滑块改变强度时,照片效果在真实 iPhone 中反应/变化缓慢
问题描述
下面的代码应用了一个图像选择器并在 contentView 中显示过滤后的图像,在这种情况下我使用了 SepiaTone 效果。当我在滑块中移动时,以更改过滤器强度。照片反应延迟,滑动不流畅。
但是,当使用 App Store 中的某些应用程序(例如“Afterlight”)时,使用它们的照片过滤器,当用户移动滑块时,转换为照片效果是很顺畅的。
那么有什么不同,这些应用程序使用其他框架吗?如果我们仍然使用 Core Image,我们如何提高不断变化的滤镜强度的性能?
图像选择器
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
picker.dismiss(animated: true, completion: nil)
}
}
@Binding var image: UIImage?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
内容视图
import CoreImage
import CoreImage.CIFilterBuiltins
import SwiftUI
struct ContentView: View {
@State private var image: Image?
@State private var filterIntensity = 0.5
@State private var showingImagePicker = false
@State private var inputImage: UIImage?
@State private var currentFilter = CIFilter.sepiaTone()
let context = CIContext()
var body: some View {
let intensity = Binding<Double>(
get: {
self.filterIntensity
},
set: {
self.filterIntensity = $0
self.applyProcessing()
})
return NavigationView {
VStack {
ZStack {
Rectangle()
.fill(Color.secondary)
if image != nil {
image?
.resizable()
.scaledToFit()
} else {
Text("Tap to select a picture")
.foregroundColor(.white)
.font(.headline)
}
}
.onTapGesture {
self.showingImagePicker = true
}
HStack {
Text("Intensity")
Slider(value: intensity)
}
.padding(.vertical)
HStack {
Button("Change Filter") {
// change filter
}
Spacer()
Button("Save") {
// save the picture
}
}
}
.padding([.horizontal, .bottom])
.navigationBarTitle("Instafilter")
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}
}
}
func loadImage() {
guard let inputImage = inputImage else { return }
let beginImage = CIImage(image: inputImage)
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
applyProcessing()
}
func applyProcessing() {
currentFilter.intensity = Float(filterIntensity)
guard let outputImage = currentFilter.outputImage else { return }
if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgImage)
image = Image(uiImage: uiImage)
}
}
}
解决方案
它有助于在后台线程中执行图像过滤,以免图像处理阻塞主队列。此外,不对每一个变化做出反应,而是对变化进行去抖动是有意义的:在实际计算完成之前等待 50ms 的短时间没有变化 - 这可以防止在过滤器参数发生变化时发生大量图像计算通常在短时间内(例如当用户拖动滑块时)。两者都可以使用 Combine 框架来实现,这是一个基于您的代码的示例。效果在一个单独的类中分离ImageEffect
,每当 inputImage/filterIntensity 发生变化时,该类使用 Combine 在后台执行图像处理,并使用 debounce运算符进行了优化:
import CoreImage
import CoreImage.CIFilterBuiltins
import SwiftUI
import Combine
class ImageEffect: ObservableObject {
@Published var filterIntensity = 0.5
@Published var inputImage: UIImage?
@Published var outputImage: UIImage?
@Published var currentFilter = CIFilter.sepiaTone()
let context = CIContext()
var subscriptions = Set<AnyCancellable>()
let queue = DispatchQueue(label: "Image processing")
init() {
self.$inputImage
.map { inputImage -> CIImage? in
guard let inputImage = inputImage else { return nil }
return CIImage(image: inputImage)
}
.combineLatest(self.$filterIntensity)
.debounce(for: .milliseconds(50), scheduler: queue)
.map { inputImage, filterIntensity -> UIImage? in
guard let inputImage = inputImage else { return nil }
self.currentFilter.inputImage = inputImage
self.currentFilter.intensity = Float(filterIntensity)
guard let outputImage = self.currentFilter.outputImage else { return nil }
guard let cgImage = self.context.createCGImage(outputImage, from: outputImage.extent) else { return nil }
return UIImage(cgImage: cgImage)
}
.receive(on: RunLoop.main)
.sink { image in
self.outputImage = image
}
.store(in: &self.subscriptions)
}
}
struct ImageEffectView: View {
@ObservedObject var imageEffect = ImageEffect()
@State private var showingImagePicker = false
var body: some View {
NavigationView {
VStack {
ZStack {
Rectangle()
.fill(Color.secondary)
if imageEffect.outputImage != nil {
Image(uiImage: imageEffect.outputImage!)
.resizable()
.scaledToFit()
} else {
Text("Tap to select a picture")
.foregroundColor(.white)
.font(.headline)
}
}
.onTapGesture {
self.showingImagePicker = true
}
HStack {
Text("Intensity")
Slider(value: $imageEffect.filterIntensity)
}
.padding(.vertical)
HStack {
Button("Change Filter") {
// change filter
}
Spacer()
Button("Save") {
// save the picture
}
}
}
.padding([.horizontal, .bottom])
.navigationBarTitle("Instafilter")
.sheet(isPresented: $showingImagePicker) {
ImagePickerView(image: self.$imageEffect.inputImage)
}
}
}
}
推荐阅读
- laravel - laravel 6 使用社交名流包登录后重定向回页面
- macos - Apple SIP 保护 - 我可以将我的二进制文件添加到保护中吗
- javascript - 使用 Javascript 动态创建的输入不显示下拉列表
- ios - 在真实设备中调用方法“FirebaseApp.configure()”时应用程序崩溃
- html - 图像叠加卡在图像底部,当用户悬停时不会移动
- bash - 别名的有效形式是什么?
- swift - EKEvent event.eventIdentifier 未删除
- android - 迁移 Room 2.1.0 -> 2.2.3(Android / Room / Java:预打包的数据库具有无效架构)
- django - Django 日志在文件大小达到 1050KB 后创建新的日志文件
- selenium - Selenium 块请求,页面加载前的脚本