下面的代码应用图像选择器并在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)
}
}
}
这有助于在后台线程中执行图像过滤,以免在处理图像时阻塞主队列。同样,不对任何更改做出反应而是一起“分组更改”并且每50毫秒仅进行一次图像计算将是有意义的。两者都可以使用Combine框架来实现,这是基于您的代码的示例。效果被分隔在单独的类ImageEffect
中,该类使用debounce operator优化后,只要inputImage / filterIntensity发生更改,就使用Combine在后台执行图像处理。
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)
}
}
}
}