Skip to content

Instantly share code, notes, and snippets.

@pd95
Last active September 9, 2022 09:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pd95/c13fc1b2880a2cd4f33d16723470ad0e to your computer and use it in GitHub Desktop.
Save pd95/c13fc1b2880a2cd4f33d16723470ad0e to your computer and use it in GitHub Desktop.
InstaFilter main logic for CoreImage filter application
//
// ContentView.swift
// Project13
//
// Created by Paul Hudson on 17/02/2020.
// Copyright © 2020 Paul Hudson. All rights reserved.
//
import CoreImage
import CoreImage.CIFilterBuiltins
import SwiftUI
struct ContentView: View {
@State private var image: Image?
@State private var filterIntensity = 0.5
@State private var showingFilterSheet = false
@State private var showingImagePicker = false
@State private var processedImage: UIImage?
@State private var inputImage: UIImage?
@State private var currentFilter: CIFilter = CIFilter.sepiaTone()
let context = CIContext()
@State private var showAlert = false
@State private var alertTitle = ""
@State private var alertMessage = ""
let operationQueue = OperationQueue()
init() {
operationQueue.maxConcurrentOperationCount = 1
}
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") {
self.showingFilterSheet = true
}
Spacer()
Button("Save") {
guard let processedImage = self.processedImage else {
self.showAlert = true
self.alertTitle = "Cannot save"
self.alertMessage = "No image has been processed yet."
return
}
let imageSaver = ImageSaver()
imageSaver.successHandler = {
print("Success!")
}
imageSaver.errorHandler = {
print("Oops: \($0.localizedDescription)")
}
imageSaver.writeToPhotoAlbum(image: processedImage)
}
}
}
.padding([.horizontal, .bottom])
.navigationBarTitle("Instafilter")
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}
.alert(isPresented: $showAlert) {
Alert(title: Text(self.alertTitle), message: Text(self.alertMessage), dismissButton: .default(Text("OK")))
}
.actionSheet(isPresented: self.$showingFilterSheet) {
ActionSheet(title: Text("Select a filter (current '\(CIFilter.localizedName(forFilterName: self.currentFilter.name) ?? "Unknown Filter"))'"), buttons: [
.default(Text("Crystallize")) { self.setFilter(CIFilter.crystallize()) },
.default(Text("Edges")) { self.setFilter(CIFilter.edges()) },
.default(Text("Gaussian Blur")) { self.setFilter(CIFilter.gaussianBlur()) },
.default(Text("Pixellate")) { self.setFilter(CIFilter.pixellate()) },
.default(Text("Sepia Tone")) { self.setFilter(CIFilter.sepiaTone()) },
.default(Text("Unsharp Mask")) { self.setFilter(CIFilter.unsharpMask()) },
.default(Text("Vignette")) { self.setFilter(CIFilter.vignette()) },
.cancel()
])
}
}
}
func loadImage() {
guard let inputImage = inputImage else { return }
let beginImage : CIImage
if let ciImage = inputImage.ciImage {
beginImage = ciImage
}
else {
beginImage = CIImage(cgImage: inputImage.cgImage!).oriented(CGImagePropertyOrientation(inputImage.imageOrientation))
}
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
applyProcessing()
}
var hasIntensity: Bool {
currentFilter.inputKeys.contains(kCIInputIntensityKey)
}
var hasRadius: Bool {
currentFilter.inputKeys.contains(kCIInputRadiusKey)
}
var hasScale: Bool {
currentFilter.inputKeys.contains(kCIInputScaleKey)
}
func applyProcessing() {
if hasIntensity {
print("set intensity \(filterIntensity)")
currentFilter.setValue(filterIntensity, forKey: kCIInputIntensityKey)
}
if hasRadius {
print("set radius \(filterIntensity * 200)")
currentFilter.setValue(filterIntensity * 200, forKey: kCIInputRadiusKey)
}
if hasScale {
print("set scale \(filterIntensity * 10)")
currentFilter.setValue(filterIntensity * 10, forKey: kCIInputScaleKey)
}
guard let outputImage = currentFilter.outputImage else { return }
operationQueue.cancelAllOperations()
let operation = BlockOperation()
operation.addExecutionBlock {
if let cgimg = self.context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgimg)
DispatchQueue.main.async {
self.image = Image(uiImage: uiImage)
self.processedImage = uiImage
}
}
}
operationQueue.addOperation(operation)
}
func setFilter(_ filter: CIFilter) {
currentFilter = filter
loadImage()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension CGImagePropertyOrientation {
init(_ uiOrientation: UIImage.Orientation) {
switch uiOrientation {
case .up: self = .up
case .upMirrored: self = .upMirrored
case .down: self = .down
case .downMirrored: self = .downMirrored
case .left: self = .left
case .leftMirrored: self = .leftMirrored
case .right: self = .right
case .rightMirrored: self = .rightMirrored
@unknown default:
fatalError("Unknown uiOrientation \(uiOrientation)")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment