├── 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 |
--------------------------------------------------------------------------------