├── Screenshots ├── Mac 1.jpg ├── Mac 2.jpg ├── iPad Pro (Dark).jpg ├── iPad Pro (Light).jpg ├── iPhone 8 Plus (Dark).jpg ├── iPhone 8 Plus (Light).jpg ├── iPhone 11 Pro Max (Dark).jpg └── iPhone 11 Pro Max (Light).jpg ├── Blurry ├── Supporting Files │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── Rate.imageset │ │ │ ├── Rate.pdf │ │ │ └── Contents.json │ │ ├── Contact.imageset │ │ │ ├── Contact.pdf │ │ │ └── Contents.json │ │ ├── LargeIcon.imageset │ │ │ ├── Icon.png │ │ │ ├── Icon@2x.png │ │ │ ├── Icon@3x.png │ │ │ ├── Icon-iPad.png │ │ │ ├── Icon-iPad@2x.png │ │ │ └── Contents.json │ │ ├── MacIcon.imageset │ │ │ ├── MacIcon.png │ │ │ ├── MacIcon@2x.png │ │ │ └── Contents.json │ │ ├── Twitter.imageset │ │ │ ├── Twitter.pdf │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon@2x.png │ │ │ ├── AppIcon@3x.png │ │ │ ├── AppIcon-iPad.png │ │ │ ├── MacIcon_16x16.png │ │ │ ├── MacIcon_32x32.png │ │ │ ├── AppIcon-iPad@2x.png │ │ │ ├── MacIcon_128x128.png │ │ │ ├── MacIcon_256x256.png │ │ │ ├── MacIcon_512x512.png │ │ │ ├── AppIcon-AppStore.png │ │ │ ├── AppIcon-iPadPro@2x.png │ │ │ ├── MacIcon_128x128@2x.png │ │ │ ├── MacIcon_16x16@2x.png │ │ │ ├── MacIcon_256x256@2x.png │ │ │ ├── MacIcon_32x32@2x.png │ │ │ ├── MacIcon_512x512@2x.png │ │ │ └── Contents.json │ │ ├── SigmaPlanner.imageset │ │ │ ├── sigma-planner.pdf │ │ │ └── Contents.json │ │ └── Acknowledgements.imageset │ │ │ ├── Acknowledgements.pdf │ │ │ └── Contents.json │ ├── Info.plist │ ├── BlurryOld-Info.plist │ └── Acknowledgement.txt ├── UIImageEffects │ ├── Blurry-Bridging-Header.h │ ├── UIImageEffects.h │ └── UIImageEffects.m ├── Extensions │ ├── Bundle++.swift │ ├── UIView++.swift │ ├── CGSize++.swift │ └── UIImage++.swift ├── Blurry.entitlements ├── View │ ├── AboutHeaderView.swift │ ├── NavigationController.swift │ ├── AcknowledgementView.swift │ ├── AboutCell.swift │ ├── MailComposerButton.swift │ ├── MailComposerView.swift │ ├── CustomPaletteView.swift │ ├── AboutView.swift │ └── RootViewController.swift ├── SceneDelegate.swift ├── Model │ ├── BlurStyle.swift │ └── Blurry.swift ├── AppDelegate.swift ├── Base.lproj │ └── LaunchScreen.storyboard └── AboutSceneDelegate.swift ├── Blurry.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── Blurry.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── .gitignore └── README.md /Screenshots/Mac 1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/Mac 1.jpg -------------------------------------------------------------------------------- /Screenshots/Mac 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/Mac 2.jpg -------------------------------------------------------------------------------- /Screenshots/iPad Pro (Dark).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPad Pro (Dark).jpg -------------------------------------------------------------------------------- /Screenshots/iPad Pro (Light).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPad Pro (Light).jpg -------------------------------------------------------------------------------- /Screenshots/iPhone 8 Plus (Dark).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPhone 8 Plus (Dark).jpg -------------------------------------------------------------------------------- /Screenshots/iPhone 8 Plus (Light).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPhone 8 Plus (Light).jpg -------------------------------------------------------------------------------- /Screenshots/iPhone 11 Pro Max (Dark).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPhone 11 Pro Max (Dark).jpg -------------------------------------------------------------------------------- /Screenshots/iPhone 11 Pro Max (Light).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Screenshots/iPhone 11 Pro Max (Light).jpg -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Blurry/UIImageEffects/Blurry-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // Bridging Header to Bridge UIImageEffects to Swift 2 | 3 | #import "UIImageEffects.h" 4 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Rate.imageset/Rate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/Rate.imageset/Rate.pdf -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Contact.imageset/Contact.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/Contact.imageset/Contact.pdf -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/MacIcon.imageset/MacIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/MacIcon.imageset/MacIcon.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Twitter.imageset/Twitter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/Twitter.imageset/Twitter.pdf -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon@3x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/MacIcon.imageset/MacIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/MacIcon.imageset/MacIcon@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon-iPad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon-iPad.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_16x16.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_32x32.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon-iPad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Icon-iPad@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPad@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_128x128.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_256x256.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_512x512.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-AppStore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-AppStore.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPadPro@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/AppIcon-iPadPro@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_128x128@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_16x16@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_256x256@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_32x32@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/MacIcon_512x512@2x.png -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/SigmaPlanner.imageset/sigma-planner.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/SigmaPlanner.imageset/sigma-planner.pdf -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Acknowledgements.imageset/Acknowledgements.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteochu/Blurry/HEAD/Blurry/Supporting Files/Assets.xcassets/Acknowledgements.imageset/Acknowledgements.pdf -------------------------------------------------------------------------------- /Blurry.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Rate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Rate.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Contact.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Contact.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Twitter.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/Acknowledgements.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Acknowledgements.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/SigmaPlanner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sigma-planner.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Blurry.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Blurry.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Blurry.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | .DS_Store 20 | 21 | Pods/ 22 | Podfile.lock 23 | -------------------------------------------------------------------------------- /Blurry/Extensions/Bundle++.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Andy Liang. All rights reserved. 2 | 3 | import Foundation 4 | 5 | extension Bundle { 6 | var marketingVersion: String { 7 | object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 8 | } 9 | 10 | var bundleVersion: String { 11 | object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/MacIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MacIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "MacIcon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Blurry/Extensions/UIView++.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIView { 6 | func addSubviewWithAutoLayout(_ subview: UIView) { 7 | subview.translatesAutoresizingMaskIntoConstraints = false 8 | addSubview(subview) 9 | } 10 | } 11 | 12 | extension NSLayoutConstraint { 13 | @discardableResult 14 | func atPriority(_ priority: UILayoutPriority) -> Self { 15 | self.priority = priority 16 | return self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Blurry/Blurry.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.pictures.read-only 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.personal-information.photos-library 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Blurry/View/AboutHeaderView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import SwiftUI 4 | 5 | struct AboutHeaderView : View { 6 | var body: some View { 7 | VStack { 8 | Image(isCatalyst ? "MacIcon" : "LargeIcon") 9 | .shadow(color: Color.black.opacity(0.25), radius: 3, x: 0, y: 2) 10 | Text("Blurry") 11 | .bold() 12 | .font(.title) 13 | Text("Quick and Easy Image Blurring") 14 | .font(.body) 15 | .foregroundColor(.secondary) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Blurry/View/NavigationController.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | class NavigationController : UINavigationController { 6 | @objc public func popLastViewController() { 7 | guard viewControllers.count > 1 else { return } 8 | popViewController(animated: true) 9 | } 10 | 11 | open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { 12 | guard action == #selector(popLastViewController) else { 13 | return super.canPerformAction(action, withSender: sender) 14 | } 15 | return viewControllers.count > 1 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Blurry/Extensions/CGSize++.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension CGSize { 6 | func aspectFill(ratio aspectRatio: CGSize) -> (size: CGSize, ratio: CGFloat) { 7 | var newSize = aspectRatio 8 | let mW = self.width / aspectRatio.width 9 | let mH = self.height / aspectRatio.height 10 | var ratio: CGFloat = 1.0 11 | if mW < mH { 12 | newSize.height = self.height / mW 13 | ratio = mW 14 | } else if mH < mW { 15 | newSize.width = self.width / mH 16 | ratio = mH 17 | } 18 | return (newSize, ratio) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon.png", 5 | "idiom" : "iphone", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Icon@2x.png", 10 | "idiom" : "iphone", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Icon@3x.png", 15 | "idiom" : "iphone", 16 | "scale" : "3x" 17 | }, 18 | { 19 | "filename" : "Icon-iPad.png", 20 | "idiom" : "ipad", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "filename" : "Icon-iPad@2x.png", 25 | "idiom" : "ipad", 26 | "scale" : "2x" 27 | } 28 | ], 29 | "info" : { 30 | "author" : "xcode", 31 | "version" : 1 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Blurry/View/AcknowledgementView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import SwiftUI 4 | 5 | struct AcknowledgementView : View { 6 | var body: some View { 7 | ScrollView(.vertical, showsIndicators: true) { 8 | Text(textContent) 9 | .font(.footnote) 10 | .padding() 11 | .navigationBarHidden(ProcessInfo.processInfo.isMacCatalystApp) 12 | .navigationTitle("Open Source Licenses") 13 | } 14 | } 15 | 16 | private var textContent: String { 17 | guard let filePath = Bundle.main.path(forResource: "Acknowledgement", ofType: "txt"), 18 | let content = try? String(contentsOfFile: filePath) 19 | else { return "Acknowledgements" } 20 | return content 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Blurry/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | class SceneDelegate : UIResponder, UIWindowSceneDelegate { 6 | var window: UIWindow? 7 | 8 | func scene( 9 | _ scene: UIScene, 10 | willConnectTo session: UISceneSession, 11 | options connectionOptions: UIScene.ConnectionOptions) 12 | { 13 | guard let windowScene = scene as? UIWindowScene, 14 | session.role == .windowApplication // don't permit external display support 15 | else { return } 16 | window = UIWindow(windowScene: windowScene) 17 | window?.rootViewController = RootViewController() 18 | window?.makeKeyAndVisible() 19 | #if targetEnvironment(macCatalyst) 20 | guard let bar = windowScene.titlebar else { return } 21 | bar.titleVisibility = .hidden 22 | #endif 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Blurry/View/AboutCell.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import SwiftUI 4 | 5 | struct AboutCell : View { 6 | let image: Image 7 | let title: String 8 | let accessory: Content 9 | 10 | init(image: Image, title: String, @ViewBuilder accessory: () -> Content) { 11 | self.image = image 12 | self.title = title 13 | self.accessory = accessory() 14 | } 15 | 16 | var body: some View { 17 | HStack { 18 | image 19 | Text(title) 20 | .foregroundColor(.primary) 21 | .font(.body) 22 | Spacer() 23 | accessory 24 | .font(.callout) 25 | .foregroundColor(Color(.placeholderText)) 26 | } 27 | .padding(.horizontal, 12) 28 | .padding(.vertical, isCatalyst ? 4 : 6) 29 | .frame(maxWidth: .infinity) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Blurry/Extensions/UIImage++.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIImage { 6 | func applying(style: BlurStyle, with radius: CGFloat) -> UIImage { 7 | switch style { 8 | case .dark: 9 | return UIImageEffects.imageByApplyingDarkEffect(to: self, withRadius: radius) 10 | case .light: 11 | return UIImageEffects.imageByApplyingLightEffect(to: self, withRadius: radius) 12 | case .custom(let color, let saturation): 13 | return UIImageEffects.imageByApplyingBlur( 14 | to: self, withRadius: radius, tintColor: color, saturationDeltaFactor: saturation, maskImage: nil) 15 | } 16 | } 17 | 18 | func scaled(to newSize: CGSize) -> UIImage { 19 | let renderFormat = UIGraphicsImageRendererFormat.default() 20 | renderFormat.opaque = false 21 | return UIGraphicsImageRenderer(size: newSize, format: renderFormat).image { context in 22 | self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Blurry/View/MailComposerButton.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import SwiftUI 4 | import MessageUI 5 | 6 | struct MailComposerButton : View { 7 | let message: Message 8 | let content: Content 9 | @State var isPresented: Bool = false 10 | 11 | init(message: Message, @ViewBuilder content: () -> Content) { 12 | self.message = message 13 | self.content = content() 14 | } 15 | 16 | var body: some View { 17 | Button { 18 | #if targetEnvironment(macCatalyst) 19 | UIApplication.shared.open(message.mailToUrl, options: [:], completionHandler: nil) 20 | #else 21 | if MFMailComposeViewController.canSendMail() { 22 | isPresented = true 23 | } else { 24 | UIApplication.shared.open(message.mailToUrl, options: [:], completionHandler: nil) 25 | } 26 | #endif 27 | } label: { 28 | content 29 | }.sheet(isPresented: $isPresented) { 30 | isPresented = false 31 | } content: { 32 | MailComposerView(message).edgesIgnoringSafeArea(.all) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Blurry/Model/BlurStyle.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | enum BlurStyle { 6 | case dark 7 | case light 8 | case custom(tint: UIColor, saturation: CGFloat) 9 | 10 | enum Label : String, CaseIterable { 11 | case dark 12 | case light 13 | case custom 14 | } 15 | 16 | var tintColor: UIColor { 17 | switch self { 18 | case .dark: 19 | return .white 20 | case .light: 21 | return UIColor(red: 0.15, green: 0.22, blue: 0.5, alpha: 1) 22 | case .custom: 23 | return .white 24 | } 25 | } 26 | 27 | var titleAttributes: [NSAttributedString.Key: Any] { 28 | return [.foregroundColor: tintColor] 29 | } 30 | 31 | var backgroundColor: UIColor { 32 | switch self { 33 | case .dark: 34 | return UIColor(red: 0.15, green: 0.22, blue: 0.5, alpha: 1) 35 | case .light: 36 | return UIColor(white: 0.9, alpha: 1) 37 | case .custom(let color, _): 38 | return color 39 | } 40 | } 41 | 42 | var infoButtonColor: UIColor { 43 | switch self { 44 | case .dark, .custom: 45 | return UIColor(red: 0.15, green: 0.22, blue: 0.5, alpha: 1) 46 | case .light: 47 | return UIColor(white: 0.9, alpha: 1) 48 | } 49 | } 50 | 51 | var shouldDisplayPicker: Bool { 52 | switch self { 53 | case .custom: return true 54 | default: return false 55 | } 56 | } 57 | 58 | static var allTitles: [String] { 59 | return ["Dark", "Light", "Custom"] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Blurry/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | @UIApplicationMain 6 | class AppDelegate : UIResponder, UIApplicationDelegate { 7 | func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil 10 | ) -> Bool { 11 | return true 12 | } 13 | 14 | func application( 15 | _ application: UIApplication, 16 | configurationForConnecting connectingSceneSession: UISceneSession, 17 | options: UIScene.ConnectionOptions 18 | ) -> UISceneConfiguration { 19 | #if targetEnvironment(macCatalyst) 20 | if options.userActivities.isEmpty { 21 | let config = UISceneConfiguration( 22 | name: "Default Configuration", sessionRole: connectingSceneSession.role) 23 | config.delegateClass = SceneDelegate.self 24 | return config 25 | } else { 26 | let config = UISceneConfiguration( 27 | name: "About Configuration", sessionRole: connectingSceneSession.role) 28 | config.delegateClass = AboutSceneDelegate.self 29 | return config 30 | } 31 | #else // iOS only 32 | let config = UISceneConfiguration( 33 | name: "Default Configuration", sessionRole: connectingSceneSession.role) 34 | config.delegateClass = SceneDelegate.self 35 | return config 36 | #endif 37 | } 38 | 39 | #if targetEnvironment(macCatalyst) 40 | @objc func orderFrontStandardAboutPanel(_ sender: Any?) { 41 | UIApplication.shared.requestSceneSessionActivation( 42 | nil, userActivity: NSUserActivity(activityType: "about-page"), 43 | options: nil, errorHandler: nil) 44 | } 45 | 46 | @objc func showHelp(_ sender: Any?) { 47 | let url = URL(string: "mailto:blurry@andyliang.me")! 48 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 49 | } 50 | #endif 51 | } 52 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Blurry 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.photography 25 | LSRequiresIPhoneOS 26 | 27 | NSPhotoLibraryAddUsageDescription 28 | Blurry requires access to save your edited photos. 29 | NSPhotoLibraryUsageDescription 30 | Blurry requires access to load a photo to edit. 31 | UIApplicationSceneManifest 32 | 33 | UIApplicationSupportsMultipleScenes 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIStatusBarStyle 43 | UIStatusBarStyleLightContent 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/BlurryOld-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Blurry 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.photography 25 | LSRequiresIPhoneOS 26 | 27 | NSPhotoLibraryAddUsageDescription 28 | Blurry requires access to save your edited photos. 29 | NSPhotoLibraryUsageDescription 30 | Blurry requires access to load a photo to edit. 31 | UIApplicationSceneManifest 32 | 33 | UIApplicationSupportsMultipleScenes 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIStatusBarStyle 43 | UIStatusBarStyleLightContent 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Blurry/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Acknowledgement.txt: -------------------------------------------------------------------------------- 1 | ACKNOWLEDGEMNTS 2 | 3 | UIImageEffects 4 | 5 | IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. 6 | 7 | In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. 8 | 9 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 10 | 11 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 14 | -------------------------------------------------------------------------------- /Blurry/View/MailComposerView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import MessageUI 4 | import SwiftUI 5 | 6 | struct Message { 7 | var recipients: [String] = [] 8 | var subject: String = "" 9 | var body: String = "" 10 | 11 | var mailToUrl: URL { 12 | let content = "mailto:\(recipients.joined(separator: ";"))" 13 | + "?subject=\(subject)" 14 | + "&body=\(body.replacingOccurrences(of: "\n", with: "%0D%0A"))" 15 | return URL(string: content.replacingOccurrences(of: " ", with: "%20"))! 16 | } 17 | } 18 | 19 | extension Message { 20 | static let contactInfo = Message( 21 | recipients: ["blurry@andyliang.me"], 22 | subject: "[Feedback] Blurry \(Bundle.main.marketingVersion)", 23 | body: """ 24 | 25 | 26 | 27 | ================================== 28 | App: \(Bundle.main.bundleIdentifier!) 29 | Version: \(Bundle.main.marketingVersion) (Build \(Bundle.main.bundleVersion)) 30 | OS: \(ProcessInfo.processInfo.isMacCatalystApp ? "macOS" : "iOS") \(ProcessInfo.processInfo.operatingSystemVersionString) 31 | Locale: \(Locale.current.localizedString(forIdentifier: Locale.current.identifier) ?? Locale.current.identifier) 32 | ================================== 33 | """) 34 | } 35 | 36 | 37 | struct MailComposerView : UIViewControllerRepresentable { 38 | let message: Message 39 | 40 | init(_ message: Message) { 41 | self.message = message 42 | } 43 | 44 | func makeUIViewController(context: Context) -> MFMailComposeViewController { 45 | let viewController = MFMailComposeViewController() 46 | viewController.mailComposeDelegate = context.coordinator 47 | return viewController 48 | } 49 | 50 | func updateUIViewController(_ viewController: MFMailComposeViewController, context: Context) { 51 | viewController.setSubject(message.subject) 52 | viewController.setToRecipients(message.recipients) 53 | viewController.setMessageBody(message.body, isHTML: false) 54 | } 55 | 56 | // MARK: Coordinator 57 | 58 | func makeCoordinator() -> Coordinator { 59 | return Coordinator() 60 | } 61 | 62 | class Coordinator : NSObject, MFMailComposeViewControllerDelegate { 63 | func mailComposeController( 64 | _ controller: MFMailComposeViewController, 65 | didFinishWith result: MFMailComposeResult, 66 | error: Error? 67 | ) { 68 | controller.dismiss(animated: true, completion: nil) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Blurry/AboutSceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | import SwiftUI 5 | 6 | #if targetEnvironment(macCatalyst) 7 | class AboutSceneDelegate: UIResponder, UIWindowSceneDelegate, NSToolbarDelegate { 8 | var window: UIWindow? 9 | 10 | func scene( 11 | _ scene: UIScene, 12 | willConnectTo session: UISceneSession, 13 | options connectionOptions: UIScene.ConnectionOptions) 14 | { 15 | guard let windowScene = scene as? UIWindowScene else { return } 16 | // 1. setup the size restrictions 17 | let maxSize = windowScene.sizeRestrictions!.maximumSize 18 | if let restrictions = windowScene.sizeRestrictions { 19 | restrictions.minimumSize = CGSize(width: 360, height: 600) 20 | restrictions.maximumSize = CGSize(width: 360, height: 600) 21 | } 22 | // 2. update the title bar 23 | let toolbar = NSToolbar(identifier: "About") 24 | toolbar.delegate = self 25 | windowScene.titlebar?.toolbarStyle = .unifiedCompact 26 | windowScene.titlebar?.toolbar = toolbar 27 | windowScene.title = "About Blurry" 28 | // 3. setup the window 29 | let rootViewController = NavigationController(rootViewController: UIHostingController(rootView: AboutView())) 30 | rootViewController.setNavigationBarHidden(true, animated: false) 31 | window = UIWindow(windowScene: windowScene) 32 | window?.rootViewController = rootViewController 33 | window?.makeKeyAndVisible() 34 | 35 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 36 | guard !windowScene.windows.isEmpty else { return } 37 | windowScene.sizeRestrictions?.maximumSize = maxSize 38 | } 39 | } 40 | 41 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 42 | return [.back, .flexibleSpace] 43 | 44 | } 45 | 46 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 47 | return toolbarDefaultItemIdentifiers(toolbar) 48 | } 49 | 50 | func toolbar( 51 | _ toolbar: NSToolbar, 52 | itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, 53 | willBeInsertedIntoToolbar flag: Bool 54 | ) -> NSToolbarItem? { 55 | switch itemIdentifier { 56 | case .back: 57 | let item = NSToolbarItem(itemIdentifier: .back) 58 | item.image = UIImage(systemName: "chevron.backward") 59 | item.isBordered = true 60 | item.action = #selector(NavigationController.popLastViewController) 61 | item.isNavigational = true 62 | return item 63 | default: 64 | return nil 65 | } 66 | } 67 | } 68 | 69 | private extension NSToolbarItem.Identifier { 70 | static let back = NSToolbarItem.Identifier("Back") 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /Blurry/View/CustomPaletteView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | protocol CustomPaletteViewDelegate : class { 6 | func paletteViewDidSelectColor(_ color: UIColor, saturation: CGFloat, in view: CustomPaletteView) 7 | } 8 | 9 | class CustomPaletteView : UIView { 10 | weak var delegate: CustomPaletteViewDelegate? 11 | 12 | private(set) var currentColor: UIColor = UIColor.systemBlue.withAlphaComponent(0.5) { 13 | didSet { updateDelegate() } 14 | } 15 | 16 | private(set) var currentSaturationValue: CGFloat = -1 { 17 | didSet { updateDelegate() } 18 | } 19 | 20 | private func updateDelegate() { 21 | delegate?.paletteViewDidSelectColor(currentColor, saturation: currentSaturationValue, in: self) 22 | } 23 | 24 | var labelColor: UIColor = .label { 25 | didSet { 26 | colorLabel.textColor = labelColor 27 | sliderLabel.textColor = labelColor 28 | } 29 | } 30 | 31 | private let colorLabel = UILabel() 32 | private let sliderLabel = UILabel() 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | 37 | colorLabel.text = "Tint Color" 38 | colorLabel.font = .preferredFont(forTextStyle: .headline) 39 | 40 | sliderLabel.text = "Saturation (\(NumberFormatter.localizedString(from: currentSaturationValue as NSNumber, number: .decimal)))" 41 | sliderLabel.font = .preferredFont(forTextStyle: .headline) 42 | 43 | let colorWell = UIColorWell(frame: .zero, primaryAction: UIAction { [weak self] action in 44 | guard let colour = (action.sender as? UIColorWell)?.selectedColor else { return } 45 | self?.currentColor = colour 46 | }) 47 | colorWell.selectedColor = currentColor 48 | colorWell.supportsAlpha = true 49 | 50 | let slider = UISlider(frame: .zero, primaryAction: UIAction { [weak self] action in 51 | guard let value = (action.sender as? UISlider)?.value else { return } 52 | let formattedValue = NumberFormatter.localizedString(from: value as NSNumber, number: .decimal) 53 | self?.sliderLabel.text = "Saturation (\(formattedValue))" 54 | guard let existing = self?.currentSaturationValue, abs(CGFloat(value) - existing) >= 0.20 else { return } 55 | self?.currentSaturationValue = CGFloat(value) 56 | }) 57 | slider.minimumValue = -5.0 58 | slider.maximumValue = 5.0 59 | 60 | let contentView = UIStackView(arrangedSubviews: [ colorLabel, colorWell, sliderLabel, slider ]) 61 | contentView.axis = .vertical 62 | contentView.spacing = 8 63 | contentView.setCustomSpacing(16, after: colorWell) 64 | 65 | addSubviewWithAutoLayout(contentView) 66 | layoutMargins = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12) 67 | NSLayoutConstraint.activate([ 68 | contentView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), 69 | contentView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), 70 | contentView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), 71 | contentView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), 72 | heightAnchor.constraint(greaterThanOrEqualToConstant: 160), 73 | widthAnchor.constraint(greaterThanOrEqualToConstant: 280) 74 | ]) 75 | 76 | layer.cornerRadius = 16 77 | layer.masksToBounds = true 78 | backgroundColor = UIColor(white: 1.0, alpha: 0.1) 79 | } 80 | 81 | required init?(coder: NSCoder) { 82 | fatalError("init(coder:) has not been implemented") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Blurry/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "AppIcon@2x.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "60x60" 38 | }, 39 | { 40 | "filename" : "AppIcon@3x.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "scale" : "1x", 48 | "size" : "20x20" 49 | }, 50 | { 51 | "idiom" : "ipad", 52 | "scale" : "2x", 53 | "size" : "20x20" 54 | }, 55 | { 56 | "idiom" : "ipad", 57 | "scale" : "1x", 58 | "size" : "29x29" 59 | }, 60 | { 61 | "idiom" : "ipad", 62 | "scale" : "2x", 63 | "size" : "29x29" 64 | }, 65 | { 66 | "idiom" : "ipad", 67 | "scale" : "1x", 68 | "size" : "40x40" 69 | }, 70 | { 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "40x40" 74 | }, 75 | { 76 | "filename" : "AppIcon-iPad.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "76x76" 80 | }, 81 | { 82 | "filename" : "AppIcon-iPad@2x.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "76x76" 86 | }, 87 | { 88 | "filename" : "AppIcon-iPadPro@2x.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "83.5x83.5" 92 | }, 93 | { 94 | "filename" : "AppIcon-AppStore.png", 95 | "idiom" : "ios-marketing", 96 | "scale" : "1x", 97 | "size" : "1024x1024" 98 | }, 99 | { 100 | "filename" : "MacIcon_16x16.png", 101 | "idiom" : "mac", 102 | "scale" : "1x", 103 | "size" : "16x16" 104 | }, 105 | { 106 | "filename" : "MacIcon_16x16@2x.png", 107 | "idiom" : "mac", 108 | "scale" : "2x", 109 | "size" : "16x16" 110 | }, 111 | { 112 | "filename" : "MacIcon_32x32.png", 113 | "idiom" : "mac", 114 | "scale" : "1x", 115 | "size" : "32x32" 116 | }, 117 | { 118 | "filename" : "MacIcon_32x32@2x.png", 119 | "idiom" : "mac", 120 | "scale" : "2x", 121 | "size" : "32x32" 122 | }, 123 | { 124 | "filename" : "MacIcon_128x128.png", 125 | "idiom" : "mac", 126 | "scale" : "1x", 127 | "size" : "128x128" 128 | }, 129 | { 130 | "filename" : "MacIcon_128x128@2x.png", 131 | "idiom" : "mac", 132 | "scale" : "2x", 133 | "size" : "128x128" 134 | }, 135 | { 136 | "filename" : "MacIcon_256x256.png", 137 | "idiom" : "mac", 138 | "scale" : "1x", 139 | "size" : "256x256" 140 | }, 141 | { 142 | "filename" : "MacIcon_256x256@2x.png", 143 | "idiom" : "mac", 144 | "scale" : "2x", 145 | "size" : "256x256" 146 | }, 147 | { 148 | "filename" : "MacIcon_512x512.png", 149 | "idiom" : "mac", 150 | "scale" : "1x", 151 | "size" : "512x512" 152 | }, 153 | { 154 | "filename" : "MacIcon_512x512@2x.png", 155 | "idiom" : "mac", 156 | "scale" : "2x", 157 | "size" : "512x512" 158 | } 159 | ], 160 | "info" : { 161 | "author" : "xcode", 162 | "version" : 1 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Blurry/Model/Blurry.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | 5 | class Blurry { 6 | private let completionHandler: (UIImage) -> Void 7 | private let completionQueue: DispatchQueue 8 | private let boundingSize: CGSize 9 | private let processQueue = DispatchQueue( 10 | label: "Blurry", qos: .utility, attributes: .concurrent, autoreleaseFrequency: .workItem) 11 | 12 | var currentImage: UIImage? { 13 | didSet { 14 | guard let image = currentImage else { return } 15 | if image.size.height < boundingSize.height || image.size.width < boundingSize.width { 16 | scaledImage = image 17 | } else { 18 | let (size, ratio) = image.size.aspectFill(ratio: boundingSize) 19 | scaledImage = image.scaled(to: size) 20 | self.scaleRatio = ratio 21 | } 22 | } 23 | } 24 | 25 | var blurRadius: CGFloat = .defaultBlurRadius { 26 | didSet { scaledRadius = blurRadius / scaleRatio } 27 | } 28 | 29 | var blurStyle: BlurStyle = .dark { 30 | didSet { processImage() } 31 | } 32 | 33 | private var scaledImage: UIImage? { 34 | didSet { processImage() } 35 | } 36 | 37 | private var scaleRatio: CGFloat = 1.0 { 38 | didSet { scaledRadius = blurRadius / scaleRatio } 39 | } 40 | 41 | private var scaledRadius: CGFloat = .defaultBlurRadius { 42 | didSet { processImage() } 43 | } 44 | 45 | private var isProcessing = false 46 | private var nextWorkItem: DispatchWorkItem? 47 | 48 | init(size: CGSize, queue: DispatchQueue = .main, completion: @escaping (UIImage) -> Void) { 49 | boundingSize = size 50 | completionQueue = queue 51 | completionHandler = completion 52 | } 53 | 54 | private func processImage(for workItem: DispatchWorkItem? = nil) { 55 | processQueue.async { [weak self] in 56 | // if the queue image is nil, ignore 57 | guard let _self = self, let image = _self.scaledImage else { return } 58 | if _self.isProcessing { 59 | _self.nextWorkItem = _self.createWorkItem(for: image) 60 | } else { 61 | let item = workItem ?? _self.createWorkItem(for: image) 62 | _self.processQueue.async(execute: item) 63 | } 64 | } 65 | } 66 | 67 | private func createWorkItem(for image: UIImage) -> DispatchWorkItem { 68 | let style = blurStyle 69 | let radius = scaledRadius 70 | return DispatchWorkItem { 71 | self.isProcessing = true 72 | let startTime = Date() 73 | let blurredImage = image.applying(style: style, with: radius) 74 | let length = -startTime.timeIntervalSinceNow.rounded(toPlaces: 4) 75 | print("[Blurry] Time took to apply blur: \(length)s") 76 | self.completionQueue.async { 77 | self.completionHandler(blurredImage) 78 | } 79 | // Start processing next item if available... 80 | self.isProcessing = false 81 | if let nextItem = self.nextWorkItem { 82 | self.nextWorkItem = nil 83 | self.processImage(for: nextItem) 84 | } 85 | } 86 | } 87 | 88 | // final image that is saved 89 | func applyBlur() -> UIImage? { 90 | guard let image = currentImage else { return nil } 91 | let blurred = image.applying(style: blurStyle, with: blurRadius) 92 | return UIImage(cgImage: blurred.cgImage!, scale: image.scale, orientation: image.imageOrientation) 93 | } 94 | } 95 | 96 | extension Double { 97 | func rounded(toPlaces places: Int) -> Double { 98 | let divisor = pow(10.0, Double(places)) 99 | return (self * divisor).rounded() / divisor 100 | } 101 | } 102 | 103 | extension CGFloat { 104 | static let defaultBlurRadius: CGFloat = 32 105 | } 106 | -------------------------------------------------------------------------------- /Blurry/View/AboutView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Andy Liang. All rights reserved. 2 | 3 | import SwiftUI 4 | 5 | let isCatalyst = ProcessInfo.processInfo.isMacCatalystApp 6 | 7 | struct AboutView: View { 8 | var onDismiss: (() -> Void)? 9 | 10 | var body: some View { 11 | VStack(spacing: 4) { 12 | #if targetEnvironment(macCatalyst) 13 | Spacer() 14 | #endif 15 | AboutHeaderView() 16 | Spacer().frame(height: 16) 17 | Text("OTHER APPS") 18 | .font(.footnote) 19 | .offset(x: 12) 20 | .foregroundColor(.secondary) 21 | .frame(maxWidth: .infinity, alignment: .leading) 22 | Group { 23 | Button { 24 | let appStoreUrl = URL(string: "https://apps.apple.com/app/sigma-planner/id1106938042")! 25 | UIApplication.shared.open(appStoreUrl, options: [:], completionHandler: nil) 26 | } label: { 27 | AboutCell(image: Image("SigmaPlanner"), title: "Sigma Planner") { 28 | Image(systemName: "arrow.up.forward.app") 29 | } 30 | } 31 | Spacer().frame(height: 8) 32 | }.background(cellContentBackground) 33 | 34 | 35 | Text("CREATED BY") 36 | .font(.footnote) 37 | .offset(x: 12) 38 | .foregroundColor(.secondary) 39 | .frame(maxWidth: .infinity, alignment: .leading) 40 | Group { 41 | Button { 42 | let twitterUrl = URL(string: "https://twitter.com/meteochu")! 43 | UIApplication.shared.open(twitterUrl, options: [:], completionHandler: nil) 44 | } label: { 45 | AboutCell(image: Image("Twitter"), title: "Andy Liang") { 46 | Text("@meteochu") 47 | } 48 | } 49 | Spacer().frame(height: 16) 50 | }.background(cellContentBackground) 51 | 52 | VStack(spacing: 0) { 53 | Button { 54 | let appstoreUrl = URL(string: "https://apps.apple.com/app/id1254612844?action=write-review")! 55 | UIApplication.shared.open(appstoreUrl, options: [:], completionHandler: nil) 56 | } label: { 57 | AboutCell(image: Image("Rate"), title: "Review Blurry") { 58 | Image(systemName: "chevron.forward") 59 | } 60 | } 61 | Divider().padding(.leading, 16) 62 | MailComposerButton(message: .contactInfo) { 63 | AboutCell(image: Image("Contact"), title: "Contact Support") { 64 | Image(systemName: "chevron.forward") 65 | } 66 | } 67 | Divider().padding(.leading, 16) 68 | NavigationLink(destination: AcknowledgementView()) { 69 | AboutCell(image: Image("Acknowledgements"), title: "Open Source Licenses") { 70 | Image(systemName: "chevron.forward") 71 | } 72 | } 73 | }.background(cellContentBackground) 74 | Text("Blurry \(Bundle.main.marketingVersion) (\(Bundle.main.bundleVersion))") 75 | .font(.callout) 76 | .foregroundColor(.secondary) 77 | Spacer() 78 | } 79 | .buttonStyle(BorderlessButtonStyle()) 80 | .padding() 81 | .background(Color(.systemGroupedBackground)) 82 | .navigationBarHidden(isCatalyst) 83 | .navigationTitle("About") 84 | } 85 | 86 | var cellContentBackground: some View { 87 | Color(.secondarySystemGroupedBackground) 88 | .cornerRadius(isCatalyst ? 10 : 13) 89 | } 90 | } 91 | 92 | struct AboutView_Previews: PreviewProvider { 93 | static var previews: some View { 94 | AboutView() 95 | .previewLayout(.fixed(width: 375, height: 600)) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blurry 2 | 3 | Blurry is the go-to image blurring tool to help you apply beautiful blurs for your photos. It is perfect for creating wallpapers, backgrounds, and more. 4 | 5 | With Blurry, you can customize and apply a blur effect on top of any photos from your photo library to create the perfect background for your wallpaper. 6 | 7 | Through an extraordinarily simple UI, and a selection of dark blur, light blur, and custom colour blur, you can tweak the image effect however you like and save it when you are satisfied. It's that easy! 8 | 9 | 10 | ## Product 11 | 12 | - [Blurry on the App Store](https://apps.apple.com/app/id1254612844) (Requires iOS 14.0, macOS 11.0) 13 | 14 | ## LICENSE 15 | 16 | Copyright (c) 2017 Andy Liang 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | 36 | ## Open Source Licenses 37 | 38 | ### [UIImageEffects](https://developer.apple.com/library/content/samplecode/UIImageEffects/Introduction/Intro.html) 39 | IMPORTANT: This Apple software is supplied to you by Apple 40 | Inc. ("Apple") in consideration of your agreement to the following 41 | terms, and your use, installation, modification or redistribution of 42 | this Apple software constitutes acceptance of these terms. If you do 43 | not agree with these terms, please do not use, install, modify or 44 | redistribute this Apple software. 45 | 46 | In consideration of your agreement to abide by the following terms, and 47 | subject to these terms, Apple grants you a personal, non-exclusive 48 | license, under Apple's copyrights in this original Apple software (the 49 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 50 | Software, with or without modifications, in source and/or binary forms; 51 | provided that if you redistribute the Apple Software in its entirety and 52 | without modifications, you must retain this notice and the following 53 | text and disclaimers in all such redistributions of the Apple Software. 54 | Neither the name, trademarks, service marks or logos of Apple Inc. may 55 | be used to endorse or promote products derived from the Apple Software 56 | without specific prior written permission from Apple. Except as 57 | expressly stated in this notice, no other rights or licenses, express or 58 | implied, are granted by Apple herein, including but not limited to any 59 | patent rights that may be infringed by your derivative works or by other 60 | works in which the Apple Software may be incorporated. 61 | 62 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 63 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 64 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 65 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 66 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 67 | 68 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 69 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 70 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 71 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 72 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 73 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 74 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 75 | POSSIBILITY OF SUCH DAMAGE. 76 | 77 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 78 | -------------------------------------------------------------------------------- /Blurry/UIImageEffects/UIImageEffects.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImageEffects.h 3 | Abstract: This class contains methods to apply blur and tint effects to an image. 4 | This is the code you’ll want to look out to find out how to use vImage to 5 | efficiently calculate a blur. 6 | Version: 1.1 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import 51 | 52 | @interface UIImageEffects : NSObject 53 | 54 | + (UIImage *)imageByApplyingLightEffectToImage:(UIImage*)inputImage; 55 | + (UIImage *)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage; 56 | + (UIImage *)imageByApplyingDarkEffectToImage:(UIImage*)inputImage; 57 | + (UIImage *)imageByApplyingTintEffectWithColor:(UIColor *)tintColor toImage:(UIImage*)inputImage; 58 | 59 | + (UIImage *)imageByApplyingLightEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius; 60 | + (UIImage *)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius; 61 | + (UIImage *)imageByApplyingDarkEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius; 62 | 63 | //| ---------------------------------------------------------------------------- 64 | //! Applies a blur, tint color, and saturation adjustment to @a inputImage, 65 | //! optionally within the area specified by @a maskImage. 66 | //! 67 | //! @param inputImage 68 | //! The source image. A modified copy of this image will be returned. 69 | //! @param blurRadius 70 | //! The radius of the blur in points. 71 | //! @param tintColor 72 | //! An optional UIColor object that is uniformly blended with the 73 | //! result of the blur and saturation operations. The alpha channel 74 | //! of this color determines how strong the tint is. 75 | //! @param saturationDeltaFactor 76 | //! A value of 1.0 produces no change in the resulting image. Values 77 | //! less than 1.0 will desaturation the resulting image while values 78 | //! greater than 1.0 will have the opposite effect. 79 | //! @param maskImage 80 | //! If specified, @a inputImage is only modified in the area(s) defined 81 | //! by this mask. This must be an image mask or it must meet the 82 | //! requirements of the mask parameter of CGContextClipToMask. 83 | + (UIImage*)imageByApplyingBlurToImage:(UIImage*)inputImage withRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage; 84 | 85 | @end 86 | 87 | -------------------------------------------------------------------------------- /Blurry/View/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Andy Liang. All rights reserved. 2 | 3 | import UIKit 4 | import SwiftUI 5 | 6 | class RootViewController : UIViewController { 7 | 8 | // MARK: view 9 | private let imageView = UIImageView(image: UIImage()) 10 | private let browseButton = UIButton() 11 | private let saveButton = UIButton() 12 | private let blurRadiusLabel = UILabel() 13 | private let paletteView = CustomPaletteView() 14 | #if !targetEnvironment(macCatalyst) 15 | private let infoButton = UIButton(type: .detailDisclosure) 16 | #endif 17 | private var fileName: String? 18 | 19 | private var color: UIColor = UIColor.red.withAlphaComponent(0.5) 20 | private var alpha: CGFloat = 0.35 21 | 22 | private lazy var blurry = Blurry(size: view.bounds.size) { [weak self] image in 23 | self?.imageView.image = image 24 | } 25 | 26 | override var preferredStatusBarStyle: UIStatusBarStyle { 27 | switch blurry.blurStyle { 28 | case .light: 29 | return .darkContent 30 | default: 31 | return .lightContent 32 | } 33 | } 34 | 35 | override func loadView() { 36 | super.loadView() 37 | imageView.contentMode = .scaleAspectFill 38 | view.addInteraction(UIDropInteraction(delegate: self)) 39 | 40 | paletteView.delegate = self 41 | paletteView.isHidden = true 42 | 43 | let blurModePicker = UISegmentedControl(frame: .zero, actions: BlurStyle.Label.allCases.map { label in 44 | return UIAction(title: label.rawValue.capitalized) { [unowned self] action in 45 | let style: BlurStyle 46 | switch label { 47 | case .dark: style = .dark 48 | case .light: style = .light 49 | case .custom: style = .custom( 50 | tint: self.paletteView.currentColor, 51 | saturation: self.paletteView.currentSaturationValue) 52 | } 53 | self.blurry.blurStyle = style 54 | if let segmentedControl = action.sender as? UISegmentedControl { 55 | segmentedControl.setTitleTextAttributes(style.titleAttributes, for: .normal) 56 | } 57 | self.paletteView.isHidden = !style.shouldDisplayPicker 58 | self.updateUI() 59 | } 60 | }) 61 | blurModePicker.selectedSegmentIndex = 0 62 | blurModePicker.setTitleTextAttributes(blurry.blurStyle.titleAttributes, for: .normal) 63 | if traitCollection.userInterfaceIdiom != .mac { 64 | blurModePicker.selectedSegmentTintColor = UIColor(white: 1.0, alpha: 0.25) 65 | } 66 | 67 | blurRadiusLabel.font = .preferredFont(forTextStyle: .headline) 68 | blurRadiusLabel.text = "Blur Radius (\(Int(CGFloat.defaultBlurRadius)))" 69 | 70 | let radiusSlider = UISlider(frame: .zero, primaryAction: UIAction { [unowned self] action in 71 | guard let value = (action.sender as? UISlider)?.value else { return } 72 | let newValue = CGFloat(value.rounded()) 73 | // only re-blur image if there's a diff 74 | guard abs(newValue - blurry.blurRadius) >= 1 else { return } 75 | self.blurRadiusLabel.text = "Blur Radius (\(Int(newValue)))" 76 | self.blurry.blurRadius = newValue 77 | }) 78 | radiusSlider.isContinuous = true 79 | radiusSlider.minimumValue = 0.0 80 | radiusSlider.maximumValue = 200 81 | radiusSlider.value = Float(CGFloat.defaultBlurRadius) 82 | 83 | browseButton.setTitle("Select Photo", for: .normal) 84 | browseButton.titleLabel?.font = .preferredFont(forTextStyle: .headline) 85 | browseButton.showsMenuAsPrimaryAction = true 86 | browseButton.menu = UIMenu(title: "Select a Source", children: [ 87 | UIAction(title: "Photo Library", image: UIImage(systemName: "photo")) { [weak self] _ in 88 | let picker = UIImagePickerController() 89 | picker.delegate = self 90 | self?.present(picker, animated: true, completion: nil) 91 | }, 92 | UIAction(title: "File Browser", image: UIImage(systemName: "folder")) { [weak self] _ in 93 | let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.image], asCopy: true) 94 | picker.delegate = self 95 | self?.present(picker, animated: true, completion: nil) 96 | } 97 | ]) 98 | 99 | saveButton.isEnabled = false 100 | saveButton.setTitle("Save Image", for: .normal) 101 | saveButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium) 102 | let shareAction = UIAction(title: "Share") { _ in 103 | self.processSaveRequest { image in 104 | let shareSheet = UIActivityViewController(activityItems: [image], applicationActivities: []) 105 | shareSheet.modalPresentationStyle = .popover 106 | if let popover = shareSheet.popoverPresentationController { 107 | popover.sourceView = self.saveButton 108 | popover.sourceRect = self.saveButton.bounds 109 | } 110 | self.present(shareSheet, animated: true, completion: nil) 111 | } 112 | } 113 | #if targetEnvironment(macCatalyst) 114 | let saveAction = UIAction(title: "Save to Files") { _ in 115 | self.processSaveRequest { image in 116 | let fileName = "blurry-\(self.fileName ?? "image.jpeg")" 117 | guard let data = image.jpegData(compressionQuality: 0.8), 118 | let exportURL = FileManager.default 119 | .urls(for: .documentDirectory, in: .userDomainMask) 120 | .first?.appendingPathComponent(fileName) else { return } 121 | do { 122 | try data.write(to: exportURL) 123 | let browser = UIDocumentPickerViewController(forExporting: [exportURL], asCopy: true) 124 | self.present(browser, animated: true, completion: nil) 125 | } catch { 126 | print(error) 127 | } 128 | } 129 | } 130 | saveButton.menu = UIMenu(title: "Destination", children: [shareAction, saveAction]) 131 | saveButton.showsMenuAsPrimaryAction = true 132 | #else 133 | saveButton.addAction(shareAction, for: .touchUpInside) 134 | #endif 135 | 136 | let contentView = UIStackView(arrangedSubviews: [ 137 | blurModePicker, blurRadiusLabel, radiusSlider, browseButton, saveButton 138 | ]) 139 | contentView.axis = .vertical 140 | contentView.distribution = .equalCentering 141 | contentView.spacing = 12 142 | contentView.setCustomSpacing(4, after: blurRadiusLabel) 143 | 144 | browseButton.isPointerInteractionEnabled = true 145 | saveButton.isPointerInteractionEnabled = true 146 | #if !targetEnvironment(macCatalyst) 147 | infoButton.isPointerInteractionEnabled = true 148 | #endif 149 | 150 | view.addSubviewWithAutoLayout(imageView) 151 | view.addSubviewWithAutoLayout(paletteView) 152 | view.addSubviewWithAutoLayout(contentView) 153 | #if !targetEnvironment(macCatalyst) 154 | infoButton.addAction(UIAction { [weak self] _ in 155 | let viewController = UINavigationController(rootViewController: UIHostingController(rootView: AboutView())) 156 | self?.present(viewController, animated: true, completion: nil) 157 | }, for: .primaryActionTriggered) 158 | 159 | view.addSubviewWithAutoLayout(infoButton) 160 | #endif 161 | 162 | let guide = view.layoutMarginsGuide 163 | let safeAreaGuide = view.safeAreaLayoutGuide 164 | 165 | NSLayoutConstraint.activate([ 166 | // 1. image view 167 | imageView.topAnchor.constraint(equalTo: view.topAnchor), 168 | imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 169 | imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 170 | imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 171 | // 2. color picker view 172 | paletteView.topAnchor.constraint(greaterThanOrEqualTo: safeAreaGuide.topAnchor, constant: 8), 173 | paletteView.leadingAnchor.constraint(greaterThanOrEqualTo: guide.leadingAnchor), 174 | paletteView.trailingAnchor.constraint(lessThanOrEqualTo: guide.trailingAnchor), 175 | paletteView.centerXAnchor.constraint(equalTo: guide.centerXAnchor), 176 | paletteView.bottomAnchor.constraint(equalTo: view.centerYAnchor).atPriority(.defaultLow), 177 | // 3. content view 178 | contentView.topAnchor.constraint(equalTo: paletteView.bottomAnchor, constant: 32), 179 | contentView.leadingAnchor.constraint(equalTo: paletteView.leadingAnchor), 180 | contentView.trailingAnchor.constraint(equalTo: paletteView.trailingAnchor), 181 | contentView.bottomAnchor.constraint(lessThanOrEqualTo: safeAreaGuide.bottomAnchor, constant: -8), 182 | ]) 183 | 184 | #if !targetEnvironment(macCatalyst) 185 | NSLayoutConstraint.activate([ 186 | infoButton.bottomAnchor.constraint(equalTo: safeAreaGuide.bottomAnchor, constant: -16), 187 | infoButton.trailingAnchor.constraint(equalTo: safeAreaGuide.trailingAnchor, constant: -16) 188 | ]) 189 | #endif 190 | updateUI() 191 | } 192 | 193 | private func updateUI() { 194 | let tintColor = blurry.blurStyle.tintColor 195 | let backgroundColor = blurry.blurStyle.backgroundColor 196 | view.backgroundColor = backgroundColor 197 | view.tintColor = tintColor 198 | paletteView.labelColor = tintColor 199 | blurRadiusLabel.textColor = tintColor 200 | browseButton.setTitleColor(tintColor, for: .normal) 201 | browseButton.setTitleColor(tintColor.withAlphaComponent(0.5), for: .highlighted) 202 | saveButton.setTitleColor(tintColor, for: .normal) 203 | saveButton.setTitleColor(tintColor.withAlphaComponent(0.5), for: .highlighted) 204 | saveButton.setTitleColor(tintColor.withAlphaComponent(0.35), for: .disabled) 205 | setNeedsStatusBarAppearanceUpdate() 206 | } 207 | 208 | private func startProcessing(_ image: UIImage, url: URL?) { 209 | fileName = url?.lastPathComponent 210 | saveButton.isEnabled = true 211 | blurry.currentImage = image 212 | print("[Blurry] opened file: \(fileName ?? "-")") 213 | } 214 | 215 | private func processSaveRequest(with saveAction: (UIImage) -> Void) { 216 | guard let image = blurry.applyBlur() else { 217 | let failedAlert = UIAlertController( 218 | title: "Save Failed...", 219 | message: "There was no source photo.", 220 | preferredStyle: .alert) 221 | let cancelAction = UIAlertAction(title: "Okay", style: .cancel, handler: nil) 222 | failedAlert.addAction(cancelAction) 223 | self.present(failedAlert, animated: true, completion: nil) 224 | return 225 | } 226 | saveAction(image) 227 | } 228 | } 229 | 230 | extension RootViewController : UIDropInteractionDelegate { 231 | func dropInteraction( 232 | _ interaction: UIDropInteraction, 233 | sessionDidUpdate session: UIDropSession 234 | ) -> UIDropProposal { 235 | return UIDropProposal(operation: .copy) 236 | } 237 | 238 | func dropInteraction( 239 | _ interaction: UIDropInteraction, 240 | canHandle session: UIDropSession 241 | ) -> Bool { 242 | return session.canLoadObjects(ofClass: UIImage.self) 243 | } 244 | 245 | func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { 246 | session.loadObjects(ofClass: UIImage.self) { images in 247 | guard let image = images.first as? UIImage else { return } 248 | self.startProcessing(image, url: nil) 249 | } 250 | } 251 | } 252 | 253 | extension RootViewController : UIDocumentPickerDelegate { 254 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 255 | controller.dismiss(animated: true, completion: nil) 256 | } 257 | 258 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 259 | guard let url = urls.first, 260 | let data = try? Data(contentsOf: url), 261 | let image = UIImage(data: data) 262 | else { 263 | let alertController = UIAlertController( 264 | title: "Invalid Image", 265 | message: "Blurry failed to open the image, try again.", 266 | preferredStyle: .alert) 267 | alertController.addAction(UIAlertAction(title: "Okay", style: .cancel, handler: nil)) 268 | self.present(alertController, animated: true, completion: nil) 269 | return 270 | } 271 | 272 | self.startProcessing(image, url: url) 273 | } 274 | } 275 | 276 | extension RootViewController : 277 | UIImagePickerControllerDelegate, 278 | UINavigationControllerDelegate 279 | { 280 | 281 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 282 | picker.dismiss(animated: true, completion: nil) 283 | } 284 | 285 | typealias MediaInfo = [UIImagePickerController.InfoKey : Any] 286 | func imagePickerController( 287 | _ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: MediaInfo) 288 | { 289 | picker.dismiss(animated: true, completion: nil) 290 | guard let image = info[.originalImage] as? UIImage else { return } 291 | startProcessing(image, url: nil) 292 | } 293 | } 294 | 295 | extension RootViewController : CustomPaletteViewDelegate { 296 | func paletteViewDidSelectColor(_ color: UIColor, saturation: CGFloat, in view: CustomPaletteView) { 297 | blurry.blurStyle = .custom(tint: color, saturation: saturation) 298 | updateUI() 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Blurry/UIImageEffects/UIImageEffects.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImageEffects.m 3 | Abstract: This class contains methods to apply blur and tint effects to an image. 4 | This is the code you’ll want to look out to find out how to use vImage to 5 | efficiently calculate a blur. 6 | Version: 1.1 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import "UIImageEffects.h" 51 | 52 | @import Accelerate; 53 | 54 | @implementation UIImageEffects 55 | 56 | #pragma mark - 57 | #pragma mark - Effects 58 | 59 | //| ---------------------------------------------------------------------------- 60 | + (UIImage *)imageByApplyingLightEffectToImage:(UIImage*)inputImage 61 | { 62 | UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; 63 | return [self imageByApplyingBlurToImage:inputImage withRadius:60 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 64 | } 65 | 66 | + (UIImage *)imageByApplyingLightEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius 67 | { 68 | UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; 69 | return [self imageByApplyingBlurToImage:inputImage withRadius:radius tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 70 | } 71 | 72 | 73 | //| ---------------------------------------------------------------------------- 74 | + (UIImage *)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage 75 | { 76 | UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; 77 | return [self imageByApplyingBlurToImage:inputImage withRadius:40 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 78 | } 79 | 80 | + (UIImage *)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius 81 | { 82 | UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; 83 | return [self imageByApplyingBlurToImage:inputImage withRadius:40 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 84 | } 85 | 86 | //| ---------------------------------------------------------------------------- 87 | + (UIImage *)imageByApplyingDarkEffectToImage:(UIImage*)inputImage 88 | { 89 | UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; 90 | return [self imageByApplyingBlurToImage:inputImage withRadius:40 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 91 | } 92 | 93 | + (UIImage *)imageByApplyingDarkEffectToImage:(UIImage*)inputImage withRadius:(CGFloat)radius 94 | { 95 | UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; 96 | return [self imageByApplyingBlurToImage:inputImage withRadius:radius tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 97 | } 98 | 99 | 100 | //| ---------------------------------------------------------------------------- 101 | + (UIImage *)imageByApplyingTintEffectWithColor:(UIColor *)tintColor toImage:(UIImage*)inputImage 102 | { 103 | const CGFloat EffectColorAlpha = 0.6; 104 | UIColor *effectColor = tintColor; 105 | size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); 106 | if (componentCount == 2) { 107 | CGFloat b; 108 | if ([tintColor getWhite:&b alpha:NULL]) { 109 | effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; 110 | } 111 | } 112 | else { 113 | CGFloat r, g, b; 114 | if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { 115 | effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; 116 | } 117 | } 118 | return [self imageByApplyingBlurToImage:inputImage withRadius:20 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil]; 119 | } 120 | 121 | #pragma mark - 122 | #pragma mark - Implementation 123 | 124 | //| ---------------------------------------------------------------------------- 125 | + (UIImage*)imageByApplyingBlurToImage:(UIImage*)inputImage withRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage 126 | { 127 | #define ENABLE_BLUR 1 128 | #define ENABLE_SATURATION_ADJUSTMENT 1 129 | #define ENABLE_TINT 1 130 | 131 | // Check pre-conditions. 132 | if (inputImage.size.width < 1 || inputImage.size.height < 1) 133 | { 134 | NSLog(@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", inputImage.size.width, inputImage.size.height, inputImage); 135 | return nil; 136 | } 137 | if (!inputImage.CGImage) 138 | { 139 | NSLog(@"*** error: inputImage must be backed by a CGImage: %@", inputImage); 140 | return nil; 141 | } 142 | if (maskImage && !maskImage.CGImage) 143 | { 144 | NSLog(@"*** error: effectMaskImage must be backed by a CGImage: %@", maskImage); 145 | return nil; 146 | } 147 | 148 | BOOL hasBlur = blurRadius > __FLT_EPSILON__; 149 | BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; 150 | 151 | CGImageRef inputCGImage = inputImage.CGImage; 152 | CGFloat inputImageScale = inputImage.scale; 153 | CGBitmapInfo inputImageBitmapInfo = CGImageGetBitmapInfo(inputCGImage); 154 | CGImageAlphaInfo inputImageAlphaInfo = (inputImageBitmapInfo & kCGBitmapAlphaInfoMask); 155 | 156 | CGSize outputImageSizeInPoints = inputImage.size; 157 | CGRect outputImageRectInPoints = { CGPointZero, outputImageSizeInPoints }; 158 | 159 | // Set up output context. 160 | BOOL useOpaqueContext; 161 | if (inputImageAlphaInfo == kCGImageAlphaNone || inputImageAlphaInfo == kCGImageAlphaNoneSkipLast || inputImageAlphaInfo == kCGImageAlphaNoneSkipFirst) 162 | useOpaqueContext = YES; 163 | else 164 | useOpaqueContext = NO; 165 | UIGraphicsBeginImageContextWithOptions(outputImageRectInPoints.size, useOpaqueContext, inputImageScale); 166 | CGContextRef outputContext = UIGraphicsGetCurrentContext(); 167 | CGContextScaleCTM(outputContext, 1.0, -1.0); 168 | CGContextTranslateCTM(outputContext, 0, -outputImageRectInPoints.size.height); 169 | 170 | if (hasBlur || hasSaturationChange) 171 | { 172 | vImage_Buffer effectInBuffer; 173 | vImage_Buffer scratchBuffer1; 174 | 175 | vImage_Buffer *inputBuffer; 176 | vImage_Buffer *outputBuffer; 177 | 178 | vImage_CGImageFormat format = { 179 | .bitsPerComponent = 8, 180 | .bitsPerPixel = 32, 181 | .colorSpace = NULL, 182 | // (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) 183 | // requests a BGRA buffer. 184 | .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, 185 | .version = 0, 186 | .decode = NULL, 187 | .renderingIntent = kCGRenderingIntentDefault 188 | }; 189 | 190 | vImage_Error e = vImageBuffer_InitWithCGImage(&effectInBuffer, &format, NULL, inputImage.CGImage, kvImagePrintDiagnosticsToConsole); 191 | if (e != kvImageNoError) 192 | { 193 | NSLog(@"*** error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", e, inputImage); 194 | UIGraphicsEndImageContext(); 195 | return nil; 196 | } 197 | 198 | vImageBuffer_Init(&scratchBuffer1, effectInBuffer.height, effectInBuffer.width, format.bitsPerPixel, kvImageNoFlags); 199 | inputBuffer = &effectInBuffer; 200 | outputBuffer = &scratchBuffer1; 201 | 202 | #if ENABLE_BLUR 203 | if (hasBlur) 204 | { 205 | // A description of how to compute the box kernel width from the Gaussian 206 | // radius (aka standard deviation) appears in the SVG spec: 207 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 208 | // 209 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 210 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 211 | // approximates the Gaussian kernel to within roughly 3%. 212 | // 213 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 214 | // 215 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 216 | // 217 | CGFloat inputRadius = blurRadius * inputImageScale; 218 | if (inputRadius - 2. < __FLT_EPSILON__) 219 | inputRadius = 2.; 220 | uint32_t radius = floor((inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5) / 2); 221 | 222 | radius |= 1; // force radius to be odd so that the three box-blur methodology works. 223 | 224 | NSInteger tempBufferSize = vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); 225 | void *tempBuffer = malloc(tempBufferSize); 226 | 227 | vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 228 | vImageBoxConvolve_ARGB8888(outputBuffer, inputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 229 | vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 230 | 231 | free(tempBuffer); 232 | 233 | vImage_Buffer *temp = inputBuffer; 234 | inputBuffer = outputBuffer; 235 | outputBuffer = temp; 236 | } 237 | #endif 238 | 239 | #if ENABLE_SATURATION_ADJUSTMENT 240 | if (hasSaturationChange) 241 | { 242 | CGFloat s = saturationDeltaFactor; 243 | // These values appear in the W3C Filter Effects spec: 244 | // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/index.html#grayscaleEquivalent 245 | // 246 | CGFloat floatingPointSaturationMatrix[] = { 247 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 248 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 249 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 250 | 0, 0, 0, 1, 251 | }; 252 | const int32_t divisor = 256; 253 | NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]); 254 | int16_t saturationMatrix[matrixSize]; 255 | for (NSUInteger i = 0; i < matrixSize; ++i) { 256 | saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); 257 | } 258 | vImageMatrixMultiply_ARGB8888(inputBuffer, outputBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); 259 | 260 | vImage_Buffer *temp = inputBuffer; 261 | inputBuffer = outputBuffer; 262 | outputBuffer = temp; 263 | } 264 | #endif 265 | 266 | CGImageRef effectCGImage; 267 | if ( (effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, &cleanupBuffer, NULL, kvImageNoAllocate, NULL)) == NULL ) { 268 | effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, NULL, NULL, kvImageNoFlags, NULL); 269 | free(inputBuffer->data); 270 | } 271 | if (maskImage) { 272 | // Only need to draw the base image if the effect image will be masked. 273 | CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); 274 | } 275 | 276 | // draw effect image 277 | CGContextSaveGState(outputContext); 278 | if (maskImage) 279 | CGContextClipToMask(outputContext, outputImageRectInPoints, maskImage.CGImage); 280 | CGContextDrawImage(outputContext, outputImageRectInPoints, effectCGImage); 281 | CGContextRestoreGState(outputContext); 282 | 283 | // Cleanup 284 | CGImageRelease(effectCGImage); 285 | free(outputBuffer->data); 286 | } 287 | else 288 | { 289 | // draw base image 290 | CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); 291 | } 292 | 293 | #if ENABLE_TINT 294 | // Add in color tint. 295 | if (tintColor) 296 | { 297 | CGContextSaveGState(outputContext); 298 | CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); 299 | CGContextFillRect(outputContext, outputImageRectInPoints); 300 | CGContextRestoreGState(outputContext); 301 | } 302 | #endif 303 | 304 | // Output image is ready. 305 | UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); 306 | UIGraphicsEndImageContext(); 307 | 308 | return outputImage; 309 | #undef ENABLE_BLUR 310 | #undef ENABLE_SATURATION_ADJUSTMENT 311 | #undef ENABLE_TINT 312 | } 313 | 314 | 315 | //| ---------------------------------------------------------------------------- 316 | // Helper function to handle deferred cleanup of a buffer. 317 | // 318 | void cleanupBuffer(void *userData, void *buf_data) 319 | { free(buf_data); } 320 | 321 | @end 322 | 323 | -------------------------------------------------------------------------------- /Blurry.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1106CA4C1F01A0FA00434E58 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1106CA4B1F01A0FA00434E58 /* AppDelegate.swift */; }; 11 | 1106CA531F01A0FA00434E58 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1106CA521F01A0FA00434E58 /* Assets.xcassets */; }; 12 | 1106CA561F01A0FA00434E58 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1106CA541F01A0FA00434E58 /* LaunchScreen.storyboard */; }; 13 | 11376D56206DB294006E15BA /* Blurry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11376D55206DB294006E15BA /* Blurry.swift */; }; 14 | 115DDD93255A330D004F5FD5 /* MailComposerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDD92255A330D004F5FD5 /* MailComposerButton.swift */; }; 15 | 115DDD97255A332D004F5FD5 /* MailComposerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDD96255A332D004F5FD5 /* MailComposerView.swift */; }; 16 | 115DDD9B255A34E1004F5FD5 /* AcknowledgementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDD9A255A34E1004F5FD5 /* AcknowledgementView.swift */; }; 17 | 115DDD9F255A34F8004F5FD5 /* AboutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDD9E255A34F8004F5FD5 /* AboutCell.swift */; }; 18 | 115DDDA3255A3513004F5FD5 /* AboutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDDA2255A3513004F5FD5 /* AboutHeaderView.swift */; }; 19 | 115DDDB1255A390B004F5FD5 /* CustomPaletteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115DDDB0255A390B004F5FD5 /* CustomPaletteView.swift */; }; 20 | 1165BC741F06E42900460749 /* UIImage++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1165BC731F06E42900460749 /* UIImage++.swift */; }; 21 | 1165BC761F06E44200460749 /* BlurStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1165BC751F06E44200460749 /* BlurStyle.swift */; }; 22 | 11850A632559B7DD005E0813 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11850A622559B7DD005E0813 /* NavigationController.swift */; }; 23 | 11850A672559B8F1005E0813 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11850A662559B8F1005E0813 /* AboutView.swift */; }; 24 | 11B91EBC206DEF160070454D /* CGSize++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11B91EBB206DEF160070454D /* CGSize++.swift */; }; 25 | 11F5AA5E1F01A89200FC1522 /* UIImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 11F5AA5D1F01A89200FC1522 /* UIImageEffects.m */; }; 26 | 11FAAF951F32B248007D212F /* Acknowledgement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 11FAAF941F32B248007D212F /* Acknowledgement.txt */; }; 27 | A189B20423666BCA001C5814 /* AboutSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A189B20323666BCA001C5814 /* AboutSceneDelegate.swift */; }; 28 | A1B4F70E234ADB76004203BB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4F70D234ADB76004203BB /* SceneDelegate.swift */; }; 29 | A1B4F710234ADD4B004203BB /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4F70F234ADD4B004203BB /* RootViewController.swift */; }; 30 | A1B4F716234AF641004203BB /* UIView++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4F715234AF641004203BB /* UIView++.swift */; }; 31 | A1B4F71D234B027E004203BB /* Bundle++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4F71C234B027E004203BB /* Bundle++.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 1106CA481F01A0FA00434E58 /* Blurry.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Blurry.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 1106CA4B1F01A0FA00434E58 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 1106CA521F01A0FA00434E58 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 1106CA551F01A0FA00434E58 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | 1106CA571F01A0FA00434E58 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 11376D55206DB294006E15BA /* Blurry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Blurry.swift; sourceTree = ""; }; 41 | 115DDD92255A330D004F5FD5 /* MailComposerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposerButton.swift; sourceTree = ""; }; 42 | 115DDD96255A332D004F5FD5 /* MailComposerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposerView.swift; sourceTree = ""; }; 43 | 115DDD9A255A34E1004F5FD5 /* AcknowledgementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementView.swift; sourceTree = ""; }; 44 | 115DDD9E255A34F8004F5FD5 /* AboutCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutCell.swift; sourceTree = ""; }; 45 | 115DDDA2255A3513004F5FD5 /* AboutHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutHeaderView.swift; sourceTree = ""; }; 46 | 115DDDB0255A390B004F5FD5 /* CustomPaletteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPaletteView.swift; sourceTree = ""; }; 47 | 1165BC731F06E42900460749 /* UIImage++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage++.swift"; sourceTree = ""; }; 48 | 1165BC751F06E44200460749 /* BlurStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurStyle.swift; sourceTree = ""; }; 49 | 11850A622559B7DD005E0813 /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; 50 | 11850A662559B8F1005E0813 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 51 | 11B5317C2559B1CF00B79764 /* BlurryOld-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "BlurryOld-Info.plist"; path = "/Users/andyl/Projects/Apps/Blurry/Blurry/Supporting Files/BlurryOld-Info.plist"; sourceTree = ""; }; 52 | 11B91EBB206DEF160070454D /* CGSize++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize++.swift"; sourceTree = ""; }; 53 | 11F5AA5B1F01A89100FC1522 /* Blurry-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Blurry-Bridging-Header.h"; sourceTree = ""; }; 54 | 11F5AA5C1F01A89200FC1522 /* UIImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIImageEffects.h; sourceTree = ""; }; 55 | 11F5AA5D1F01A89200FC1522 /* UIImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImageEffects.m; sourceTree = ""; }; 56 | 11FAAF941F32B248007D212F /* Acknowledgement.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Acknowledgement.txt; sourceTree = ""; }; 57 | A189B20323666BCA001C5814 /* AboutSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSceneDelegate.swift; sourceTree = ""; }; 58 | A1B4F70D234ADB76004203BB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 59 | A1B4F70F234ADD4B004203BB /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; 60 | A1B4F715234AF641004203BB /* UIView++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView++.swift"; sourceTree = ""; }; 61 | A1B4F71C234B027E004203BB /* Bundle++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle++.swift"; sourceTree = ""; }; 62 | A1CB720B234BC0CB00654734 /* Blurry.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Blurry.entitlements; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 1106CA451F01A0FA00434E58 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 1106CA3F1F01A0FA00434E58 = { 77 | isa = PBXGroup; 78 | children = ( 79 | 1106CA4A1F01A0FA00434E58 /* Blurry */, 80 | 1106CA491F01A0FA00434E58 /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 1106CA491F01A0FA00434E58 /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 1106CA481F01A0FA00434E58 /* Blurry.app */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 1106CA4A1F01A0FA00434E58 /* Blurry */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | A1CB720B234BC0CB00654734 /* Blurry.entitlements */, 96 | 1106CA4B1F01A0FA00434E58 /* AppDelegate.swift */, 97 | A1B4F70D234ADB76004203BB /* SceneDelegate.swift */, 98 | A189B20323666BCA001C5814 /* AboutSceneDelegate.swift */, 99 | A1B4F713234AE2B0004203BB /* View */, 100 | A1B4F714234AEB3F004203BB /* Model */, 101 | A1B4F717234AF830004203BB /* Extensions */, 102 | 119D68E71F01B71B00426D85 /* UIImageEffects */, 103 | 119D68E81F01B72400426D85 /* Supporting Files */, 104 | ); 105 | path = Blurry; 106 | sourceTree = ""; 107 | }; 108 | 115DDD91255A3303004F5FD5 /* About */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 11850A662559B8F1005E0813 /* AboutView.swift */, 112 | 115DDDA2255A3513004F5FD5 /* AboutHeaderView.swift */, 113 | 115DDD9E255A34F8004F5FD5 /* AboutCell.swift */, 114 | 115DDD9A255A34E1004F5FD5 /* AcknowledgementView.swift */, 115 | 115DDD92255A330D004F5FD5 /* MailComposerButton.swift */, 116 | 115DDD96255A332D004F5FD5 /* MailComposerView.swift */, 117 | ); 118 | name = About; 119 | sourceTree = ""; 120 | }; 121 | 119D68E71F01B71B00426D85 /* UIImageEffects */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 11F5AA5B1F01A89100FC1522 /* Blurry-Bridging-Header.h */, 125 | 11F5AA5C1F01A89200FC1522 /* UIImageEffects.h */, 126 | 11F5AA5D1F01A89200FC1522 /* UIImageEffects.m */, 127 | ); 128 | path = UIImageEffects; 129 | sourceTree = ""; 130 | }; 131 | 119D68E81F01B72400426D85 /* Supporting Files */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 11B5317C2559B1CF00B79764 /* BlurryOld-Info.plist */, 135 | 11FAAF941F32B248007D212F /* Acknowledgement.txt */, 136 | 1106CA521F01A0FA00434E58 /* Assets.xcassets */, 137 | 1106CA541F01A0FA00434E58 /* LaunchScreen.storyboard */, 138 | 1106CA571F01A0FA00434E58 /* Info.plist */, 139 | ); 140 | path = "Supporting Files"; 141 | sourceTree = ""; 142 | }; 143 | A1B4F713234AE2B0004203BB /* View */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | A1B4F70F234ADD4B004203BB /* RootViewController.swift */, 147 | 11850A622559B7DD005E0813 /* NavigationController.swift */, 148 | 115DDDB0255A390B004F5FD5 /* CustomPaletteView.swift */, 149 | 115DDD91255A3303004F5FD5 /* About */, 150 | ); 151 | path = View; 152 | sourceTree = ""; 153 | }; 154 | A1B4F714234AEB3F004203BB /* Model */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 11376D55206DB294006E15BA /* Blurry.swift */, 158 | 1165BC751F06E44200460749 /* BlurStyle.swift */, 159 | ); 160 | path = Model; 161 | sourceTree = ""; 162 | }; 163 | A1B4F717234AF830004203BB /* Extensions */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | A1B4F715234AF641004203BB /* UIView++.swift */, 167 | 1165BC731F06E42900460749 /* UIImage++.swift */, 168 | 11B91EBB206DEF160070454D /* CGSize++.swift */, 169 | A1B4F71C234B027E004203BB /* Bundle++.swift */, 170 | ); 171 | path = Extensions; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 1106CA471F01A0FA00434E58 /* Blurry */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 1106CA701F01A0FA00434E58 /* Build configuration list for PBXNativeTarget "Blurry" */; 180 | buildPhases = ( 181 | 1106CA441F01A0FA00434E58 /* Sources */, 182 | 1106CA451F01A0FA00434E58 /* Frameworks */, 183 | 1106CA461F01A0FA00434E58 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = Blurry; 190 | productName = Blurr; 191 | productReference = 1106CA481F01A0FA00434E58 /* Blurry.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | 1106CA401F01A0FA00434E58 /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | LastSwiftUpdateCheck = 0900; 201 | LastUpgradeCheck = 1220; 202 | ORGANIZATIONNAME = "Andy Liang"; 203 | TargetAttributes = { 204 | 1106CA471F01A0FA00434E58 = { 205 | CreatedOnToolsVersion = 9.0; 206 | LastSwiftMigration = 1100; 207 | }; 208 | }; 209 | }; 210 | buildConfigurationList = 1106CA431F01A0FA00434E58 /* Build configuration list for PBXProject "Blurry" */; 211 | compatibilityVersion = "Xcode 8.0"; 212 | developmentRegion = en; 213 | hasScannedForEncodings = 0; 214 | knownRegions = ( 215 | en, 216 | Base, 217 | ); 218 | mainGroup = 1106CA3F1F01A0FA00434E58; 219 | productRefGroup = 1106CA491F01A0FA00434E58 /* Products */; 220 | projectDirPath = ""; 221 | projectRoot = ""; 222 | targets = ( 223 | 1106CA471F01A0FA00434E58 /* Blurry */, 224 | ); 225 | }; 226 | /* End PBXProject section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 1106CA461F01A0FA00434E58 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 11FAAF951F32B248007D212F /* Acknowledgement.txt in Resources */, 234 | 1106CA561F01A0FA00434E58 /* LaunchScreen.storyboard in Resources */, 235 | 1106CA531F01A0FA00434E58 /* Assets.xcassets in Resources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXResourcesBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | 1106CA441F01A0FA00434E58 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 115DDD9B255A34E1004F5FD5 /* AcknowledgementView.swift in Sources */, 247 | A1B4F716234AF641004203BB /* UIView++.swift in Sources */, 248 | 11B91EBC206DEF160070454D /* CGSize++.swift in Sources */, 249 | 11F5AA5E1F01A89200FC1522 /* UIImageEffects.m in Sources */, 250 | 115DDD93255A330D004F5FD5 /* MailComposerButton.swift in Sources */, 251 | A1B4F710234ADD4B004203BB /* RootViewController.swift in Sources */, 252 | 11850A672559B8F1005E0813 /* AboutView.swift in Sources */, 253 | 11850A632559B7DD005E0813 /* NavigationController.swift in Sources */, 254 | A189B20423666BCA001C5814 /* AboutSceneDelegate.swift in Sources */, 255 | 1165BC761F06E44200460749 /* BlurStyle.swift in Sources */, 256 | A1B4F71D234B027E004203BB /* Bundle++.swift in Sources */, 257 | A1B4F70E234ADB76004203BB /* SceneDelegate.swift in Sources */, 258 | 1165BC741F06E42900460749 /* UIImage++.swift in Sources */, 259 | 115DDDA3255A3513004F5FD5 /* AboutHeaderView.swift in Sources */, 260 | 115DDD97255A332D004F5FD5 /* MailComposerView.swift in Sources */, 261 | 115DDD9F255A34F8004F5FD5 /* AboutCell.swift in Sources */, 262 | 1106CA4C1F01A0FA00434E58 /* AppDelegate.swift in Sources */, 263 | 115DDDB1255A390B004F5FD5 /* CustomPaletteView.swift in Sources */, 264 | 11376D56206DB294006E15BA /* Blurry.swift in Sources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXSourcesBuildPhase section */ 269 | 270 | /* Begin PBXVariantGroup section */ 271 | 1106CA541F01A0FA00434E58 /* LaunchScreen.storyboard */ = { 272 | isa = PBXVariantGroup; 273 | children = ( 274 | 1106CA551F01A0FA00434E58 /* Base */, 275 | ); 276 | name = LaunchScreen.storyboard; 277 | path = ..; 278 | sourceTree = ""; 279 | }; 280 | /* End PBXVariantGroup section */ 281 | 282 | /* Begin XCBuildConfiguration section */ 283 | 1106CA6E1F01A0FA00434E58 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_ANALYZER_NONNULL = YES; 288 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 290 | CLANG_CXX_LIBRARY = "libc++"; 291 | CLANG_ENABLE_MODULES = YES; 292 | CLANG_ENABLE_OBJC_ARC = YES; 293 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_COMMA = YES; 296 | CLANG_WARN_CONSTANT_CONVERSION = YES; 297 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 300 | CLANG_WARN_EMPTY_BODY = YES; 301 | CLANG_WARN_ENUM_CONVERSION = YES; 302 | CLANG_WARN_INFINITE_RECURSION = YES; 303 | CLANG_WARN_INT_CONVERSION = YES; 304 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 305 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 306 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 309 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 310 | CLANG_WARN_STRICT_PROTOTYPES = YES; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | CODE_SIGN_IDENTITY = "iPhone Developer"; 316 | COPY_PHASE_STRIP = NO; 317 | DEBUG_INFORMATION_FORMAT = dwarf; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | ENABLE_TESTABILITY = YES; 320 | GCC_C_LANGUAGE_STANDARD = gnu11; 321 | GCC_DYNAMIC_NO_PIC = NO; 322 | GCC_NO_COMMON_BLOCKS = YES; 323 | GCC_OPTIMIZATION_LEVEL = 0; 324 | GCC_PREPROCESSOR_DEFINITIONS = ( 325 | "DEBUG=1", 326 | "$(inherited)", 327 | ); 328 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 329 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 330 | GCC_WARN_UNDECLARED_SELECTOR = YES; 331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 332 | GCC_WARN_UNUSED_FUNCTION = YES; 333 | GCC_WARN_UNUSED_VARIABLE = YES; 334 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 335 | MTL_ENABLE_DEBUG_INFO = YES; 336 | ONLY_ACTIVE_ARCH = YES; 337 | SDKROOT = iphoneos; 338 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 339 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 340 | }; 341 | name = Debug; 342 | }; 343 | 1106CA6F1F01A0FA00434E58 /* Release */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ALWAYS_SEARCH_USER_PATHS = NO; 347 | CLANG_ANALYZER_NONNULL = YES; 348 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 349 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 350 | CLANG_CXX_LIBRARY = "libc++"; 351 | CLANG_ENABLE_MODULES = YES; 352 | CLANG_ENABLE_OBJC_ARC = YES; 353 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_COMMA = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 358 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 359 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INFINITE_RECURSION = YES; 363 | CLANG_WARN_INT_CONVERSION = YES; 364 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 365 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 366 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 368 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 369 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 370 | CLANG_WARN_STRICT_PROTOTYPES = YES; 371 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 372 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 373 | CLANG_WARN_UNREACHABLE_CODE = YES; 374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 375 | CODE_SIGN_IDENTITY = "iPhone Developer"; 376 | COPY_PHASE_STRIP = NO; 377 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 378 | ENABLE_NS_ASSERTIONS = NO; 379 | ENABLE_STRICT_OBJC_MSGSEND = YES; 380 | GCC_C_LANGUAGE_STANDARD = gnu11; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 389 | MTL_ENABLE_DEBUG_INFO = NO; 390 | SDKROOT = iphoneos; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 392 | VALIDATE_PRODUCT = YES; 393 | }; 394 | name = Release; 395 | }; 396 | 1106CA711F01A0FA00434E58 /* Debug */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 400 | CLANG_ENABLE_MODULES = YES; 401 | CODE_SIGN_ENTITLEMENTS = Blurry/Blurry.entitlements; 402 | CURRENT_PROJECT_VERSION = 19; 403 | DEVELOPMENT_TEAM = TSCXJ4KWQ4; 404 | INFOPLIST_FILE = "$(SRCROOT)/Blurry/Supporting Files/Info.plist"; 405 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 406 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 408 | MARKETING_VERSION = 1.6.0; 409 | PRODUCT_BUNDLE_IDENTIFIER = com.andyliang.Blurry; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | SUPPORTS_MACCATALYST = YES; 412 | SWIFT_OBJC_BRIDGING_HEADER = "Blurry/UIImageEffects/Blurry-Bridging-Header.h"; 413 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 414 | SWIFT_VERSION = 5.0; 415 | TARGETED_DEVICE_FAMILY = "1,2,6"; 416 | }; 417 | name = Debug; 418 | }; 419 | 1106CA721F01A0FA00434E58 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 423 | CLANG_ENABLE_MODULES = YES; 424 | CODE_SIGN_ENTITLEMENTS = Blurry/Blurry.entitlements; 425 | CURRENT_PROJECT_VERSION = 19; 426 | DEVELOPMENT_TEAM = TSCXJ4KWQ4; 427 | INFOPLIST_FILE = "$(SRCROOT)/Blurry/Supporting Files/Info.plist"; 428 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 429 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | MARKETING_VERSION = 1.6.0; 432 | PRODUCT_BUNDLE_IDENTIFIER = com.andyliang.Blurry; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | SUPPORTS_MACCATALYST = YES; 435 | SWIFT_OBJC_BRIDGING_HEADER = "Blurry/UIImageEffects/Blurry-Bridging-Header.h"; 436 | SWIFT_VERSION = 5.0; 437 | TARGETED_DEVICE_FAMILY = "1,2,6"; 438 | }; 439 | name = Release; 440 | }; 441 | /* End XCBuildConfiguration section */ 442 | 443 | /* Begin XCConfigurationList section */ 444 | 1106CA431F01A0FA00434E58 /* Build configuration list for PBXProject "Blurry" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | 1106CA6E1F01A0FA00434E58 /* Debug */, 448 | 1106CA6F1F01A0FA00434E58 /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | 1106CA701F01A0FA00434E58 /* Build configuration list for PBXNativeTarget "Blurry" */ = { 454 | isa = XCConfigurationList; 455 | buildConfigurations = ( 456 | 1106CA711F01A0FA00434E58 /* Debug */, 457 | 1106CA721F01A0FA00434E58 /* Release */, 458 | ); 459 | defaultConfigurationIsVisible = 0; 460 | defaultConfigurationName = Release; 461 | }; 462 | /* End XCConfigurationList section */ 463 | }; 464 | rootObject = 1106CA401F01A0FA00434E58 /* Project object */; 465 | } 466 | --------------------------------------------------------------------------------