import SwiftUI
import PhotosUI
import CoreImage
import CoreImage.CIFilterBuiltins
struct ContentView: View {
@State private var processedImage: Image?
@State private var filterIntensity = 0.5
@State private var beginImage: CIImage?
@State private var selectedItem: PhotosPickerItem?
@State private var currentFilter: CIFilter = CIFilter.sepiaTone()
let context = CIContext()
@State private var showingFilters = false
var body: some View {
NavigationStack {
VStack {
Spacer()
PhotosPicker(selection: $selectedItem) {
if let processedImage {
processedImage
.resizable()
.scaledToFit()
} else {
ContentUnavailableView("No Picture", systemImage: "photo.badge.plus", description: Text("Tap to import a photo"))
}
}.buttonStyle(.plain)
.onChange(of: selectedItem, loadImage)
Spacer()
HStack {
Text("Intensity")
Slider(value: $filterIntensity)
.onChange(of: filterIntensity, applyProcessing)
}
.padding(.vertical)
HStack {
Button("Change Filter", action: changeFilter)
Spacer()
// share the picture
}
}
.padding([.horizontal, .bottom])
.navigationTitle("Instafilter")
.confirmationDialog("Select a filter", isPresented: $showingFilters) {
Button("Crystallize") { setFilter(CIFilter.crystallize()) }
Button("Edges") { setFilter(CIFilter.edges()) }
Button("Gaussian Blur") { setFilter(CIFilter.gaussianBlur()) }
Button("Pixellate") { setFilter(CIFilter.pixellate()) }
Button("Sepia Tone") { setFilter(CIFilter.sepiaTone()) }
Button("Unsharp Mask") { setFilter(CIFilter.unsharpMask()) }
Button("Vignette") { setFilter(CIFilter.vignette()) }
Button("Cancel", role: .cancel) { }
}
}
}
func changeFilter() {
showingFilters = true
}
func loadImage() {
Task {
guard let imageData = try await selectedItem?.loadTransferable(type: Data.self) else { return }
guard let inputImage = UIImage(data: imageData) else { return }
guard let beginImage = CIImage(image: inputImage) else {return}
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
applyProcessing()
}
}
func applyProcessing() {
let inputKeys = currentFilter.inputKeys
if inputKeys.contains(kCIInputIntensityKey) { currentFilter.setValue(filterIntensity, forKey: kCIInputIntensityKey) }
if inputKeys.contains(kCIInputRadiusKey) { currentFilter.setValue(filterIntensity * 200, forKey: kCIInputRadiusKey) }
if inputKeys.contains(kCIInputScaleKey) { currentFilter.setValue(filterIntensity * 10, forKey: kCIInputScaleKey) }
guard let outputImage = currentFilter.outputImage else { return }
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
let uiImage = UIImage(cgImage: cgImage)
processedImage = Image(uiImage: uiImage)
}
func setFilter(_ filter: CIFilter) {
currentFilter = filter
loadImage()
}
}
var buttonDisable: Bool {
processedImage == nil
}
VStack {
// Sliders
// Button
}
.disable(buttonDisable)
@State private var processedImage: Image?
is nil or not. Meaning, if the user selected any image from their library to be uploaded to the app it will be false.
//print(CIFilter.keleidoscope().attributes) Whenever you want to see particular filter attributes you can just adjust it and see exactly what values need to be stored, what’s. Max/min, what data type is needed.
applyProcessing() to match the current data type and allowed values. filterCount that’s an Int, wrap it in @State and bind to a Stepper in range 1…64.
applyProcessing we create a new if statement for kcIInputCount and pass the value from filterCount to adjust the filter amount.
filterCenter property a Double and bind it to a Slider. Then in applyProcessing() wrap the value of center filter inside the CIVector (we know it’s necessary from printing out the attributes). Since we are passing minuscule values like 0.1 or 0.5, multiply it to actually see some changes.
inputKeys.contains(kCIInputCenterKey) { currentFilter.setValue(CIVector(x: filterCenter * 100, y: filterCenter * 100), forKey: kCIInputCenterKey) }
loadImage).
["CIAttributeFilterAvailable_Mac": 10.4, "inputAngle": {
CIAttributeClass = NSNumber;
CIAttributeDefault = 0;
CIAttributeDescription = "The angle in radians of reflection.";
CIAttributeDisplayName = Angle;
CIAttributeIdentity = 0;
CIAttributeSliderMax = "3.141592653589793";
CIAttributeSliderMin = "-3.141592653589793";
CIAttributeType = CIAttributeTypeAngle;
}, "CIAttributeFilterAvailable_iOS": 9, "inputImage": {
CIAttributeClass = CIImage;
CIAttributeDescription = "The image to use as an input for the effect.";
CIAttributeDisplayName = Image;
CIAttributeType = CIAttributeTypeImage;
}, "inputCount": {
CIAttributeClass = NSNumber;
CIAttributeDefault = 6;
CIAttributeDescription = "The number of reflections in the pattern.";
CIAttributeDisplayName = Count;
CIAttributeMin = 1;
CIAttributeSliderMax = 64;
CIAttributeSliderMin = 1;
CIAttributeType = CIAttributeTypeScalar;
}, "CIAttributeFilterName": CIKaleidoscope, "inputCenter": {
CIAttributeClass = CIVector;
CIAttributeDefault = "[150 150]";
CIAttributeDescription = "The center of the effect as x and y pixel coordinates.";
CIAttributeDisplayName = Center;
CIAttributeType = CIAttributeTypePosition;
.confirmationDialog. Same steps need to be folowed for adding different filters.
Text("Sharpness")
.containerRelativeFrame(.horizontal) { size, axis in
size * 0.25
}
.background(Image(.background).resizable().scaledToFill().ignoresSafeArea())
.navigationTitle, I fixed it by applying a .preferredColorScheme(.dark). It will automatically apply white to all text elements.
Button("Change Filter", action: changeFilter).disabled(buttonDisable).padding(7).background(.orange.opacity(0.8)).clipShape(RoundedRectangle(cornerRadius: 20)).foregroundStyle(.white)
}
.tint view modifier. .tint(.cyan.opacity(0.8))
import SwiftUI
struct SliderView: View {
var key: String
@Binding var value: Double
@Binding var currentFilter: CIFilter
var body: some View {
VStack {
HStack {
Text(String(key).trimmingPrefix("input"))
.containerRelativeFrame(.horizontal) { size, axis in
size * 0.25
}
Slider(value: $value)
}
}
}
}
Binding. It’s a series of if statements, we pass the key and based on its value we return the correct property with binding.
func determineValue(_ key: String) -> Binding {
if key == kCIInputIntensityKey {
return $filterIntensity
}
if key == kCIInputRadiusKey {
return $filterRadius
}
if key == kCIInputScaleKey {
return $filterScale
}
if key == kCIInputAngleKey {
return $filterAngle
}
if key == kCIInputSharpnessKey {
return $filterSharpness
}
if key == kCIInputCountKey {
return $filterCount
}
if key == kCIInputCenterKey {
return $filterCenter
}
else {
return $value
}
}
func determineValue2Watch(_ key: String) -> Double {
if key == kCIInputIntensityKey {
return filterIntensity
}
if key == kCIInputRadiusKey {
return filterRadius
}
if key == kCIInputScaleKey {
return filterScale
}
if key == kCIInputAngleKey {
return filterAngle
}
if key == kCIInputSharpnessKey {
return filterSharpness
}
if key == kCIInputCountKey {
return filterCount
}
if key == kCIInputCenterKey {
return filterCenter
}
else {
return value
}
}