├── .DS_Store ├── .gitignore ├── PhotoApp.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── WorkspaceSettings.xcsettings ├── PhotoApp ├── .DS_Store ├── AppDelegate.swift ├── AppIcon.appiconset │ └── Contents.json ├── Assets.xcassets │ ├── .DS_Store │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── PhotoAppMainLogo.png │ ├── CheckMark.imageset │ │ ├── Contents.json │ │ └── icons8-Checkmark Filled-500.png │ ├── CheckMarkColored.imageset │ │ ├── Contents.json │ │ └── icons8-Checkmark Filled-500.png │ ├── Contents.json │ ├── CrossMark.imageset │ │ ├── Contents.json │ │ └── icons8-Delete Filled-500.png │ ├── CrossMarkColored.imageset │ │ ├── Contents.json │ │ └── icons8-Delete Filled-500.png │ ├── flipHorizontal.imageset │ │ ├── Contents.json │ │ └── flip_vertical1600.png │ ├── flipVertical.imageset │ │ ├── Contents.json │ │ └── flip_vertical1600.png │ ├── glassTexture.imageset │ │ ├── Contents.json │ │ └── Frosted-Sand-Window-Texture.jpg │ ├── gradient.imageset │ │ ├── Contents.json │ │ └── photoshop-foreground-background-gradient.jpg │ ├── logo.imageset │ │ ├── Contents.json │ │ └── Olympic-logo.png │ ├── photo.imageset │ │ ├── Contents.json │ │ └── photo.jpg │ ├── rotate-512.imageset │ │ ├── Contents.json │ │ └── rotate-512.png │ ├── scissors1600.imageset │ │ ├── Contents.json │ │ └── scissors1600.png │ └── top.imageset │ │ ├── Contents.json │ │ └── top.jpg ├── Base.lproj │ └── Main.storyboard ├── CenteredClipView.swift ├── CustomImageView.swift ├── Document.swift ├── Info.plist ├── PhotoApp.entitlements ├── PopoverViewController.swift ├── ViewController.swift └── WindowController.swift ├── PhotoAppArchive.zip ├── README.markdown └── Resources └── screenshot.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /PhotoApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4C035A7D1EF72FE400DE6F5F /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C035A7C1EF72FE400DE6F5F /* WindowController.swift */; }; 11 | 4C098AAA1F399D0B000BE2E1 /* CustomImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFC5C491F00FB5000E8EBEE /* CustomImageView.swift */; }; 12 | 4CBCA73F1EF15A9B006DB385 /* CenteredClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA73E1EF15A9B006DB385 /* CenteredClipView.swift */; }; 13 | 4CBCA7411EF15C56006DB385 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA7401EF15C56006DB385 /* Document.swift */; }; 14 | 4CD980D81F1ED58500D3E5BF /* PopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD980D71F1ED58500D3E5BF /* PopoverViewController.swift */; }; 15 | 4CFF372F1EEFBA540009E8D8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF372E1EEFBA540009E8D8 /* AppDelegate.swift */; }; 16 | 4CFF37311EEFBA540009E8D8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF37301EEFBA540009E8D8 /* ViewController.swift */; }; 17 | 4CFF37331EEFBA540009E8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CFF37321EEFBA540009E8D8 /* Assets.xcassets */; }; 18 | 4CFF37361EEFBA540009E8D8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CFF37341EEFBA540009E8D8 /* Main.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 4C035A7C1EF72FE400DE6F5F /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; usesTabs = 1; }; 23 | 4CBCA73E1EF15A9B006DB385 /* CenteredClipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenteredClipView.swift; sourceTree = ""; }; 24 | 4CBCA7401EF15C56006DB385 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 25 | 4CD980D71F1ED58500D3E5BF /* PopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverViewController.swift; sourceTree = ""; }; 26 | 4CFC5C491F00FB5000E8EBEE /* CustomImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomImageView.swift; sourceTree = ""; }; 27 | 4CFF372B1EEFBA540009E8D8 /* PhotoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 4CFF372E1EEFBA540009E8D8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | 4CFF37301EEFBA540009E8D8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 30 | 4CFF37321EEFBA540009E8D8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | 4CFF37351EEFBA540009E8D8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32 | 4CFF37371EEFBA540009E8D8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 4CFF37381EEFBA540009E8D8 /* PhotoApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PhotoApp.entitlements; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 4CFF37281EEFBA540009E8D8 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 4CFF37221EEFBA540009E8D8 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 4CFF372D1EEFBA540009E8D8 /* PhotoApp */, 51 | 4CFF372C1EEFBA540009E8D8 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 4CFF372C1EEFBA540009E8D8 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 4CFF372B1EEFBA540009E8D8 /* PhotoApp.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 4CFF372D1EEFBA540009E8D8 /* PhotoApp */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 4CFF372E1EEFBA540009E8D8 /* AppDelegate.swift */, 67 | 4CFF37301EEFBA540009E8D8 /* ViewController.swift */, 68 | 4CBCA73E1EF15A9B006DB385 /* CenteredClipView.swift */, 69 | 4CBCA7401EF15C56006DB385 /* Document.swift */, 70 | 4CD980D71F1ED58500D3E5BF /* PopoverViewController.swift */, 71 | 4C035A7C1EF72FE400DE6F5F /* WindowController.swift */, 72 | 4CFC5C491F00FB5000E8EBEE /* CustomImageView.swift */, 73 | 4CFF37321EEFBA540009E8D8 /* Assets.xcassets */, 74 | 4CFF37341EEFBA540009E8D8 /* Main.storyboard */, 75 | 4CFF37371EEFBA540009E8D8 /* Info.plist */, 76 | 4CFF37381EEFBA540009E8D8 /* PhotoApp.entitlements */, 77 | ); 78 | path = PhotoApp; 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXNativeTarget section */ 84 | 4CFF372A1EEFBA540009E8D8 /* PhotoApp */ = { 85 | isa = PBXNativeTarget; 86 | buildConfigurationList = 4CFF373B1EEFBA540009E8D8 /* Build configuration list for PBXNativeTarget "PhotoApp" */; 87 | buildPhases = ( 88 | 4CFF37271EEFBA540009E8D8 /* Sources */, 89 | 4CFF37281EEFBA540009E8D8 /* Frameworks */, 90 | 4CFF37291EEFBA540009E8D8 /* Resources */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = PhotoApp; 97 | productName = PhotoApp; 98 | productReference = 4CFF372B1EEFBA540009E8D8 /* PhotoApp.app */; 99 | productType = "com.apple.product-type.application"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | 4CFF37231EEFBA540009E8D8 /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastSwiftUpdateCheck = 0900; 108 | LastUpgradeCheck = 0900; 109 | ORGANIZATIONNAME = Pixelmator; 110 | TargetAttributes = { 111 | 4CFF372A1EEFBA540009E8D8 = { 112 | CreatedOnToolsVersion = 9.0; 113 | SystemCapabilities = { 114 | com.apple.Sandbox = { 115 | enabled = 0; 116 | }; 117 | }; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 4CFF37261EEFBA540009E8D8 /* Build configuration list for PBXProject "PhotoApp" */; 122 | compatibilityVersion = "Xcode 8.0"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 4CFF37221EEFBA540009E8D8; 130 | productRefGroup = 4CFF372C1EEFBA540009E8D8 /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 4CFF372A1EEFBA540009E8D8 /* PhotoApp */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 4CFF37291EEFBA540009E8D8 /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 4CFF37331EEFBA540009E8D8 /* Assets.xcassets in Resources */, 145 | 4CFF37361EEFBA540009E8D8 /* Main.storyboard in Resources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXResourcesBuildPhase section */ 150 | 151 | /* Begin PBXSourcesBuildPhase section */ 152 | 4CFF37271EEFBA540009E8D8 /* Sources */ = { 153 | isa = PBXSourcesBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | 4CBCA7411EF15C56006DB385 /* Document.swift in Sources */, 157 | 4CD980D81F1ED58500D3E5BF /* PopoverViewController.swift in Sources */, 158 | 4C035A7D1EF72FE400DE6F5F /* WindowController.swift in Sources */, 159 | 4C098AAA1F399D0B000BE2E1 /* CustomImageView.swift in Sources */, 160 | 4CFF37311EEFBA540009E8D8 /* ViewController.swift in Sources */, 161 | 4CFF372F1EEFBA540009E8D8 /* AppDelegate.swift in Sources */, 162 | 4CBCA73F1EF15A9B006DB385 /* CenteredClipView.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin PBXVariantGroup section */ 169 | 4CFF37341EEFBA540009E8D8 /* Main.storyboard */ = { 170 | isa = PBXVariantGroup; 171 | children = ( 172 | 4CFF37351EEFBA540009E8D8 /* Base */, 173 | ); 174 | name = Main.storyboard; 175 | sourceTree = ""; 176 | }; 177 | /* End PBXVariantGroup section */ 178 | 179 | /* Begin XCBuildConfiguration section */ 180 | 4CFF37391EEFBA540009E8D8 /* Debug */ = { 181 | isa = XCBuildConfiguration; 182 | buildSettings = { 183 | ALWAYS_SEARCH_USER_PATHS = NO; 184 | CLANG_ANALYZER_NONNULL = YES; 185 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 186 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 187 | CLANG_CXX_LIBRARY = "libc++"; 188 | CLANG_ENABLE_MODULES = YES; 189 | CLANG_ENABLE_OBJC_ARC = YES; 190 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_COMMA = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INFINITE_RECURSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 201 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 202 | CLANG_WARN_STRICT_PROTOTYPES = YES; 203 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 204 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 205 | CLANG_WARN_UNREACHABLE_CODE = YES; 206 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 207 | CODE_SIGN_IDENTITY = "Mac Developer"; 208 | COPY_PHASE_STRIP = NO; 209 | DEBUG_INFORMATION_FORMAT = dwarf; 210 | ENABLE_STRICT_OBJC_MSGSEND = YES; 211 | ENABLE_TESTABILITY = YES; 212 | GCC_C_LANGUAGE_STANDARD = gnu11; 213 | GCC_DYNAMIC_NO_PIC = NO; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_OPTIMIZATION_LEVEL = 0; 216 | GCC_PREPROCESSOR_DEFINITIONS = ( 217 | "DEBUG=1", 218 | "$(inherited)", 219 | ); 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 222 | GCC_WARN_UNDECLARED_SELECTOR = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 224 | GCC_WARN_UNUSED_FUNCTION = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | MACOSX_DEPLOYMENT_TARGET = 10.13; 227 | MTL_ENABLE_DEBUG_INFO = YES; 228 | ONLY_ACTIVE_ARCH = YES; 229 | SDKROOT = macosx; 230 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 231 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 232 | }; 233 | name = Debug; 234 | }; 235 | 4CFF373A1EEFBA540009E8D8 /* Release */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_ANALYZER_NONNULL = YES; 240 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 256 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 257 | CLANG_WARN_STRICT_PROTOTYPES = YES; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | CODE_SIGN_IDENTITY = "Mac Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu11; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | MACOSX_DEPLOYMENT_TARGET = 10.13; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = macosx; 278 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 279 | }; 280 | name = Release; 281 | }; 282 | 4CFF373C1EEFBA540009E8D8 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 286 | CODE_SIGN_IDENTITY = ""; 287 | COMBINE_HIDPI_IMAGES = YES; 288 | DEVELOPMENT_TEAM = ""; 289 | INFOPLIST_FILE = PhotoApp/Info.plist; 290 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 291 | PRODUCT_BUNDLE_IDENTIFIER = com.sereika.augustas.PhotoApp; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_VERSION = 4.0; 294 | }; 295 | name = Debug; 296 | }; 297 | 4CFF373D1EEFBA540009E8D8 /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 301 | CODE_SIGN_IDENTITY = ""; 302 | COMBINE_HIDPI_IMAGES = YES; 303 | DEVELOPMENT_TEAM = ""; 304 | INFOPLIST_FILE = PhotoApp/Info.plist; 305 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 306 | PRODUCT_BUNDLE_IDENTIFIER = com.sereika.augustas.PhotoApp; 307 | PRODUCT_NAME = "$(TARGET_NAME)"; 308 | SWIFT_VERSION = 4.0; 309 | }; 310 | name = Release; 311 | }; 312 | /* End XCBuildConfiguration section */ 313 | 314 | /* Begin XCConfigurationList section */ 315 | 4CFF37261EEFBA540009E8D8 /* Build configuration list for PBXProject "PhotoApp" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | 4CFF37391EEFBA540009E8D8 /* Debug */, 319 | 4CFF373A1EEFBA540009E8D8 /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | 4CFF373B1EEFBA540009E8D8 /* Build configuration list for PBXNativeTarget "PhotoApp" */ = { 325 | isa = XCConfigurationList; 326 | buildConfigurations = ( 327 | 4CFF373C1EEFBA540009E8D8 /* Debug */, 328 | 4CFF373D1EEFBA540009E8D8 /* Release */, 329 | ); 330 | defaultConfigurationIsVisible = 0; 331 | defaultConfigurationName = Release; 332 | }; 333 | /* End XCConfigurationList section */ 334 | }; 335 | rootObject = 4CFF37231EEFBA540009E8D8 /* Project object */; 336 | } 337 | -------------------------------------------------------------------------------- /PhotoApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoApp.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PhotoApp/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/.DS_Store -------------------------------------------------------------------------------- /PhotoApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PhotoApp 4 | // 5 | // Created by Pixelmator on 6/12/17. 6 | // Copyright © 2017 Pixelmator. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | weak var windowController: WindowController? = nil 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { 25 | return false 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /PhotoApp/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images":[ 3 | { 4 | "idiom":"iphone", 5 | "size":"20x20", 6 | "scale":"2x", 7 | "filename":"Icon-App-20x20@2x.png" 8 | }, 9 | { 10 | "idiom":"iphone", 11 | "size":"20x20", 12 | "scale":"3x", 13 | "filename":"Icon-App-20x20@3x.png" 14 | }, 15 | { 16 | "idiom":"iphone", 17 | "size":"29x29", 18 | "scale":"1x", 19 | "filename":"Icon-App-29x29@1x.png" 20 | }, 21 | { 22 | "idiom":"iphone", 23 | "size":"29x29", 24 | "scale":"2x", 25 | "filename":"Icon-App-29x29@2x.png" 26 | }, 27 | { 28 | "idiom":"iphone", 29 | "size":"29x29", 30 | "scale":"3x", 31 | "filename":"Icon-App-29x29@3x.png" 32 | }, 33 | { 34 | "idiom":"iphone", 35 | "size":"40x40", 36 | "scale":"1x", 37 | "filename":"Icon-App-40x40@1x.png" 38 | }, 39 | { 40 | "idiom":"iphone", 41 | "size":"40x40", 42 | "scale":"2x", 43 | "filename":"Icon-App-40x40@2x.png" 44 | }, 45 | { 46 | "idiom":"iphone", 47 | "size":"40x40", 48 | "scale":"3x", 49 | "filename":"Icon-App-40x40@3x.png" 50 | }, 51 | { 52 | "idiom":"iphone", 53 | "size":"57x57", 54 | "scale":"1x", 55 | "filename":"Icon-App-57x57@1x.png" 56 | }, 57 | { 58 | "idiom":"iphone", 59 | "size":"57x57", 60 | "scale":"2x", 61 | "filename":"Icon-App-57x57@2x.png" 62 | }, 63 | { 64 | "idiom":"iphone", 65 | "size":"60x60", 66 | "scale":"1x", 67 | "filename":"Icon-App-60x60@1x.png" 68 | }, 69 | { 70 | "idiom":"iphone", 71 | "size":"60x60", 72 | "scale":"2x", 73 | "filename":"Icon-App-60x60@2x.png" 74 | }, 75 | { 76 | "idiom":"iphone", 77 | "size":"60x60", 78 | "scale":"3x", 79 | "filename":"Icon-App-60x60@3x.png" 80 | }, 81 | { 82 | "idiom":"iphone", 83 | "size":"76x76", 84 | "scale":"1x", 85 | "filename":"Icon-App-76x76@1x.png" 86 | }, 87 | { 88 | "idiom":"ipad", 89 | "size":"20x20", 90 | "scale":"1x", 91 | "filename":"Icon-App-20x20@1x.png" 92 | }, 93 | { 94 | "idiom":"ipad", 95 | "size":"20x20", 96 | "scale":"2x", 97 | "filename":"Icon-App-20x20@2x.png" 98 | }, 99 | { 100 | "idiom":"ipad", 101 | "size":"29x29", 102 | "scale":"1x", 103 | "filename":"Icon-App-29x29@1x.png" 104 | }, 105 | { 106 | "idiom":"ipad", 107 | "size":"29x29", 108 | "scale":"2x", 109 | "filename":"Icon-App-29x29@2x.png" 110 | }, 111 | { 112 | "idiom":"ipad", 113 | "size":"40x40", 114 | "scale":"1x", 115 | "filename":"Icon-App-40x40@1x.png" 116 | }, 117 | { 118 | "idiom":"ipad", 119 | "size":"40x40", 120 | "scale":"2x", 121 | "filename":"Icon-App-40x40@2x.png" 122 | }, 123 | { 124 | "size" : "50x50", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-Small-50x50@1x.png", 127 | "scale" : "1x" 128 | }, 129 | { 130 | "size" : "50x50", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-Small-50x50@2x.png", 133 | "scale" : "2x" 134 | }, 135 | { 136 | "idiom":"ipad", 137 | "size":"72x72", 138 | "scale":"1x", 139 | "filename":"Icon-App-72x72@1x.png" 140 | }, 141 | { 142 | "idiom":"ipad", 143 | "size":"72x72", 144 | "scale":"2x", 145 | "filename":"Icon-App-72x72@2x.png" 146 | }, 147 | { 148 | "idiom":"ipad", 149 | "size":"76x76", 150 | "scale":"1x", 151 | "filename":"Icon-App-76x76@1x.png" 152 | }, 153 | { 154 | "idiom":"ipad", 155 | "size":"76x76", 156 | "scale":"2x", 157 | "filename":"Icon-App-76x76@2x.png" 158 | }, 159 | { 160 | "idiom":"ipad", 161 | "size":"76x76", 162 | "scale":"3x", 163 | "filename":"Icon-App-76x76@3x.png" 164 | }, 165 | { 166 | "idiom":"ipad", 167 | "size":"83.5x83.5", 168 | "scale":"2x", 169 | "filename":"Icon-App-83.5x83.5@2x.png" 170 | } 171 | ], 172 | "info":{ 173 | "version":1, 174 | "author":"makeappicon" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "size" : "512x512", 50 | "idiom" : "mac", 51 | "filename" : "PhotoAppMainLogo.png", 52 | "scale" : "2x" 53 | } 54 | ], 55 | "info" : { 56 | "version" : 1, 57 | "author" : "xcode" 58 | } 59 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/AppIcon.appiconset/PhotoAppMainLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/AppIcon.appiconset/PhotoAppMainLogo.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CheckMark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-Checkmark Filled-500.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CheckMark.imageset/icons8-Checkmark Filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/CheckMark.imageset/icons8-Checkmark Filled-500.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CheckMarkColored.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-Checkmark Filled-500.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CheckMarkColored.imageset/icons8-Checkmark Filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/CheckMarkColored.imageset/icons8-Checkmark Filled-500.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CrossMark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-Delete Filled-500.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CrossMark.imageset/icons8-Delete Filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/CrossMark.imageset/icons8-Delete Filled-500.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CrossMarkColored.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-Delete Filled-500.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/CrossMarkColored.imageset/icons8-Delete Filled-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/CrossMarkColored.imageset/icons8-Delete Filled-500.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/flipHorizontal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "flip_vertical1600.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/flipHorizontal.imageset/flip_vertical1600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/flipHorizontal.imageset/flip_vertical1600.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/flipVertical.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "flip_vertical1600.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/flipVertical.imageset/flip_vertical1600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/flipVertical.imageset/flip_vertical1600.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/glassTexture.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Frosted-Sand-Window-Texture.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/glassTexture.imageset/Frosted-Sand-Window-Texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/glassTexture.imageset/Frosted-Sand-Window-Texture.jpg -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/gradient.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photoshop-foreground-background-gradient.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/gradient.imageset/photoshop-foreground-background-gradient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/gradient.imageset/photoshop-foreground-background-gradient.jpg -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Olympic-logo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/logo.imageset/Olympic-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/logo.imageset/Olympic-logo.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/photo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/photo.imageset/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/photo.imageset/photo.jpg -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/rotate-512.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "rotate-512.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/rotate-512.imageset/rotate-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/rotate-512.imageset/rotate-512.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/scissors1600.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "scissors1600.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/scissors1600.imageset/scissors1600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/scissors1600.imageset/scissors1600.png -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/top.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "top.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoApp/Assets.xcassets/top.imageset/top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoApp/Assets.xcassets/top.imageset/top.jpg -------------------------------------------------------------------------------- /PhotoApp/CenteredClipView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CenteredClipView.swift 3 | // PhotoApp 4 | // 5 | // Created by Pixelmator on 6/14/17. 6 | // Copyright © 2017 Pixelmator. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CenteredClipView: NSClipView { 12 | override func constrainBoundsRect(_ proposedBounds: NSRect) -> NSRect { 13 | var rect = super.constrainBoundsRect(proposedBounds) 14 | if let containerView = documentView { 15 | if (rect.size.width > containerView.frame.size.width) { 16 | rect.origin.x = (containerView.frame.width - rect.width) / 2 17 | } 18 | if (rect.size.height > containerView.frame.size.height) { 19 | rect.origin.y = (containerView.frame.height - rect.height) / 2 20 | } 21 | } 22 | // NSLog("\(proposedBounds) proposed bounds") 23 | // NSLog("\(rect) rect") 24 | return rect 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PhotoApp/CustomImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomImageView.swift 3 | // PhotoApp 4 | // 5 | // Created by Pixelmator on 6/26/17. 6 | // Copyright © 2017 Pixelmator. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import MetalKit 11 | 12 | class CustomImageView: NSView { 13 | 14 | var windowController: WindowController? = nil 15 | 16 | var image: CIImage? = nil 17 | 18 | 19 | var cropRect: CGRect = CGRect.zero {didSet{needsDisplay = true}} 20 | var mouseLocationDragStart: CGPoint = .zero 21 | var mouseLocationDragFinish: CGPoint = .zero 22 | var theOrigin: CGPoint = .zero 23 | var theSize: CGSize = CGSize(width: 1.0, height: 1.0) 24 | var initialRotationAngle: CGFloat = 0.0 25 | var finalRotationAngle: CGFloat = 0.0 26 | 27 | 28 | override func mouseDown(with event: NSEvent) { 29 | super.mouseDown(with: event) 30 | 31 | 32 | if let indexOfButton = windowController?.geometryControls.indexOfSelectedItem { 33 | 34 | image = (windowController?.contentViewController as! ViewController).image 35 | switch indexOfButton { 36 | case 0: 37 | if let view = window?.contentView, 38 | let scrollView = view.subviews.first as? NSScrollView, 39 | let clipView = scrollView.subviews.first as? CenteredClipView, 40 | let metalView = clipView.subviews.first as? MTKView, 41 | let customView = metalView.subviews.first as? CustomImageView { 42 | self.cropRect.origin = self.window!.contentView!.convert(event.locationInWindow, to: self) 43 | } 44 | self.theOrigin = transformCoordinate(from: event.locationInWindow) 45 | self.mouseLocationDragStart = self.theOrigin 46 | case 1: 47 | self.mouseLocationDragStart = transformCoordinate(from: event.locationInWindow) 48 | 49 | if let image = self.image { 50 | self.initialRotationAngle = pointToAngle(from: self.mouseLocationDragStart, imageSize: image.extent.size) 51 | } 52 | 53 | default: 54 | NSLog("mouseDown default statement") 55 | } 56 | } 57 | } 58 | 59 | override func mouseDragged(with event: NSEvent) { 60 | super.mouseDragged(with: event) 61 | let mouseLocation = self.window!.contentView!.convert(event.locationInWindow, to: self) 62 | 63 | if let indexOfButton = windowController?.geometryControls.selectedSegment, 64 | (windowController?.geometryControls.isSelected(forSegment: indexOfButton))! { 65 | switch indexOfButton { 66 | case 0: 67 | cropRect.size = CGSize(width: mouseLocation.x - cropRect.origin.x, height: mouseLocation.y - cropRect.origin.y) 68 | case 1: 69 | finalRotationAngle = pointToAngle(from: transformCoordinate(from: event.locationInWindow), imageSize: image!.extent.size) 70 | self.windowController?.updateRotation(with: finalRotationAngle - initialRotationAngle) 71 | default: 72 | _ = 4 73 | } 74 | } 75 | } 76 | 77 | override func mouseUp(with event: NSEvent) { 78 | super.mouseUp(with: event) 79 | if let indexOfButton = windowController?.geometryControls.selectedSegment, 80 | (windowController?.geometryControls.isSelected(forSegment: indexOfButton))! { 81 | switch indexOfButton { 82 | case 0: 83 | self.mouseLocationDragFinish = transformCoordinate(from: event.locationInWindow) 84 | theSize.width = abs(mouseLocationDragStart.x - mouseLocationDragFinish.x) 85 | theSize.height = abs(mouseLocationDragStart.y - mouseLocationDragFinish.y) 86 | if mouseLocationDragStart.x > mouseLocationDragFinish.x { 87 | theOrigin.x = mouseLocationDragFinish.x 88 | } 89 | if mouseLocationDragStart.y > mouseLocationDragFinish.y { 90 | theOrigin.y = mouseLocationDragFinish.y 91 | } 92 | 93 | self.windowController?.updateCrop(with: CGRect(origin: self.theOrigin, size: self.theSize)) 94 | windowController?.geometryControls.setSelected(false, forSegment: indexOfButton) 95 | self.cropRect = .zero 96 | 97 | case 1: 98 | self.mouseLocationDragFinish = transformCoordinate(from: event.locationInWindow) 99 | 100 | if let image = image { 101 | self.finalRotationAngle = pointToAngle(from: self.mouseLocationDragFinish, imageSize: image.extent.size) 102 | } 103 | self.windowController?.updateRotation(with: finalRotationAngle - initialRotationAngle) 104 | windowController?.geometryControls.setSelected(false, forSegment: indexOfButton) 105 | default: 106 | NSLog("mouseUp default statement") 107 | } 108 | } 109 | } 110 | 111 | func transformCoordinate(from origin: CGPoint) -> CGPoint { 112 | var newOrigin = self.convert(origin, from: window?.contentView) 113 | newOrigin.x = newOrigin.x * ((self.image?.extent.size.width)! / (window?.contentView?.frame.size.width)!) 114 | newOrigin.y = newOrigin.y * ((self.image?.extent.size.height)! / (window?.contentView?.frame.size.height)!) 115 | 116 | let imageAspectRatio = (image?.extent.size.width)! / (image?.extent.size.height)! 117 | let boundsAspectRatio = (window?.contentView?.frame.size.width)! / (window?.contentView?.frame.size.height)! 118 | var actualX: CGFloat = 0.0 119 | var actualY: CGFloat = 0.0 120 | 121 | if let image = image { 122 | if imageAspectRatio < boundsAspectRatio { 123 | actualX = newOrigin.x * ((window?.contentView?.frame.size.width)! / ((window?.contentView?.frame.size.height)! * imageAspectRatio)) - (((image.extent.size.height * boundsAspectRatio) - image.extent.size.width) / 2) 124 | actualY = newOrigin.y 125 | } else { 126 | actualY = newOrigin.y * ((window?.contentView?.frame.size.height)! / ((window?.contentView?.frame.size.width)! / imageAspectRatio)) - (((image.extent.size.width / boundsAspectRatio) - image.extent.size.height) / 2) 127 | actualX = newOrigin.x 128 | } 129 | } 130 | if let image = image { 131 | if actualX > image.extent.size.width { 132 | actualX = image.extent.size.width 133 | } else if actualX < 0.0 { 134 | actualX = 0.0 135 | } 136 | if actualY > image.extent.size.height { 137 | actualY = image.extent.size.height 138 | } else if actualY < 0.0 { 139 | actualY = 0.0 140 | } 141 | } 142 | return CGPoint(x: actualX, y: actualY) 143 | } 144 | 145 | func pointToAngle(from point: CGPoint, imageSize: CGSize) -> CGFloat { 146 | let middleX = imageSize.width / 2 147 | let middleY = imageSize.height / 2 148 | 149 | let theX = middleX - point.x 150 | let theY = middleY - point.y 151 | let half: CGFloat 152 | let quarterTuple = (middleX < point.x, middleY < point.y) 153 | switch quarterTuple { 154 | case(true, false): 155 | half = 0.0 156 | case(false, false): 157 | half = 1.0 158 | case(false, true): 159 | half = 1.0 160 | default: 161 | half = 0.0 162 | } 163 | 164 | return (atan(theY / theX) + (half * 3.14159265)) 165 | } 166 | 167 | override func draw(_ dirtyRect: NSRect) { 168 | NSColor.systemGray.setStroke() 169 | NSBezierPath(rect: cropRect).stroke() 170 | NSColor(white: 1.0, alpha: 0.3).setFill() 171 | NSBezierPath(rect: cropRect).fill() 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /PhotoApp/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // PhotoApp 4 | // 5 | // Created by Pixelmator on 6/14/17. 6 | // Copyright © 2017 Pixelmator. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Document: NSDocument { 12 | 13 | override init() { 14 | super.init() 15 | } 16 | 17 | override class var autosavesInPlace: Bool { 18 | return true 19 | } 20 | 21 | var loadedImage: CIImage? 22 | 23 | override func makeWindowControllers() { 24 | // padaro storyboard kuris atvaizduoja dokumenta 25 | let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) //sukuriamas storyboard 26 | let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController // sukuria window controller 27 | self.addWindowController(windowController) 28 | 29 | if let viewController = windowControllers.first?.contentViewController as? ViewController { 30 | let actuallyLoadedImage: CIImage 31 | if let image = loadedImage { 32 | actuallyLoadedImage = image 33 | } else { 34 | actuallyLoadedImage = CIImage(cgImage: NSImage(named: NSImage.Name(rawValue: "photo"))!.cgImage(forProposedRect: nil, context: nil, hints: nil)!) 35 | loadedImage = actuallyLoadedImage 36 | //CIImage(cgImage: actuallyLoadedImage.cgImage(forProposedRect: nil, context:nil, hints: nil)!) 37 | } 38 | viewController.image = actuallyLoadedImage 39 | // (windowController as? WindowController)?.originalImage = actuallyLoadedImage 40 | (windowController as? WindowController)?.originalCIImage = actuallyLoadedImage 41 | windowController.window?.setFrame(CGRect(origin: origin(windowsize: windowSizeXY(imageSize: actuallyLoadedImage.extent.size, window: windowController.window)), size: windowSizeXY(imageSize: actuallyLoadedImage.extent.size, window: windowController.window)), display: true) 42 | } 43 | } 44 | 45 | func windowSizeXY(imageSize: CGSize, window: NSWindow?) -> CGSize { 46 | let toolbarHeight: CGFloat = window!.toolbarHeight() 47 | let displayX: CGFloat = (NSScreen.main?.visibleFrame.width)! 48 | let displayY: CGFloat = (NSScreen.main?.visibleFrame.height)! 49 | let imageX: CGFloat = imageSize.width 50 | let imageY: CGFloat = imageSize.height 51 | var windowX: CGFloat 52 | var windowY: CGFloat 53 | var tooBigX: Bool = false 54 | var tooBigY: Bool = false 55 | 56 | if imageY > displayY { tooBigY = true } 57 | if imageX > displayX { tooBigX = true } 58 | 59 | let imageRatio: CGFloat = imageX/imageY 60 | let tooBig = (tooBigX, tooBigY) 61 | 62 | switch tooBig { 63 | case (true, false) : 64 | windowX = displayX 65 | windowY = windowX/imageRatio 66 | case (false, true) : 67 | windowY = displayY 68 | windowX = windowY*imageRatio 69 | case (true, true) : 70 | let dispRatio: CGFloat = displayX/displayY 71 | 72 | if dispRatio < imageRatio { 73 | windowX = displayX 74 | windowY = windowX/imageRatio 75 | } 76 | else { 77 | windowY = displayY 78 | windowX = windowY*imageRatio 79 | } 80 | default: 81 | if imageY > 150.0 { 82 | windowY = imageY 83 | } else { windowY = 150.0 } 84 | 85 | if imageX > 300.0 { 86 | windowX = imageX 87 | } else { windowX = 300.0 } 88 | } 89 | windowX = -(1.52*toolbarHeight*imageRatio) + windowX 90 | //windowY = windowY + toolHeight 91 | return CGSize(width: windowX, height: windowY) 92 | } 93 | 94 | func origin(windowsize: CGSize) -> CGPoint { 95 | //get display size 96 | let displayX: CGFloat = (NSScreen.main?.frame.width)! 97 | let displayY: CGFloat = (NSScreen.main?.frame.height)! 98 | let windowX: CGFloat = windowsize.width 99 | let windowY: CGFloat = windowsize.height 100 | let point: CGPoint = CGPoint(x: (displayX - windowX)/2, y: (displayY - windowY)/2) 101 | return point 102 | } 103 | 104 | override func data(ofType typeName: String) throws -> Data { 105 | if let viewController = windowControllers.first?.window?.contentViewController as? ViewController, 106 | let image = viewController.image { 107 | let ciImageRep: NSCIImageRep = NSCIImageRep(ciImage: image) 108 | let nsImage: NSImage = NSImage(size: ciImageRep.size) 109 | nsImage.addRepresentation(ciImageRep) 110 | if let tiffData = nsImage.tiffRepresentation { 111 | return tiffData 112 | } 113 | } 114 | fatalError("Unable to write data") 115 | } 116 | 117 | override func read(from data: Data, ofType typeName: String) throws { 118 | loadedImage = CIImage(cgImage: (NSImage(data: data)?.cgImage(forProposedRect: nil, context: nil, hints: nil)!)!) 119 | // CIImage(cgImage: actuallyLoadedImage.cgImage(forProposedRect: nil, context:nil, hints: nil)!) 120 | } 121 | } 122 | 123 | 124 | extension NSWindow { 125 | func toolbarHeight() -> CGFloat { 126 | var toolbar: NSToolbar? 127 | var toolbarHeight: CGFloat = CGFloat(0.0) 128 | var windowFrame: NSRect 129 | toolbar = self.toolbar 130 | if let toolbar = toolbar { 131 | if toolbar.isVisible { 132 | windowFrame = NSWindow.contentRect(forFrameRect: self.frame, styleMask: self.styleMask) 133 | toolbarHeight = windowFrame.height - (self.contentView?.frame.height)! 134 | } 135 | } 136 | return CGFloat(toolbarHeight) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /PhotoApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeExtensions 9 | 10 | jpg 11 | png 12 | gif 13 | bmp 14 | jpeg 15 | 16 | CFBundleTypeIconFile 17 | 18 | CFBundleTypeName 19 | DocumentType 20 | CFBundleTypeOSTypes 21 | 22 | ???? 23 | 24 | CFBundleTypeRole 25 | Editor 26 | NSDocumentClass 27 | $(PRODUCT_MODULE_NAME).Document 28 | 29 | 30 | CFBundleDevelopmentRegion 31 | $(DEVELOPMENT_LANGUAGE) 32 | CFBundleExecutable 33 | $(EXECUTABLE_NAME) 34 | CFBundleIconFile 35 | 36 | CFBundleIdentifier 37 | $(PRODUCT_BUNDLE_IDENTIFIER) 38 | CFBundleInfoDictionaryVersion 39 | 6.0 40 | CFBundleName 41 | $(PRODUCT_NAME) 42 | CFBundlePackageType 43 | APPL 44 | CFBundleShortVersionString 45 | 1.0 46 | CFBundleVersion 47 | 1 48 | LSMinimumSystemVersion 49 | $(MACOSX_DEPLOYMENT_TARGET) 50 | NSHumanReadableCopyright 51 | Copyright © 2017 Pixelmator. All rights reserved. 52 | NSMainStoryboardFile 53 | Main 54 | NSPrincipalClass 55 | NSApplication 56 | 57 | 58 | -------------------------------------------------------------------------------- /PhotoApp/PhotoApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PhotoApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PhotoApp 4 | // 5 | // Created by Pixelmator on 6/12/17. 6 | // Copyright © 2017 Pixelmator. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import MetalKit 11 | 12 | class ViewController: NSViewController, MTKViewDelegate { 13 | 14 | //FIXME: kai updeitini paveiksliuka reikia kviesti drawableSizeWillChange / mtkView funkcija 15 | var image: CIImage? = nil { 16 | didSet { mtkView.needsDisplay = true } 17 | } 18 | 19 | weak var windowController: WindowController? = nil 20 | 21 | @IBOutlet weak var mtkView: MTKView! 22 | @IBOutlet var scrollView: NSScrollView? 23 | 24 | let metalDevice: MTLDevice = MTLCreateSystemDefaultDevice()! 25 | var commandQueue: MTLCommandQueue { return metalDevice.makeCommandQueue()! } 26 | let context = CIContext() 27 | 28 | 29 | 30 | func draw(in view: MTKView) { 31 | 32 | if let drawable = view.currentDrawable { 33 | let commandBuffer = commandQueue.makeCommandBuffer() 34 | if var image = image { 35 | 36 | let translateImageX = -image.extent.origin.x 37 | let translateImageY = -image.extent.origin.y 38 | let imageTransformTranslation = CGAffineTransform(translationX: translateImageX, y: translateImageY) 39 | image = image.transformed(by: imageTransformTranslation) 40 | 41 | let scale: CGFloat 42 | if (CGFloat(drawable.texture.width) / CGFloat(drawable.texture.height)) > (image.extent.size.width / image.extent.size.height) { 43 | scale = CGFloat(drawable.texture.height) / image.extent.size.height 44 | } else { 45 | scale = CGFloat(drawable.texture.width) / image.extent.size.width 46 | } 47 | let transformScale = CGAffineTransform(scaleX: scale, y: scale) 48 | var viewingImage = image.transformed(by: transformScale) 49 | 50 | 51 | var translationX: CGFloat 52 | var translationY: CGFloat 53 | if (CGFloat(drawable.texture.width) / CGFloat(drawable.texture.height)) > (image.extent.size.width / image.extent.size.height) { 54 | translationX = (abs(CGFloat(drawable.texture.width) - viewingImage.extent.size.width) / 2) - viewingImage.extent.origin.x 55 | translationY = -viewingImage.extent.origin.y 56 | } else { 57 | translationY = (abs(CGFloat(drawable.texture.height) - viewingImage.extent.height) / 2) - viewingImage.extent.origin.y 58 | translationX = -viewingImage.extent.origin.x 59 | } 60 | let transformTranslation = CGAffineTransform(translationX: translationX, y: translationY) 61 | viewingImage = viewingImage.transformed(by: transformTranslation) 62 | 63 | do { 64 | let renderDestination = CIRenderDestination(mtlTexture: drawable.texture, commandBuffer: commandBuffer) 65 | try context.startTask(toClear: renderDestination) 66 | try context.startTask(toRender: viewingImage, to: renderDestination) 67 | } catch { 68 | NSLog("failed to render") 69 | } 70 | 71 | 72 | } 73 | commandBuffer!.present(drawable) 74 | commandBuffer!.commit() 75 | commandBuffer!.waitUntilCompleted() 76 | } 77 | 78 | 79 | } 80 | 81 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 82 | // NSLog("drawableSizeWillChange") 83 | } 84 | 85 | override func viewDidLoad() { 86 | super.viewDidLoad() 87 | if let mainView = view.subviews.first as? NSScrollView, 88 | let clipView = mainView.subviews.first as? CenteredClipView, 89 | let metalView = clipView.subviews.first as? MTKView { 90 | metalView.autoResizeDrawable = true 91 | metalView.delegate = self 92 | metalView.enableSetNeedsDisplay = true 93 | metalView.device = metalDevice 94 | metalView.framebufferOnly = false 95 | metalView.layer?.isOpaque = false 96 | } 97 | 98 | if let mainView = view.subviews.first as? NSScrollView, 99 | let clipView = mainView.subviews.first as? CenteredClipView, 100 | let metalView = clipView.subviews.first as? MTKView { 101 | (metalView.subviews.first as? CustomImageView)?.layer?.isHidden = true 102 | } 103 | } 104 | 105 | override var representedObject: Any? { 106 | didSet { 107 | 108 | // Update the view, if already loaded. 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /PhotoApp/WindowController.swift: -------------------------------------------------------------------------------- 1 | // WindowController.swift 2 | // PhotoApp 3 | // 4 | // Created by Pixelmator on 6/19/17. 5 | // Copyright © 2017 Pixelmator. All rights reserved. 6 | 7 | import Cocoa 8 | import CoreImage 9 | 10 | class WindowController: NSWindowController, NSToolbarDelegate, NSPopoverDelegate { 11 | 12 | var originalImage: NSImage? = nil 13 | var originalCIImage: CIImage? = nil 14 | let ciContext = CIContext() 15 | 16 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 17 | // toolbar 18 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 19 | 20 | let ZoomToolbarItemID = "Zoom" 21 | let applyChangesToolbarItemID = "Apply Changes" 22 | let blurFilterToolbarItemID = "Blur Filter" 23 | let colorEffectFilterToolbarItemID = "Color Effect Filter" 24 | let colorAdjustmentFilterToolbarItemID = "Color Adjustment Filter" 25 | let geometryAdjustToolbarItemID = "Geometry Adjustment" 26 | let sharpenFilterToolbarItemID = "Sharpen Filter" 27 | let distortionFilterToolbarItemID = "Distortion" 28 | let stylizeToolbarItemID = "Stylize" 29 | 30 | @IBOutlet var toolbar: NSToolbar! 31 | 32 | 33 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 34 | // zoom toolbar item 35 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 36 | 37 | @IBOutlet var zoomControlView: NSView! 38 | @IBAction func changeZoom(_ sender: NSSegmentedControl) { 39 | let whichButton = sender.selectedSegment 40 | self.zoom(index: whichButton) 41 | } 42 | 43 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 44 | // apply changes toolbar item 45 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 46 | 47 | @IBOutlet var applyChangesView: NSView! 48 | @IBAction func applyChangesToolbar(_ sender: NSSegmentedControl) { 49 | applyChanges(index: sender.selectedSegment) 50 | } 51 | @IBOutlet weak var applyChangesButtons: NSSegmentedControl! 52 | 53 | @IBOutlet weak var applyChangesButton: NSSegmentedCell! 54 | 55 | @IBAction func applyChangesMenu(_ sender: NSMenuItem) { 56 | applyChanges(index: sender.tag) 57 | } 58 | 59 | func applyChanges(index: Int) { 60 | if let view = window?.contentViewController as? ViewController, 61 | let _ = originalCIImage { 62 | switch index { 63 | case 0: 64 | originalCIImage = view.image 65 | case 1: 66 | view.image = originalCIImage 67 | default: 68 | assertionFailure("Failed to apply changes") 69 | } 70 | setApplyState(state: false) 71 | } 72 | } 73 | 74 | func setApplyState(state: Bool) { 75 | if state { 76 | applyChangesButton.setImage(NSImage(named: NSImage.Name(rawValue: "CheckMarkColored")), forSegment: 0) 77 | applyChangesButton.setImage(NSImage(named: NSImage.Name(rawValue: "CrossMarkColored")), forSegment: 1) 78 | } 79 | if !state { 80 | applyChangesButton.setImage(NSImage(named: NSImage.Name(rawValue: "CheckMark")), forSegment: 0) 81 | applyChangesButton.setImage(NSImage(named: NSImage.Name(rawValue: "CrossMark")), forSegment: 1) 82 | } 83 | applyChangesButton.setEnabled(state, forSegment: 0) 84 | applyChangesButton.setEnabled(state, forSegment: 1) 85 | } 86 | 87 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 88 | // blur filter toolbar item and popovers 89 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 90 | 91 | @IBOutlet var blurFilterView: NSView! 92 | @IBOutlet weak var blurFilterPopUpButton: NSPopUpButton! 93 | 94 | var blurPopovers: [NSPopover?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, ] 95 | var blurPopoverControllerNames: [String] = ["", "BoxBlurPopoverViewController", "DiscBlurPopoverViewController", "GaussianBlurPopoverViewController", "MotionBlurPopoverViewController", "NoiseBlurPopoverViewController", "ZoomBlurPopoverViewController"] 96 | var blurPopoverControllers: [NSViewController?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil] 97 | 98 | func updateBoxBlur(with boxRadius: Double) { 99 | if let view = window?.contentViewController as? ViewController, 100 | let image = originalCIImage { 101 | var filteredImage = image.applyingFilter("CIBoxBlur", parameters: ["inputRadius": boxRadius]) 102 | filteredImage = filteredImage.cropped(to: image.extent) 103 | view.image = filteredImage 104 | setApplyState(state: true) 105 | } 106 | } 107 | 108 | func updateDiscBlur(with discRadius: Double) { 109 | if let view = window?.contentViewController as? ViewController, 110 | let image = originalCIImage { 111 | //Crashes with values 0...1 radar 33770007 112 | var filteredImage = image.applyingFilter("CIDiscBlur", parameters: ["inputRadius": discRadius]) 113 | filteredImage = filteredImage.cropped(to: image.extent) 114 | view.image = filteredImage 115 | setApplyState(state: true) 116 | } 117 | } 118 | 119 | func updateGaussianBlur(with sigma: Double) { 120 | if let view = window?.contentViewController as? ViewController, 121 | let image = originalCIImage { 122 | var filteredImage = image.applyingFilter("CIGaussianBlur", parameters: ["inputRadius": sigma]) 123 | filteredImage = filteredImage.cropped(to: image.extent) 124 | view.image = filteredImage 125 | setApplyState(state: true) 126 | } 127 | } 128 | 129 | func updateMaskedVariableBlur(with radius: Double) { 130 | if let view = window?.contentViewController as? ViewController, 131 | let image = originalCIImage { 132 | var filteredImage = image.applyingFilter("CIMaskedVariableBlur", parameters: ["inputRadius": radius]) 133 | filteredImage = filteredImage.cropped(to: image.extent) 134 | view.image = filteredImage 135 | setApplyState(state: true) 136 | } 137 | } 138 | 139 | func updateMedianFilter() { 140 | if let view = window?.contentViewController as? ViewController, 141 | let image = originalCIImage { 142 | var filteredImage = image.applyingFilter("CIMedianFilter", parameters: [:]) 143 | filteredImage = filteredImage.cropped(to: image.extent) 144 | view.image = filteredImage 145 | setApplyState(state: true) 146 | } 147 | } 148 | 149 | func updateMotionBlur(with motionRadius: Double, motionAngle: Double) { 150 | if let view = window?.contentViewController as? ViewController, 151 | let image = originalCIImage { 152 | var filteredImage = image.applyingFilter("CIMotionBlur", parameters: ["inputRadius": motionRadius, "inputAngle": motionAngle]) 153 | filteredImage = filteredImage.cropped(to: image.extent) 154 | view.image = filteredImage 155 | setApplyState(state: true) 156 | } 157 | } 158 | 159 | func updateNoiseReduction(with noiseLevel: Double, sharpness: Double) { 160 | if let view = window?.contentViewController as? ViewController, 161 | let image = originalCIImage { 162 | var filteredImage = image.applyingFilter("CINoiseReduction", parameters: ["inputNoiseLevel": noiseLevel, "inputSharpness": sharpness]) 163 | filteredImage = filteredImage.cropped(to: image.extent) 164 | view.image = filteredImage 165 | setApplyState(state: true) 166 | } 167 | } 168 | 169 | func updateZoomBlur(with amount: Double, center: CIVector) { 170 | if let view = window?.contentViewController as? ViewController, 171 | let image = originalCIImage { 172 | var filteredImage = image.applyingFilter("CIZoomBlur", parameters: ["inputAmount": amount, "inputCenter": center]) 173 | filteredImage = filteredImage.cropped(to: image.extent) 174 | view.image = filteredImage 175 | setApplyState(state: true) 176 | } 177 | } 178 | 179 | @IBAction func showBlurPopoverAction(_ sender: NSButton) { 180 | showBlurPopover(id: blurFilterPopUpButton.indexOfSelectedItem) 181 | } 182 | 183 | @IBAction func showBlurPopoverActionMenu(_ sender: NSMenuItem) { 184 | showBlurPopover(id: sender.tag) 185 | } 186 | 187 | func showBlurPopover(id: Int) { 188 | if id != 0 && id != 7 { 189 | blurPopovers[id] = nil 190 | blurPopoverControllers[id] = nil 191 | blurPopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: blurPopoverControllerNames[id] )) as? NSViewController 192 | self.createBlurPopover(popoverViewController: self.blurPopoverControllers[id]!, x: id) 193 | let prefEdge: NSRectEdge = NSRectEdge.minY 194 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 195 | self.blurPopovers[id]?.show(relativeTo: targetButton.bounds , of: targetButton, preferredEdge: prefEdge) 196 | callBlurUpdate(id: id) 197 | } else if id == 7 { 198 | self.updateMedianFilter() 199 | } 200 | } 201 | 202 | func createBlurPopover(popoverViewController: NSViewController, x: Int) { 203 | if (self.blurPopovers[x] == nil) { 204 | self.blurPopovers[x] = NSPopover.init() 205 | self.blurPopovers[x]?.contentViewController = popoverViewController; 206 | self.blurPopovers[x]?.animates = true 207 | self.blurPopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 208 | self.blurPopovers[x]?.behavior = NSPopover.Behavior.transient 209 | self.blurPopovers[x]?.delegate = self 210 | (self.blurPopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 211 | } 212 | } 213 | 214 | func callBlurUpdate(id: Int) { 215 | switch id { 216 | case 1: 217 | updateBoxBlur(with: 10.0) 218 | case 2: 219 | updateDiscBlur(with: 10.0) 220 | case 3: 221 | updateGaussianBlur(with: 10.0) 222 | case 4: 223 | updateMotionBlur(with: 10.0, motionAngle: 0.0) 224 | case 5: 225 | updateNoiseReduction(with: 0.2, sharpness: 1.0) 226 | default: 227 | self.doNothing() 228 | } 229 | } 230 | 231 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 232 | // color adjustment filter toolbar item and popovers 233 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 234 | 235 | @IBOutlet var colorAdjustmentFilterView: NSView! 236 | @IBOutlet weak var colorAdjustmentFilterPopUpButton: NSPopUpButton! 237 | 238 | var colorAdjustmentPopovers: [NSPopover?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 239 | var colorAdjustmentPopoverControllerNames: [String] = ["", "ColorControlsPopoverViewController", "ExposureAdjustPopoverViewController", "GammaAdjustPopoverViewController", "HueAdjustPopoverViewController", "", "", "TemperatureAndTintPopoverViewController", "VibrancePopoverViewController", "WhitePointAdjustPopoverViewController"] 240 | var colorAdjustmentPopoverControllers: [NSViewController?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 241 | let colorAdjustmentArray: [Int] = [1, 2, 3, 4, 7, 8, 9] 242 | 243 | func updateColorControls(with saturation: Double, brightness: Double, contrast: Double) { 244 | if let view = window?.contentViewController as? ViewController, 245 | let image = originalCIImage { 246 | var filteredImage = image.applyingFilter("CIColorControls", parameters: ["inputSaturation": saturation, "inputBrightness": brightness, "inputContrast": contrast]) 247 | filteredImage = filteredImage.cropped(to: image.extent) 248 | view.image = filteredImage 249 | setApplyState(state: true) 250 | } 251 | } 252 | 253 | func updateExposureAdjust(with ev: Double) { 254 | if let view = window?.contentViewController as? ViewController, 255 | let image = originalCIImage { 256 | var filteredImage = image.applyingFilter("CIExposureAdjust", parameters: ["inputEV": ev]) 257 | filteredImage = filteredImage.cropped(to: image.extent) 258 | view.image = filteredImage 259 | setApplyState(state: true) 260 | } 261 | } 262 | 263 | func updateGammaAdjust(with power: Double) { 264 | if let view = window?.contentViewController as? ViewController, 265 | let image = originalCIImage { 266 | var filteredImage = image.applyingFilter("CIGammaAdjust", parameters: ["inputPower": power]) 267 | filteredImage = filteredImage.cropped(to: image.extent) 268 | view.image = filteredImage 269 | setApplyState(state: true) 270 | } 271 | } 272 | 273 | func updateHueAdjust(with angle: Double) { 274 | if let view = window?.contentViewController as? ViewController, 275 | let image = originalCIImage { 276 | var filteredImage = image.applyingFilter("CIHueAdjust", parameters: ["inputAngle": angle]) 277 | filteredImage = filteredImage.cropped(to: image.extent) 278 | view.image = filteredImage 279 | setApplyState(state: true) 280 | } 281 | } 282 | 283 | func updateLinearToSRGBFilter() { 284 | if let view = window?.contentViewController as? ViewController, 285 | let image = originalCIImage { 286 | var filteredImage = image.applyingFilter("CILinearToSRGBToneCurve", parameters: [:]) 287 | filteredImage = filteredImage.cropped(to: image.extent) 288 | view.image = filteredImage 289 | setApplyState(state: true) 290 | } 291 | } 292 | 293 | func updateSRGBToLinearFilter() { 294 | if let view = window?.contentViewController as? ViewController, 295 | let image = originalCIImage { 296 | var filteredImage = image.applyingFilter("CISRGBToneCurveToLinear", parameters: [:]) 297 | filteredImage = filteredImage.cropped(to: image.extent) 298 | view.image = filteredImage 299 | setApplyState(state: true) 300 | } 301 | } 302 | 303 | func updateTemperatureAndTint(with temperature: CGFloat, tint: CGFloat) { 304 | if let view = window?.contentViewController as? ViewController, 305 | let image = originalCIImage { 306 | var filteredImage = image.applyingFilter("CITemperatureAndTint", parameters: ["inputNeutral": CIVector(x: 13000.0 - temperature, y: tint), "inputTargetNeutral": CIVector(x: 6500.0, y: 0.0)]) 307 | filteredImage = filteredImage.cropped(to: image.extent) 308 | view.image = filteredImage 309 | setApplyState(state: true) 310 | } 311 | } 312 | 313 | func updateVibranceAdjust(with vibrance: Double) { 314 | if let view = window?.contentViewController as? ViewController, 315 | let image = originalCIImage { 316 | var filteredImage = image.applyingFilter("CIVibrance", parameters: ["inputAmount": vibrance]) 317 | filteredImage = filteredImage.cropped(to: image.extent) 318 | view.image = filteredImage 319 | setApplyState(state: true) 320 | } 321 | } 322 | 323 | func updateWhitePointAdjust(with whitePoint: CIColor) { 324 | if let view = window?.contentViewController as? ViewController, 325 | let image = originalCIImage { 326 | var filteredImage = image.applyingFilter("CIWhitePointAdjust", parameters: ["inputColor": whitePoint]) 327 | filteredImage = filteredImage.cropped(to: image.extent) 328 | view.image = filteredImage 329 | setApplyState(state: true) 330 | } 331 | } 332 | 333 | @IBAction func showColorAdjustmentPopover(_ sender: NSButton) { 334 | showColorAdjustmentPopover(id: colorAdjustmentFilterPopUpButton.indexOfSelectedItem) 335 | } 336 | 337 | @IBAction func showColorAdjustmentPopoverMenu(_ sender: NSMenuItem) { 338 | showColorAdjustmentPopover(id: sender.tag) 339 | } 340 | 341 | func showColorAdjustmentPopover(id: Int) { 342 | switch id { 343 | case 5: 344 | self.updateLinearToSRGBFilter() 345 | case 6: 346 | self.updateSRGBToLinearFilter() 347 | default: 348 | if id != 0 { 349 | colorAdjustmentPopovers[id] = nil 350 | colorAdjustmentPopoverControllers[id] = nil 351 | colorAdjustmentPopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: colorAdjustmentPopoverControllerNames[id] )) as? NSViewController 352 | self.createColorAdjustmentPopover(popoverViewController: self.colorAdjustmentPopoverControllers[id]!, x: id) 353 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 354 | let prefEdge: NSRectEdge = NSRectEdge.minY 355 | self.colorAdjustmentPopovers[id]?.show(relativeTo: targetButton.bounds, of: targetButton, preferredEdge: prefEdge) 356 | callColorAdjustmentUpdate(id: id) 357 | } 358 | } 359 | } 360 | 361 | func createColorAdjustmentPopover(popoverViewController: NSViewController, x: Int) { 362 | if (self.colorAdjustmentPopovers[x] == nil) 363 | { 364 | self.colorAdjustmentPopovers[x] = NSPopover.init() 365 | self.colorAdjustmentPopovers[x]?.contentViewController = popoverViewController; 366 | self.colorAdjustmentPopovers[x]?.animates = true 367 | self.colorAdjustmentPopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 368 | self.colorAdjustmentPopovers[x]?.behavior = NSPopover.Behavior.transient 369 | self.colorAdjustmentPopovers[x]?.delegate = self 370 | (self.colorAdjustmentPopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 371 | } 372 | } 373 | 374 | func callColorAdjustmentUpdate(id: Int) { 375 | switch id { 376 | case 1: 377 | updateColorControls(with: 2.0, brightness: 0.25, contrast: 1.5) 378 | case 2: 379 | updateExposureAdjust(with: 1.0) 380 | case 3: 381 | updateGammaAdjust(with: 0.5) 382 | case 4: 383 | updateHueAdjust(with: 1.57079633) 384 | case 7: 385 | updateTemperatureAndTint(with: 3500.0, tint: 0.0) 386 | case 8: 387 | updateVibranceAdjust(with: 0.5) 388 | case 9: 389 | updateWhitePointAdjust(with: CIColor(red: 255 / 256, green: 255 / 256, blue: 200/256)) 390 | default: 391 | self.doNothing() 392 | } 393 | } 394 | 395 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 396 | // sharpening 397 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 398 | 399 | @IBOutlet var sharpenView: NSView! 400 | @IBOutlet var sharpenPopUpButton: NSPopUpButton! 401 | var sharpenPopovers: [NSPopover?] = [nil, nil, nil] 402 | var sharpenPopoverControllers: [NSViewController?] = [nil, nil, nil] 403 | var sharpenPopoverNames: [String] = ["", "SharpenLuminancePopoverViewController", "UnsharpMaskPopoverViewController"] 404 | 405 | // netgi dvi funkcijos 406 | 407 | func updateSharpenLuminance(with sharpness: Double) { 408 | if let view = window?.contentViewController as? ViewController, 409 | let image = originalCIImage { 410 | var filteredImage = image.applyingFilter("CISharpenLuminance", parameters: ["inputSharpness": sharpness]) 411 | filteredImage = filteredImage.cropped(to: image.extent) 412 | view.image = filteredImage 413 | setApplyState(state: true) 414 | } 415 | } 416 | 417 | func updateUnsharpMask(with radius: Double, intensity: Double) { 418 | if let view = window?.contentViewController as? ViewController, 419 | let image = originalCIImage { 420 | var filteredImage = image.applyingFilter("CIUnsharpMask", parameters: ["inputRadius": radius, "inputIntensity": intensity]) 421 | filteredImage = filteredImage.cropped(to: image.extent) 422 | view.image = filteredImage 423 | setApplyState(state: true) 424 | } 425 | } 426 | 427 | @IBAction func showSharpenPopoverAction(_ sender: NSButton) { 428 | showSharpenPopover(id: sharpenPopUpButton.indexOfSelectedItem) 429 | } 430 | 431 | @IBAction func showSharpenPopoverActionMenu(_ sender: NSMenuItem) { 432 | showSharpenPopover(id: sender.tag) 433 | } 434 | 435 | func showSharpenPopover(id: Int) { 436 | if id != 0 { 437 | sharpenPopovers[id] = nil 438 | sharpenPopoverControllers[id] = nil 439 | sharpenPopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: sharpenPopoverNames[id] )) as? NSViewController 440 | self.createSharpenPopover(popoverViewController: self.sharpenPopoverControllers[id]!, x: id) 441 | let prefEdge: NSRectEdge = NSRectEdge.minY 442 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 443 | self.sharpenPopovers[id]?.show(relativeTo: targetButton.bounds , of: targetButton, preferredEdge: prefEdge) 444 | callSharpenUpdate(id: id) 445 | } 446 | } 447 | 448 | func createSharpenPopover(popoverViewController: NSViewController, x: Int) { 449 | if (self.sharpenPopovers[x] == nil) 450 | { 451 | self.sharpenPopovers[x] = NSPopover.init() 452 | self.sharpenPopovers[x]?.contentViewController = popoverViewController; 453 | self.sharpenPopovers[x]?.animates = true 454 | self.sharpenPopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 455 | self.sharpenPopovers[x]?.behavior = NSPopover.Behavior.transient 456 | self.sharpenPopovers[x]?.delegate = self 457 | (self.sharpenPopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 458 | } 459 | } 460 | 461 | func callSharpenUpdate(id: Int) { 462 | switch id { 463 | case 1: 464 | self.updateSharpenLuminance(with: 0.5) 465 | case 2: 466 | self.updateUnsharpMask(with: 0.5, intensity: 0.5) 467 | default: 468 | self.doNothing() 469 | } 470 | } 471 | 472 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 473 | // geometry adjustment 474 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 475 | 476 | @IBOutlet var geometryAdjustmentView: NSView! 477 | @IBOutlet weak var geometryControls: NSSegmentedControl! 478 | @IBAction func initWindowControllerInstanceInCustomImageView(_ sender: Any) { 479 | if let view = window?.contentView?.subviews.first as? NSScrollView { 480 | (view.documentView?.subviews.first as? CustomImageView)?.windowController = self 481 | } 482 | } 483 | 484 | @IBOutlet weak var geometryControlsMomentary: NSSegmentedControl! 485 | @IBAction func geometryControlsMomentary(_ sender: Any) { 486 | if let sender = sender as? NSSegmentedControl { 487 | switch sender.selectedSegment { 488 | case 0: 489 | self.updateMirror(orientation: CGImagePropertyOrientation.upMirrored) 490 | case 1: 491 | self.updateMirror(orientation: CGImagePropertyOrientation.downMirrored) 492 | default: 493 | self.doNothing() 494 | } 495 | } 496 | } 497 | 498 | 499 | func updateCrop(with rectangle: CGRect) { 500 | if let view = window?.contentViewController as? ViewController, 501 | let image = originalCIImage { 502 | var offsetRect = rectangle 503 | offsetRect.origin.x += image.extent.origin.x 504 | offsetRect.origin.y += image.extent.origin.y 505 | let filteredImage = image.applyingFilter("CICrop", parameters: ["inputRectangle": CIVector(cgRect: offsetRect)]) 506 | view.image = filteredImage 507 | setApplyState(state: true) 508 | } 509 | } 510 | 511 | func updateRotation(with angle: CGFloat) { 512 | if let view = window?.contentViewController as? ViewController, 513 | let image = originalCIImage { 514 | let filteredImage = image.applyingFilter("CIStraightenFilter", parameters: ["inputAngle": angle]) 515 | view.image = filteredImage 516 | setApplyState(state: true) 517 | } 518 | } 519 | 520 | func updateMirror(orientation: CGImagePropertyOrientation) { 521 | if let view = window?.contentViewController as? ViewController, 522 | let image = originalCIImage { 523 | let filteredImage = image.oriented(orientation) 524 | view.image = filteredImage 525 | setApplyState(state: true) 526 | } 527 | } 528 | 529 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 530 | // color effect filter and toolbar item 531 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 532 | 533 | @IBOutlet var colorEffectFilterView: NSView! 534 | @IBOutlet weak var colorEffectFilterPopUpButton: NSPopUpButton! 535 | var colorEffectPopovers: [NSPopover?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 536 | var colorEffectPopoverControllerNames: [String] = ["", "", "ColorMonochromePopoverViewController", "ColorPosterizePopoverViewController", "FalseColorPopoverViewController", "", "", "", "", "", "", "", "", "", "", "", "SepiaTonePopoverViewController", "VignettePopoverViewController", "VignetteEffectPopoverViewController"] 537 | var colorEffectArray: [Int] = [2, 3, 4, 16, 17, 18] 538 | var colorEffectPopoverControllers: [NSViewController?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 539 | 540 | // labai daug funkciju 541 | 542 | func updateInvertColors() { 543 | if let view = window?.contentViewController as? ViewController, 544 | let image = originalCIImage { 545 | var filteredImage = image.applyingFilter("CIColorInvert", parameters: [:]) 546 | filteredImage = filteredImage.cropped(to: image.extent) 547 | view.image = filteredImage 548 | setApplyState(state: true) 549 | } 550 | } 551 | 552 | func updateMonochromeAdjust(with color: CIColor, intensity: Double) { 553 | if let view = window?.contentViewController as? ViewController, 554 | let image = originalCIImage { 555 | var filteredImage = image.applyingFilter("CIColorMonochrome", parameters: ["inputColor": color, "inputIntensity": intensity]) 556 | filteredImage = filteredImage.cropped(to: image.extent) 557 | view.image = filteredImage 558 | setApplyState(state: true) 559 | } 560 | } 561 | 562 | func updatePosterize(with levels: Double) { 563 | if let view = window?.contentViewController as? ViewController, 564 | let image = originalCIImage { 565 | var filteredImage = image.applyingFilter("CIColorPosterize", parameters: ["inputLevels": levels]) 566 | filteredImage = filteredImage.cropped(to: image.extent) 567 | view.image = filteredImage 568 | setApplyState(state: true) 569 | } 570 | } 571 | 572 | func updateFalseColor(with color0: CIColor, color1: CIColor) { 573 | if let view = window?.contentViewController as? ViewController, 574 | let image = originalCIImage { 575 | var filteredImage = image.applyingFilter("CIFalseColor", parameters: ["inputColor0": color0, "inputColor1": color1]) 576 | filteredImage = filteredImage.cropped(to: image.extent) 577 | view.image = filteredImage 578 | setApplyState(state: true) 579 | } 580 | } 581 | 582 | func updateMaskToAlpha() { 583 | if let view = window?.contentViewController as? ViewController, 584 | let image = originalCIImage { 585 | var filteredImage = image.applyingFilter("CIMaskToAlpha", parameters: [:]) 586 | filteredImage = filteredImage.cropped(to: image.extent) 587 | view.image = filteredImage 588 | setApplyState(state: true) 589 | } 590 | } 591 | 592 | func updateMaximumComponent() { 593 | if let view = window?.contentViewController as? ViewController, 594 | let image = originalCIImage { 595 | var filteredImage = image.applyingFilter("CIMaximumComponent", parameters: [:]) 596 | filteredImage = filteredImage.cropped(to: image.extent) 597 | view.image = filteredImage 598 | setApplyState(state: true) 599 | } 600 | } 601 | 602 | func updateMinimumComponent() { 603 | if let view = window?.contentViewController as? ViewController, 604 | let image = originalCIImage { 605 | var filteredImage = image.applyingFilter("CIMinimumComponent", parameters: [:]) 606 | filteredImage = filteredImage.cropped(to: image.extent) 607 | view.image = filteredImage 608 | setApplyState(state: true) 609 | } 610 | } 611 | 612 | func updatePhotoEffectChrome() { 613 | if let view = window?.contentViewController as? ViewController, 614 | let image = originalCIImage { 615 | var filteredImage = image.applyingFilter("CIPhotoEffectChrome", parameters: [:]) 616 | filteredImage = filteredImage.cropped(to: image.extent) 617 | view.image = filteredImage 618 | setApplyState(state: true) 619 | } 620 | } 621 | 622 | func updatePhotoEffectFade() { 623 | if let view = window?.contentViewController as? ViewController, 624 | let image = originalCIImage { 625 | var filteredImage = image.applyingFilter("CIPhotoEffectFade", parameters: [:]) 626 | filteredImage = filteredImage.cropped(to: image.extent) 627 | view.image = filteredImage 628 | setApplyState(state: true) 629 | } 630 | } 631 | 632 | func updatePhotoEffectInstant() { 633 | if let view = window?.contentViewController as? ViewController, 634 | let image = originalCIImage { 635 | var filteredImage = image.applyingFilter("CIPhotoEffectInstant", parameters: [:]) 636 | filteredImage = filteredImage.cropped(to: image.extent) 637 | view.image = filteredImage 638 | setApplyState(state: true) 639 | } 640 | } 641 | 642 | func updatePhotoEffectMono() { 643 | if let view = window?.contentViewController as? ViewController, 644 | let image = originalCIImage { 645 | var filteredImage = image.applyingFilter("CIPhotoEffectMono", parameters: [:]) 646 | filteredImage = filteredImage.cropped(to: image.extent) 647 | view.image = filteredImage 648 | setApplyState(state: true) 649 | } 650 | } 651 | 652 | func updatePhotoEffectNoir() { 653 | if let view = window?.contentViewController as? ViewController, 654 | let image = originalCIImage { 655 | var filteredImage = image.applyingFilter("CIPhotoEffectNoir", parameters: [:]) 656 | filteredImage = filteredImage.cropped(to: image.extent) 657 | view.image = filteredImage 658 | setApplyState(state: true) 659 | } 660 | } 661 | 662 | func updatePhotoEffectProcess() { 663 | if let view = window?.contentViewController as? ViewController, 664 | let image = originalCIImage { 665 | var filteredImage = image.applyingFilter("CIPhotoEffectProcess", parameters: [:]) 666 | filteredImage = filteredImage.cropped(to: image.extent) 667 | view.image = filteredImage 668 | setApplyState(state: true) 669 | } 670 | } 671 | 672 | func updatePhotoEffectTonal() { 673 | if let view = window?.contentViewController as? ViewController, 674 | let image = originalCIImage { 675 | var filteredImage = image.applyingFilter("CIPhotoEffectTonal", parameters: [:]) 676 | filteredImage = filteredImage.cropped(to: image.extent) 677 | view.image = filteredImage 678 | setApplyState(state: true) 679 | } 680 | } 681 | 682 | func updatePhotoEffectTransfer() { 683 | if let view = window?.contentViewController as? ViewController, 684 | let image = originalCIImage { 685 | var filteredImage = image.applyingFilter("CIPhotoEffectTransfer", parameters: [:]) 686 | filteredImage = filteredImage.cropped(to: image.extent) 687 | view.image = filteredImage 688 | setApplyState(state: true) 689 | } 690 | } 691 | 692 | func updateSepiaTone(with intensity: Double) { 693 | if let view = window?.contentViewController as? ViewController, 694 | let image = originalCIImage { 695 | var filteredImage = image.applyingFilter("CISepiaTone", parameters: ["inputIntensity": intensity]) 696 | filteredImage = filteredImage.cropped(to: image.extent) 697 | view.image = filteredImage 698 | setApplyState(state: true) 699 | } 700 | } 701 | 702 | func updateVignette(with radius: Double, intensity: Double) { 703 | if let view = window?.contentViewController as? ViewController, 704 | let image = originalCIImage { 705 | var filteredImage = image.applyingFilter("CIVignette", parameters: ["inputIntensity": intensity, "inputRadius": radius]) 706 | filteredImage = filteredImage.cropped(to: image.extent) 707 | view.image = filteredImage 708 | setApplyState(state: true) 709 | } 710 | } 711 | 712 | func updateVignetteEffect(with radius: Double, intensity: Double, center: CIVector) { 713 | if let view = window?.contentViewController as? ViewController, 714 | let image = originalCIImage { 715 | var filteredImage = image.applyingFilter("CIVignetteEffect", parameters: ["inputIntensity": intensity, "inputRadius": radius, "inputCenter": center]) 716 | filteredImage = filteredImage.cropped(to: image.extent) 717 | view.image = filteredImage 718 | setApplyState(state: true) 719 | } 720 | } 721 | 722 | 723 | @IBAction func showColorEffectPopoverAction(_ sender: NSButton) { 724 | showColorEffectPopover(id: colorEffectFilterPopUpButton.indexOfSelectedItem) 725 | } 726 | 727 | @IBAction func showColorEffectPopoverActionMenu(_ sender: NSMenuItem) { 728 | showColorEffectPopover(id: sender.tag) 729 | } 730 | 731 | func showColorEffectPopover(id: Int) { 732 | switch id { 733 | case 1: 734 | self.updateInvertColors() 735 | case 5: 736 | self.updateMaskToAlpha() 737 | case 6: 738 | self.updateMaximumComponent() 739 | case 7: 740 | self.updateMinimumComponent() 741 | case 8: 742 | self.updatePhotoEffectChrome() 743 | case 9: 744 | self.updatePhotoEffectFade() 745 | case 10: 746 | self.updatePhotoEffectInstant() 747 | case 11: 748 | self.updatePhotoEffectMono() 749 | case 12: 750 | self.updatePhotoEffectNoir() 751 | case 13: 752 | self.updatePhotoEffectProcess() 753 | case 14: 754 | self.updatePhotoEffectTonal() 755 | case 15: 756 | self.updatePhotoEffectTransfer() 757 | default: 758 | if id != 0 759 | { 760 | colorEffectPopovers[id] = nil 761 | colorEffectPopoverControllers[id] = nil 762 | colorEffectPopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: colorEffectPopoverControllerNames[id] )) as? NSViewController 763 | self.createColorEffectPopover(popoverViewController: self.colorEffectPopoverControllers[id]!, x: id) 764 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 765 | let prefEdge: NSRectEdge = NSRectEdge.minY 766 | self.colorEffectPopovers[id]?.show(relativeTo: targetButton.bounds, of: targetButton, preferredEdge: prefEdge) 767 | self.callColorEffectUpdate(id: id) 768 | } 769 | } 770 | } 771 | 772 | func createColorEffectPopover(popoverViewController: NSViewController, x: Int) { 773 | if (self.colorEffectPopovers[x] == nil) 774 | { 775 | self.colorEffectPopovers[x] = NSPopover.init() 776 | self.colorEffectPopovers[x]?.contentViewController = popoverViewController; 777 | self.colorEffectPopovers[x]?.animates = true 778 | self.colorEffectPopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 779 | self.colorEffectPopovers[x]?.behavior = NSPopover.Behavior.transient 780 | self.colorEffectPopovers[x]?.delegate = self 781 | (self.colorEffectPopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 782 | } 783 | } 784 | 785 | func callColorEffectUpdate(id: Int) { 786 | switch id { 787 | case 2: 788 | updateMonochromeAdjust(with: CIColor(red: 200 / 256, green: 255 / 256, blue: 255 / 256), intensity: 1.0) 789 | case 3: 790 | updatePosterize(with: 20.0) 791 | case 4: 792 | updateFalseColor(with: CIColor(red: 0.0, green: 0.0, blue: 100 / 256), color1: CIColor(red: 255 / 256, green: 200 / 256, blue: 255 / 256)) 793 | case 16: 794 | updateSepiaTone(with: 0.5) 795 | default: 796 | self.doNothing() 797 | } 798 | // } 799 | } 800 | 801 | 802 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 803 | // distortion effect filter and toolbar item 804 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 805 | 806 | @IBOutlet var distortionEffectFilterView: NSView! 807 | @IBOutlet weak var distortionEffectFilterPopUpButton: NSPopUpButton! 808 | var distortionEffectPopovers: [NSPopover?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 809 | var distortionEffectPopoverControllerNames: [String] = ["", "BumpDistortionPopoverViewController", "BumpDistortionLinearPopoverViewController", "CircleSplashDistortionPopoverViewController", "CircularWrapPopoverVIewController", "GlassDistortionPopoverViewController", "HoleDistortionPopoverViewController", "LightTunnelPopoverViewController", "PinchDistortionPopoverViewController", "StretchCropPopoverViewController", "TorusLensDistortionPopoverViewController", "TwirlDistortionPopoverViewController", "VortexDistortionPopoverViewController"] 810 | var distortionEffectArray: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 811 | var distortionEffectPopoverControllers: [NSViewController?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 812 | 813 | // labai daug funkciju 814 | 815 | func updateBumpDistortion(with x: CGFloat, y: CGFloat, radius: Double, scale: Double) { 816 | if let view = window?.contentViewController as? ViewController, 817 | let image = originalCIImage { 818 | var filteredImage = image.applyingFilter("CIBumpDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputScale": scale]) 819 | filteredImage = filteredImage.cropped(to: image.extent) 820 | view.image = filteredImage 821 | setApplyState(state: true) 822 | } 823 | } 824 | 825 | func updateBumpDistortionLinear(with x: CGFloat, y: CGFloat, radius: Double, angle: Double, scale: Double) { 826 | if let view = window?.contentViewController as? ViewController, 827 | let image = originalCIImage { 828 | var filteredImage = image.applyingFilter("CIBumpDistortionLinear", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputAngle": angle, "inputScale": scale]) 829 | filteredImage = filteredImage.cropped(to: image.extent) 830 | view.image = filteredImage 831 | setApplyState(state: true) 832 | } 833 | } 834 | 835 | func updateCircleSplashDistortion(with x: CGFloat, y: CGFloat, radius: Double) { 836 | if let view = window?.contentViewController as? ViewController, 837 | let image = originalCIImage { 838 | var filteredImage = image.applyingFilter("CICircleSplashDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius]) 839 | filteredImage = filteredImage.cropped(to: image.extent) 840 | view.image = filteredImage 841 | setApplyState(state: true) 842 | } 843 | } 844 | 845 | func updateCircularWrap(with x: CGFloat, y: CGFloat, radius: Double, angle: Double) { 846 | if let view = window?.contentViewController as? ViewController, 847 | let image = originalCIImage { 848 | var filteredImage = image.applyingFilter("CICircularWrap", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputAngle": angle]) 849 | filteredImage = filteredImage.cropped(to: image.extent) 850 | view.image = filteredImage 851 | setApplyState(state: true) 852 | } 853 | } 854 | 855 | func updateGlassDistortion(with x: CGFloat, y: CGFloat, scale: Double) { 856 | if let view = window?.contentViewController as? ViewController, 857 | let image = originalCIImage { 858 | let nsImage = NSImage(named: NSImage.Name(rawValue: "glassTexture")) 859 | let imageData = nsImage!.tiffRepresentation! 860 | let ciImage = CIImage(data: imageData) 861 | 862 | var filteredImage = image.applyingFilter("CIGlassDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputScale": scale, "inputTexture": ciImage!]) 863 | filteredImage = filteredImage.cropped(to: image.extent) 864 | view.image = filteredImage 865 | setApplyState(state: true) 866 | } 867 | } 868 | 869 | func updateHoleDistortion(with x: CGFloat, y: CGFloat, radius: Double) { 870 | if let view = window?.contentViewController as? ViewController, 871 | let image = originalCIImage { 872 | var filteredImage = image.applyingFilter("CIHoleDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius]) 873 | filteredImage = filteredImage.cropped(to: image.extent) 874 | view.image = filteredImage 875 | setApplyState(state: true) 876 | } 877 | } 878 | 879 | func updateLightTunnel(with x: CGFloat, y: CGFloat, radius: Double, rotation: Double) { 880 | if let view = window?.contentViewController as? ViewController, 881 | let image = originalCIImage { 882 | var filteredImage = image.applyingFilter("CILightTunnel", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputRotation": rotation]) 883 | filteredImage = filteredImage.cropped(to: image.extent) 884 | view.image = filteredImage 885 | setApplyState(state: true) 886 | } 887 | } 888 | 889 | func updatePinchDistortion(with x: CGFloat, y: CGFloat, radius: Double, scale: Double) { 890 | if let view = window?.contentViewController as? ViewController, 891 | let image = originalCIImage { 892 | var filteredImage = image.applyingFilter("CIPinchDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputScale": scale]) 893 | filteredImage = filteredImage.cropped(to: image.extent) 894 | view.image = filteredImage 895 | setApplyState(state: true) 896 | } 897 | } 898 | 899 | func updateStretchCrop(with width: CGFloat, height: CGFloat, cropAmount: Double, centerStretchAmount: Double) { 900 | if let view = window?.contentViewController as? ViewController, 901 | let image = originalCIImage { 902 | let filteredImage = image.applyingFilter("CIStretchCrop", parameters: ["inputSize": CIVector(x: width, y: height), "inputCropAmount": cropAmount, "inputCenterStretchAmount": centerStretchAmount]) 903 | view.image = filteredImage 904 | setApplyState(state: true) 905 | } 906 | } 907 | 908 | func updateTorusLensDistortion(with x: CGFloat, y: CGFloat, radius: Double, width: Double, refraction: Double) { 909 | if let view = window?.contentViewController as? ViewController, 910 | let image = originalCIImage { 911 | var filteredImage = image.applyingFilter("CITorusLensDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputWidth": width, "inputRefraction": refraction]) 912 | filteredImage = filteredImage.cropped(to: image.extent) 913 | view.image = filteredImage 914 | setApplyState(state: true) 915 | } 916 | } 917 | 918 | func updateTwirlDistortion(with x: CGFloat, y: CGFloat, radius: Double, angle: Double) { 919 | if let view = window?.contentViewController as? ViewController, 920 | let image = originalCIImage { 921 | var filteredImage = image.applyingFilter("CITwirlDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputAngle": angle]) 922 | filteredImage = filteredImage.cropped(to: image.extent) 923 | view.image = filteredImage 924 | setApplyState(state: true) 925 | } 926 | } 927 | 928 | func updateVortexDistortion(with x: CGFloat, y: CGFloat, radius: Double, angle: Double) { 929 | if let view = window?.contentViewController as? ViewController, 930 | let image = originalCIImage { 931 | var filteredImage = image.applyingFilter("CIVortexDistortion", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius, "inputAngle": angle]) 932 | filteredImage = filteredImage.cropped(to: image.extent) 933 | view.image = filteredImage 934 | setApplyState(state: true) 935 | } 936 | } 937 | 938 | 939 | @IBAction func showDistortionEffectPopoverAction(_ sender: NSButton) { 940 | showDistortionEffectPopover(id: distortionEffectFilterPopUpButton.indexOfSelectedItem) 941 | } 942 | 943 | @IBAction func showDistortionEffectPopoverActionMenu(_ sender: NSMenuItem) { 944 | showDistortionEffectPopover(id: sender.tag) 945 | } 946 | 947 | func showDistortionEffectPopover(id: Int) { 948 | if id != 0 949 | { 950 | distortionEffectPopovers[id] = nil 951 | distortionEffectPopoverControllers[id] = nil 952 | distortionEffectPopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: distortionEffectPopoverControllerNames[id] )) as? NSViewController 953 | self.createDistortionEffectPopover(popoverViewController: self.distortionEffectPopoverControllers[id]!, x: id) 954 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 955 | let prefEdge: NSRectEdge = NSRectEdge.minY 956 | self.distortionEffectPopovers[id]?.show(relativeTo: targetButton.bounds, of: targetButton, preferredEdge: prefEdge) 957 | callDistortionUpdate(id: id) 958 | } 959 | } 960 | 961 | func createDistortionEffectPopover(popoverViewController: NSViewController, x: Int) { 962 | if (self.distortionEffectPopovers[x] == nil) 963 | { 964 | self.distortionEffectPopovers[x] = NSPopover.init() 965 | self.distortionEffectPopovers[x]?.contentViewController = popoverViewController; 966 | self.distortionEffectPopovers[x]?.animates = true 967 | self.distortionEffectPopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 968 | self.distortionEffectPopovers[x]?.behavior = NSPopover.Behavior.transient 969 | self.distortionEffectPopovers[x]?.delegate = self 970 | (self.distortionEffectPopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 971 | } 972 | } 973 | 974 | func callDistortionUpdate(id: Int) { 975 | switch id { 976 | case 5: 977 | updateGlassDistortion(with: 0.0, y: 0.0, scale: 75.0) 978 | default: 979 | self.doNothing() 980 | } 981 | } 982 | 983 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 984 | // stylize filter and toolbar item 985 | //-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ 986 | 987 | @IBOutlet var stylizeFilterView: NSView! 988 | @IBOutlet weak var stylizeFilterPopUpButton: NSPopUpButton! 989 | var stylizePopovers: [NSPopover?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 990 | var stylizePopoverControllerNames: [String] = ["", "BloomPopoverViewController", "", "CrystallizePopoverViewController", "EdgesPopoverViewController", "EdgeWorkPopoverViewController", "GloomPopoverViewController", "HeightFieldFromMaskPopoverViewController", "HexagonalPixellatePopoverViewController", "HighlightShadowAdjustPopoverViewController", "LineOverlayPopoverViewController", "PixellatePopoverViewController", "PointillizePopoverViewController"] 991 | var stylizeArray: [Int] = [1, 3, 4, 5, 6, 7 ,8 ,9 , 10, 11, 12] 992 | var stylizePopoverControllers: [NSViewController?] = [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil] 993 | 994 | // daug funkciju 995 | 996 | 997 | func updateBloom(with radius: Double, intensity: Double) { 998 | if let view = window?.contentViewController as? ViewController, 999 | let image = originalCIImage { 1000 | var filteredImage = image.applyingFilter("CIBloom", parameters: ["inputRadius": radius, "inputIntensity": intensity]) 1001 | filteredImage = filteredImage.cropped(to: image.extent) 1002 | view.image = filteredImage 1003 | setApplyState(state: true) 1004 | } 1005 | } 1006 | 1007 | func updateComicEffect() { 1008 | if let view = window?.contentViewController as? ViewController, 1009 | let image = originalCIImage { 1010 | var filteredImage = image.applyingFilter("CIComicEffect", parameters: [:]) 1011 | filteredImage = filteredImage.cropped(to: image.extent) 1012 | view.image = filteredImage 1013 | setApplyState(state: true) 1014 | } 1015 | } 1016 | 1017 | func updateCrystallize(with x: CGFloat, y: CGFloat, radius: Double) { 1018 | if let view = window?.contentViewController as? ViewController, 1019 | let image = originalCIImage { 1020 | var filteredImage = image.applyingFilter("CICrystallize", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius]) 1021 | filteredImage = filteredImage.cropped(to: image.extent) 1022 | view.image = filteredImage 1023 | setApplyState(state: true) 1024 | } 1025 | } 1026 | 1027 | func updateEdges(with intensity: Double) { 1028 | if let view = window?.contentViewController as? ViewController, 1029 | let image = originalCIImage { 1030 | var filteredImage = image.applyingFilter("CIEdges", parameters: ["inputIntensity": intensity]) 1031 | filteredImage = filteredImage.cropped(to: image.extent) 1032 | view.image = filteredImage 1033 | setApplyState(state: true) 1034 | } 1035 | } 1036 | 1037 | func updateEdgeWork(with radius: Double) { 1038 | if let view = window?.contentViewController as? ViewController, 1039 | let image = originalCIImage { 1040 | var filteredImage = image.applyingFilter("CIEdgeWork", parameters: ["inputRadius": radius]) 1041 | filteredImage = filteredImage.cropped(to: image.extent) 1042 | view.image = filteredImage 1043 | setApplyState(state: true) 1044 | } 1045 | } 1046 | 1047 | func updateGloom(with radius: Double, intensity: Double) { 1048 | if let view = window?.contentViewController as? ViewController, 1049 | let image = originalCIImage { 1050 | var filteredImage = image.applyingFilter("CIGloom", parameters: ["inputRadius": radius, "inputIntensity": intensity]) 1051 | filteredImage = filteredImage.cropped(to: image.extent) 1052 | view.image = filteredImage 1053 | setApplyState(state: true) 1054 | } 1055 | } 1056 | 1057 | func updateHeightFieldFromMask(with radius: Double) { 1058 | if let view = window?.contentViewController as? ViewController, 1059 | let image = originalCIImage { 1060 | var filteredImage = image.applyingFilter("CIHeightFieldFromMask", parameters: ["inputRadius": radius]) 1061 | filteredImage = filteredImage.cropped(to: image.extent) 1062 | view.image = filteredImage 1063 | setApplyState(state: true) 1064 | } 1065 | } 1066 | 1067 | func updateHexagonalPixellate(with x: CGFloat, y: CGFloat, scale: Double) { 1068 | if let view = window?.contentViewController as? ViewController, 1069 | let image = originalCIImage { 1070 | var filteredImage = image.applyingFilter("CIHexagonalPixellate", parameters: ["inputCenter": CIVector(x: x, y: y), "inputScale": scale]) 1071 | filteredImage = filteredImage.cropped(to: image.extent) 1072 | view.image = filteredImage 1073 | setApplyState(state: true) 1074 | } 1075 | } 1076 | 1077 | func updateHighlightShadow(with highlight: Double, shadow: Double) { 1078 | if let view = window?.contentViewController as? ViewController, 1079 | let image = originalCIImage { 1080 | var filteredImage = image.applyingFilter("CIHighlightShadowAdjust", parameters: ["inputHighlightAmount": highlight, "inputShadowAmount": shadow]) 1081 | filteredImage = filteredImage.cropped(to: image.extent) 1082 | view.image = filteredImage 1083 | setApplyState(state: true) 1084 | } 1085 | } 1086 | 1087 | func updateLineOverlay(with NRNoiseLevel: Double, NRSharpness: Double, edgeIntensity: Double, threshold: Double, contrast: Double) { 1088 | if let view = window?.contentViewController as? ViewController, 1089 | let image = originalCIImage { 1090 | var filteredImage = image.applyingFilter("CILineOverlay", parameters: ["inputNRNoiseLevel": NRNoiseLevel, "inputNRSharpness": NRSharpness, "inputEdgeIntensity": edgeIntensity, "inputThreshold": threshold, "inputContrast": contrast]) 1091 | filteredImage = filteredImage.cropped(to: image.extent) 1092 | view.image = filteredImage 1093 | setApplyState(state: true) 1094 | } 1095 | } 1096 | 1097 | func updatePixellate(with x: CGFloat, y: CGFloat, scale: Double) { 1098 | if let view = window?.contentViewController as? ViewController, 1099 | let image = originalCIImage { 1100 | var filteredImage = image.applyingFilter("CIPixellate", parameters: ["inputCenter": CIVector(x: x, y: y), "inputScale": scale]) 1101 | filteredImage = filteredImage.cropped(to: image.extent) 1102 | view.image = filteredImage 1103 | setApplyState(state: true) 1104 | } 1105 | } 1106 | 1107 | func updatePointillize(with x: CGFloat, y: CGFloat, radius: Double) { 1108 | if let view = window?.contentViewController as? ViewController, 1109 | let image = originalCIImage { 1110 | var filteredImage = image.applyingFilter("CIPointillize", parameters: ["inputCenter": CIVector(x: x, y: y), "inputRadius": radius]) 1111 | filteredImage = filteredImage.cropped(to: image.extent) 1112 | view.image = filteredImage 1113 | setApplyState(state: true) 1114 | } 1115 | } 1116 | 1117 | @IBAction func showStylizePopoverAction(_ sender: NSButton) { 1118 | showStylizePopover(id: stylizeFilterPopUpButton.indexOfSelectedItem) 1119 | } 1120 | 1121 | @IBAction func showStylizePopoverActionMenu(_ sender: NSMenuItem) { 1122 | showStylizePopover(id: sender.tag) 1123 | } 1124 | 1125 | func showStylizePopover(id: Int) { 1126 | switch id { 1127 | case 2: 1128 | self.updateComicEffect() 1129 | default: 1130 | if id != 0 1131 | { 1132 | stylizePopovers[id] = nil 1133 | stylizePopoverControllers[id] = nil 1134 | stylizePopoverControllers[id] = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: stylizePopoverControllerNames[id])) as? NSViewController 1135 | self.createStylizePopover(popoverViewController: self.stylizePopoverControllers[id]!, x: id) 1136 | let targetButton = window!.standardWindowButton(.closeButton)!.superview! 1137 | let prefEdge: NSRectEdge = NSRectEdge.minY 1138 | self.stylizePopovers[id]?.show(relativeTo: targetButton.bounds, of: targetButton, preferredEdge: prefEdge) 1139 | callStylizeUpdate(id: id) 1140 | } 1141 | } 1142 | } 1143 | 1144 | func createStylizePopover(popoverViewController: NSViewController, x: Int) { 1145 | if (self.stylizePopovers[x] == nil) 1146 | { 1147 | self.stylizePopovers[x] = NSPopover.init() 1148 | self.stylizePopovers[x]?.contentViewController = popoverViewController; 1149 | self.stylizePopovers[x]?.animates = true 1150 | self.stylizePopovers[x]?.appearance = NSAppearance.init(named: NSAppearance.Name.vibrantLight) 1151 | self.stylizePopovers[x]?.behavior = NSPopover.Behavior.transient 1152 | self.stylizePopovers[x]?.delegate = self 1153 | (self.stylizePopovers[x]?.contentViewController as? PopoverViewController)?.windowController = self 1154 | } 1155 | } 1156 | 1157 | func callStylizeUpdate(id: Int) { 1158 | switch id { 1159 | case 1: 1160 | updateBloom(with: 20.0, intensity: 0.5) 1161 | case 4: 1162 | updateEdges(with: 10.0) 1163 | case 5: 1164 | updateEdgeWork(with: 2.0) 1165 | case 6: 1166 | updateGloom(with: 25.0, intensity: 0.5) 1167 | case 7: 1168 | updateHeightFieldFromMask(with: 25.0) 1169 | case 9: 1170 | updateHighlightShadow(with: 0.5, shadow: -0.3) 1171 | case 10: 1172 | updateLineOverlay(with: 0.01, NRSharpness: 3, edgeIntensity: 2, threshold: 1.5, contrast: 0.25) 1173 | default: 1174 | self.doNothing() 1175 | } 1176 | } 1177 | 1178 | func doNothing() {} 1179 | 1180 | override func windowDidLoad() { 1181 | super.windowDidLoad() 1182 | 1183 | // toolbar 1184 | 1185 | setApplyState(state: false) 1186 | self.toolbar.allowsUserCustomization = true 1187 | self.toolbar.autosavesConfiguration = true 1188 | self.toolbar.displayMode = .iconOnly 1189 | 1190 | if let viewController = window?.contentViewController as? ViewController { 1191 | viewController.windowController = self 1192 | } 1193 | } 1194 | 1195 | 1196 | func zoom(index: Int) { 1197 | if let view = window?.contentView?.subviews.first as? NSScrollView { 1198 | switch(index) { 1199 | case 0: view.magnification = view.magnification / 1.5 1200 | case 1: view.magnification = view.magnification * 1.5 1201 | default: print("Invalid selection") 1202 | } 1203 | } 1204 | } 1205 | 1206 | func customToolbarItem(itemForItemIdentifier itemIdentifier: String, label: String, paletteLabel: String, toolTip: String, target: AnyObject, itemContent: AnyObject, action: Selector?, menu: NSMenu?) -> NSToolbarItem? { 1207 | 1208 | let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: itemIdentifier)) 1209 | 1210 | toolbarItem.label = label 1211 | toolbarItem.paletteLabel = paletteLabel 1212 | toolbarItem.toolTip = toolTip 1213 | toolbarItem.target = target 1214 | toolbarItem.action = action 1215 | 1216 | // Set the right attribute, depending on if we were given an image or a view. 1217 | if (itemContent is NSImage) { 1218 | let image: NSImage = itemContent as! NSImage 1219 | toolbarItem.image = image 1220 | } 1221 | else if (itemContent is NSView) { 1222 | let view: NSView = itemContent as! NSView 1223 | toolbarItem.view = view 1224 | } 1225 | else { 1226 | assertionFailure("Invalid selection") 1227 | } 1228 | 1229 | let menuItem: NSMenuItem = NSMenuItem() 1230 | menuItem.submenu = menu 1231 | menuItem.title = label 1232 | toolbarItem.menuFormRepresentation = menuItem 1233 | 1234 | return toolbarItem 1235 | } 1236 | 1237 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { 1238 | 1239 | var toolbarItem: NSToolbarItem = NSToolbarItem() 1240 | 1241 | if (itemIdentifier == ((ZoomToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1242 | // 1) Zoom toolbar item. 1243 | toolbarItem = customToolbarItem(itemForItemIdentifier: ZoomToolbarItemID, label: "Zoom", paletteLabel:"Zoom", toolTip: "Change your zoom level", target: self, itemContent: self.zoomControlView, action: nil, menu: nil)! 1244 | } else if (itemIdentifier == ((blurFilterToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1245 | // 2) Blur filter toolbar item. 1246 | toolbarItem = customToolbarItem(itemForItemIdentifier: blurFilterToolbarItemID, label: "Blur Filter", paletteLabel:"Blur Filter", toolTip: "Apply blur filter to the image", target: self, itemContent: self.blurFilterView, action: nil, menu: nil)! 1247 | } else if (itemIdentifier == ((colorEffectFilterToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1248 | // 3) Color effect filter toolbar item. 1249 | toolbarItem = customToolbarItem(itemForItemIdentifier: colorEffectFilterToolbarItemID, label: "Color Effect Filter", paletteLabel:"Color Effect Filter", toolTip: "Apply color effect filter to the image", target: self, itemContent: self.colorEffectFilterView, action: nil, menu: nil)! 1250 | } else if (itemIdentifier == ((colorAdjustmentFilterToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1251 | // 4) Color effect filter toolbar item. 1252 | toolbarItem = customToolbarItem(itemForItemIdentifier: colorAdjustmentFilterToolbarItemID, label: "Color Adjustment Filter", paletteLabel:"Color Adjustment Filter", toolTip: "Apply color adjustment filter to the image", target: self, itemContent: self.colorAdjustmentFilterView, action: nil, menu: nil)! 1253 | } else if (itemIdentifier == ((geometryAdjustToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1254 | // 5) Geometry adjustment toolbar item. 1255 | toolbarItem = customToolbarItem(itemForItemIdentifier: geometryAdjustToolbarItemID, label: "Geometry Adjustment", paletteLabel:"Geometry Adjustment", toolTip: "Adjust the geometry of the image", target: self, itemContent: self.geometryAdjustmentView, action: nil, menu: nil)! 1256 | } else if (itemIdentifier == ((applyChangesToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1257 | // 6) Apply changes toolbar item. 1258 | toolbarItem = customToolbarItem(itemForItemIdentifier: applyChangesToolbarItemID, label: "Apply Changes", paletteLabel:"Apply Changes", toolTip: "Apply or discard changes done to the image", target: self, itemContent: self.applyChangesView, action: nil, menu: nil)! 1259 | } else if (itemIdentifier == ((sharpenFilterToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1260 | // 7) Sharpen filter toolbar item. 1261 | toolbarItem = customToolbarItem(itemForItemIdentifier: sharpenFilterToolbarItemID, label: "Sharpen", paletteLabel:"Sharpen", toolTip: "Sharpen the image", target: self, itemContent: self.sharpenView, action: nil, menu: nil)! 1262 | } else if (itemIdentifier == ((distortionFilterToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1263 | // 8) Distortion toolbar item. 1264 | toolbarItem = customToolbarItem(itemForItemIdentifier: distortionFilterToolbarItemID, label: "Distortion", paletteLabel:"Distortion", toolTip: "Distort the image", target: self, itemContent: self.distortionEffectFilterView, action: nil, menu: nil)! 1265 | } else if (itemIdentifier == ((stylizeToolbarItemID as NSString) as NSToolbarItem.Identifier)) { 1266 | // 9) Stylize toolbar item. 1267 | toolbarItem = customToolbarItem(itemForItemIdentifier: stylizeToolbarItemID, label: "Stylize", paletteLabel:"Stylize", toolTip: "Stylize the image", target: self, itemContent: self.stylizeFilterView, action: nil, menu: nil)! 1268 | } 1269 | 1270 | return toolbarItem 1271 | } 1272 | 1273 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 1274 | return [NSToolbarItem.Identifier(rawValue: ZoomToolbarItemID), 1275 | NSToolbarItem.Identifier(rawValue: applyChangesToolbarItemID), 1276 | NSToolbarItem.Identifier(rawValue: geometryAdjustToolbarItemID), 1277 | NSToolbarItem.Identifier(rawValue: blurFilterToolbarItemID), 1278 | NSToolbarItem.Identifier(rawValue: sharpenFilterToolbarItemID), 1279 | NSToolbarItem.Identifier(rawValue: colorAdjustmentFilterToolbarItemID), 1280 | NSToolbarItem.Identifier(rawValue: colorEffectFilterToolbarItemID), 1281 | NSToolbarItem.Identifier(rawValue: distortionFilterToolbarItemID), 1282 | NSToolbarItem.Identifier(rawValue: stylizeToolbarItemID)] 1283 | } 1284 | 1285 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { 1286 | return [NSToolbarItem.Identifier(rawValue: ZoomToolbarItemID), 1287 | NSToolbarItem.Identifier(rawValue: applyChangesToolbarItemID), 1288 | NSToolbarItem.Identifier(rawValue: geometryAdjustToolbarItemID), 1289 | NSToolbarItem.Identifier(rawValue: blurFilterToolbarItemID), 1290 | NSToolbarItem.Identifier(rawValue: sharpenFilterToolbarItemID), 1291 | NSToolbarItem.Identifier(rawValue: colorAdjustmentFilterToolbarItemID), 1292 | NSToolbarItem.Identifier(rawValue: colorEffectFilterToolbarItemID), 1293 | NSToolbarItem.Identifier(rawValue: distortionFilterToolbarItemID), 1294 | NSToolbarItem.Identifier(rawValue: stylizeToolbarItemID), 1295 | NSToolbarItem.Identifier.space, 1296 | NSToolbarItem.Identifier.flexibleSpace] 1297 | } 1298 | 1299 | func popoverShouldDetach(_ popover: NSPopover) -> Bool { 1300 | return true 1301 | } 1302 | 1303 | } 1304 | -------------------------------------------------------------------------------- /PhotoAppArchive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/PhotoAppArchive.zip -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # PhotoApp 2 | 3 | Photo editing application for macOS based on Core Image framework. 4 | 5 | ![GitHub Logo](/Resources/screenshot.png) 6 | 7 | ### Features 8 | 9 | * macOS application developed using Swift 4. 10 | * Image editing is based on Core Image framework. 11 | * Image rendering and outputting to the screen is done using Metal framework which utilizes GPU, so the process is much quicker than rendering with the CPU. 12 | * Needs no internet connection, no logging in - just download it, build it and start using the app right away! 13 | * Supports most of the widely used formats today, such as .jpg, .png, .gif and .bmp 14 | 15 | 16 | ### Requirements 17 | * Runtime: macOS 10.13 18 | * Build: Xcode 9 beta 6 or Xcode 9 19 | 20 | ### Usage 21 | * Simple as ever - open the image You want to edit, select filter, fiddle with the sliders (sometimes You won't have to do even that) and you are all set! 22 | 23 | ### Background 24 | * This app was written as a project for my summer internship at Pixelmator Team. 25 | -------------------------------------------------------------------------------- /Resources/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProtoProtonas/PhotoApp/d76782c43c188ef170b70e96e971299c0b6e4ecc/Resources/screenshot.png --------------------------------------------------------------------------------