Last active
September 9, 2022 09:02
-
-
Save pd95/c13fc1b2880a2cd4f33d16723470ad0e to your computer and use it in GitHub Desktop.
InstaFilter main logic for CoreImage filter application
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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