기본 UI의 틀을 미리 잡아두었다.
업로드한 사진이 있으면 사진을 클릭해서, 없으면 ContentUnavailableView를 클릭해서 사진을 고를 수 있도록 label로 설정했다.
PhotosPicker(selection: $selectedItem) {
if let processedImage {
processedImage
.resizable()
.scaledToFit()
} else {
ContentUnavailableView("No picture", systemImage: "photo.badge.plus", description: Text("Click to import a picture"))
}
}
.buttonStyle(.plain)
.onChange(of: selectedItem, loadImage)
그리고 사진을 변경하면 아래 loadImage
함수를 실행해서 선택된 이미지를 UIImage로 변경한다. 해당 함수는 아래에서 좀 더 보완한다.
func loadImage() {
Task {
guard let imageData = try await selectedItem?.loadTransferable(type: Data.self) else { return }
guard let inputImage = UIImage(data: imageData) else { return }
}
}
이젠 임의로 필터를 설정하고 그 변화를 사진에 적용해야 한다.
먼저, 임의로 필터를 sepiaTone()
으로 설정했다. 그리고 나중에 CGImage를 만들기 위해 context
도 설정했다.
@State private var currentFilter = CIFilter.sepiaTone()
let context = CIContext()
그리고 필터의 intensity
도 설정하고 이미지를 필터대로 변화시켜 Image로 변경하는 함수 applyProcess()
를 설정한다. processedImage
에 변경된 이미지로 설정함으로써 화면에 업로드한 이미지가 변화하는 것을 볼 수 있다.
func applyProcess() {
currentFilter.intensity = Float(intensityAmount)
guard let outputImage = try currentFilter.outputImage else { return }
guard let cgImage = try context.createCGImage(outputImage, from: outputImage.extent) else { return }
let uiImage = UIImage(cgImage: cgImage)
processedImage = Image(uiImage: uiImage)
}
추가로 Slider의 값이 바뀜에 따라 intensityAmount
가 바로 변화하도록 onChange로 설정한다.
Slider(value: $intensityAmount)
.onChange(of: intensityAmount, applyProcess)
💡 Core Image filter가 inputImage 속성을 통해 필터를 적용할 수 있는 CIImage로 바꿀 수 있게 한다. 그러나 가끔 치명적인 앱 에러로 이어지기도 해서 아래처럼 kCIInputImageKey
과 함께 setValue()
를 이용하는 것을 추천한다고 한다.
func loadImage() {
// ...
let beginImage = CIImage(image: inputImage)
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
applyProcessing()
}
왼쪽이 시뮬레이터이고, 오른쪽이 휴대폰에서 실행한 모습이다. 휴대폰이 훨씬 빠르게 적용된다.