├── .gitignore ├── LICENSE ├── README.md ├── XcoatOfPaint.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── XcoatOfPaint.xcscheme ├── XcoatOfPaint ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Xcoat macOS-1024.png │ │ ├── Xcoat macOS-128.png │ │ ├── Xcoat macOS-16.png │ │ ├── Xcoat macOS-256.png │ │ ├── Xcoat macOS-32.png │ │ ├── Xcoat macOS-512.png │ │ └── Xcoat macOS-64.png │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── FileDropImageView.swift ├── HueSlider.swift ├── ImageEditor.swift ├── Info.plist ├── RGBtoHSV.swift ├── ViewController.swift ├── ViewModel.swift ├── XcoatOfPaint-Bridging-Header.h ├── XcoatOfPaint.entitlements ├── XcodeManager.swift └── acextract │ ├── AssetsCatalog.swift │ ├── CoreUI.h │ ├── CoreUI_Swift.swift │ └── ImageSet.swift ├── Xcoats.png ├── app.png └── dock.png /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | DerivedData 3 | 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | 14 | *.xccheckout 15 | *.moved-aside 16 | *.xcuserstate 17 | *.xcscmblueprint 18 | 19 | .DS_Store 20 | 21 | *.hmap 22 | *.ipa 23 | *.app.dsym.zip 24 | 25 | Pods/ 26 | 27 | Carthage/Build 28 | 29 | fastlane/report.xml 30 | fastlane/screenshots/**/*.png 31 | fastlane/screenshots/screenshots.html 32 | metadata/ 33 | test_output/ 34 | Preview.html 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Christian Lobach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XcoatOfPaint 2 | 3 | 4 | Have you ever wished the Xcode icon could get a fresh coat of paint to match the colorful Mac you just bought? 5 | Or you want to distinguish current and older Xcode versions from one another. 6 | XcoatOfPaint lets you do this by simply adjusting a few sliders. 7 | 8 | ## How to run 9 | ### Prebuilt release 10 | Download the latest release from [GitHub](https://github.com/DerLobi/XcoatOfPaint/releases/latest/download/XcoatOfPaint.zip) 11 | 12 | Requires macOS 11 Big Sur 13 | 14 | ### Build from source 15 | Just download the project and _"Build and Run"_ from Xcode. 16 | 17 | ## Usage 18 | 19 | 20 | * Drag the Xcode.app onto the left side of the app 21 | * Change hue, saturation and brightness to your liking 22 | * Click on "Replace Xcode Icon" 23 | 24 | If you use an Xcode version that you directly downloaded from Apple's Developer website, the icon will be replaced automatically. 25 | 26 | If you use the App Store version however, the app is owned by the "system" user, so we can't change it directly. An error message will appear and you can choose to open the "Get Info" dialog for Xcode. The new icon file is copied to the clipboard, so you can select the existing icon in the "Get Info" dialog and paste by pressing ⌘+v. 27 | 28 | If Xcode is currently running, you will need to close it and open it again before the icon change takes effect. 29 | 30 | To restore the original icon, click on "Restore default Xcode icon" or, select the existing icon in the "Get Info" dialog and hit the delete key. 31 | 32 | You can also replace the icons of Xcode-Beta, Simulator or Instruments. 33 | 34 | 35 | 36 | **Pro-Tip:** You can ⌘-drag the app from your Dock, so you don't have to hunt for it in Finder. 37 | 38 | ## License 39 | [MIT License](https://github.com/DerLobi/XcoatOfPaint/blob/main/LICENSE) 40 | 41 | ## 3rd party components 42 | * Color cube code taken from [https://github.com/trav-ma/TMReplaceColorHue](https://github.com/trav-ma/TMReplaceColorHue) 43 | * Compiled asset catalog extraction code taken from [https://github.com/bartoszj/acextract](https://github.com/bartoszj/acextract) 44 | -------------------------------------------------------------------------------- /XcoatOfPaint.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CA09FF7526372F8500EAD209 /* ImageSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA09FF6E26372F8500EAD209 /* ImageSet.swift */; }; 11 | CA09FF7626372F8500EAD209 /* AssetsCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA09FF7126372F8500EAD209 /* AssetsCatalog.swift */; }; 12 | CA09FF7726372F8500EAD209 /* CoreUI_Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA09FF7226372F8500EAD209 /* CoreUI_Swift.swift */; }; 13 | CA09FF912637312C00EAD209 /* CoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA09FF902637312C00EAD209 /* CoreUI.framework */; }; 14 | CA09FF922637312C00EAD209 /* CoreUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CA09FF902637312C00EAD209 /* CoreUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | CA915A3A26516A52006B5F7D /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA915A3926516A52006B5F7D /* ViewModel.swift */; }; 16 | CAB752C62635B1B000CA5396 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752C52635B1B000CA5396 /* AppDelegate.swift */; }; 17 | CAB752C82635B1B000CA5396 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752C72635B1B000CA5396 /* ViewController.swift */; }; 18 | CAB752CA2635B1B100CA5396 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CAB752C92635B1B100CA5396 /* Assets.xcassets */; }; 19 | CAB752CD2635B1B100CA5396 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CAB752CB2635B1B100CA5396 /* Main.storyboard */; }; 20 | CAB752D72635B1F400CA5396 /* ImageEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752D62635B1F400CA5396 /* ImageEditor.swift */; }; 21 | CAB752DA2635B28700CA5396 /* XcodeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752D92635B28700CA5396 /* XcodeManager.swift */; }; 22 | CAB752DD2635B92100CA5396 /* FileDropImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752DC2635B92100CA5396 /* FileDropImageView.swift */; }; 23 | CAB752E02635C27400CA5396 /* RGBtoHSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752DF2635C27400CA5396 /* RGBtoHSV.swift */; }; 24 | CAB752E92635CBFD00CA5396 /* HueSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB752E82635CBFD00CA5396 /* HueSlider.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | CA09FF932637312C00EAD209 /* Embed Frameworks */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = ""; 32 | dstSubfolderSpec = 10; 33 | files = ( 34 | CA09FF922637312C00EAD209 /* CoreUI.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | CA09FF6E26372F8500EAD209 /* ImageSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSet.swift; sourceTree = ""; }; 43 | CA09FF7026372F8500EAD209 /* CoreUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreUI.h; sourceTree = ""; }; 44 | CA09FF7126372F8500EAD209 /* AssetsCatalog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetsCatalog.swift; sourceTree = ""; }; 45 | CA09FF7226372F8500EAD209 /* CoreUI_Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreUI_Swift.swift; sourceTree = ""; }; 46 | CA09FF7B26372FC000EAD209 /* XcoatOfPaint-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XcoatOfPaint-Bridging-Header.h"; sourceTree = ""; }; 47 | CA09FF902637312C00EAD209 /* CoreUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreUI.framework; path = "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks/CoreUI.framework"; sourceTree = ""; }; 48 | CA915A3926516A52006B5F7D /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 49 | CAB752C22635B1B000CA5396 /* XcoatOfPaint.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XcoatOfPaint.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | CAB752C52635B1B000CA5396 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | CAB752C72635B1B000CA5396 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 52 | CAB752C92635B1B100CA5396 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | CAB752CC2635B1B100CA5396 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | CAB752CE2635B1B100CA5396 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | CAB752CF2635B1B100CA5396 /* XcoatOfPaint.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XcoatOfPaint.entitlements; sourceTree = ""; }; 56 | CAB752D62635B1F400CA5396 /* ImageEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageEditor.swift; sourceTree = ""; }; 57 | CAB752D92635B28700CA5396 /* XcodeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeManager.swift; sourceTree = ""; }; 58 | CAB752DC2635B92100CA5396 /* FileDropImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDropImageView.swift; sourceTree = ""; }; 59 | CAB752DF2635C27400CA5396 /* RGBtoHSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RGBtoHSV.swift; sourceTree = ""; }; 60 | CAB752E82635CBFD00CA5396 /* HueSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HueSlider.swift; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | CAB752BF2635B1B000CA5396 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | CA09FF912637312C00EAD209 /* CoreUI.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | CA09FF6D26372F8500EAD209 /* acextract */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | CA09FF6E26372F8500EAD209 /* ImageSet.swift */, 79 | CA09FF7026372F8500EAD209 /* CoreUI.h */, 80 | CA09FF7126372F8500EAD209 /* AssetsCatalog.swift */, 81 | CA09FF7226372F8500EAD209 /* CoreUI_Swift.swift */, 82 | ); 83 | path = acextract; 84 | sourceTree = ""; 85 | }; 86 | CA09FF8F2637312C00EAD209 /* Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | CA09FF902637312C00EAD209 /* CoreUI.framework */, 90 | ); 91 | name = Frameworks; 92 | sourceTree = ""; 93 | }; 94 | CAB752B92635B1B000CA5396 = { 95 | isa = PBXGroup; 96 | children = ( 97 | CAB752C42635B1B000CA5396 /* XcoatOfPaint */, 98 | CAB752C32635B1B000CA5396 /* Products */, 99 | CA09FF8F2637312C00EAD209 /* Frameworks */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | CAB752C32635B1B000CA5396 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | CAB752C22635B1B000CA5396 /* XcoatOfPaint.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | CAB752C42635B1B000CA5396 /* XcoatOfPaint */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | CA09FF7B26372FC000EAD209 /* XcoatOfPaint-Bridging-Header.h */, 115 | CA09FF6D26372F8500EAD209 /* acextract */, 116 | CAB752C52635B1B000CA5396 /* AppDelegate.swift */, 117 | CAB752C92635B1B100CA5396 /* Assets.xcassets */, 118 | CAB752DC2635B92100CA5396 /* FileDropImageView.swift */, 119 | CAB752E82635CBFD00CA5396 /* HueSlider.swift */, 120 | CAB752D62635B1F400CA5396 /* ImageEditor.swift */, 121 | CAB752CE2635B1B100CA5396 /* Info.plist */, 122 | CAB752CB2635B1B100CA5396 /* Main.storyboard */, 123 | CAB752DF2635C27400CA5396 /* RGBtoHSV.swift */, 124 | CAB752C72635B1B000CA5396 /* ViewController.swift */, 125 | CAB752CF2635B1B100CA5396 /* XcoatOfPaint.entitlements */, 126 | CAB752D92635B28700CA5396 /* XcodeManager.swift */, 127 | CA915A3926516A52006B5F7D /* ViewModel.swift */, 128 | ); 129 | path = XcoatOfPaint; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | CAB752C12635B1B000CA5396 /* XcoatOfPaint */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = CAB752D22635B1B100CA5396 /* Build configuration list for PBXNativeTarget "XcoatOfPaint" */; 138 | buildPhases = ( 139 | CAB752BE2635B1B000CA5396 /* Sources */, 140 | CAB752BF2635B1B000CA5396 /* Frameworks */, 141 | CAB752C02635B1B000CA5396 /* Resources */, 142 | CA09FF932637312C00EAD209 /* Embed Frameworks */, 143 | CA915A3B26518534006B5F7D /* SwiftLint */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = XcoatOfPaint; 150 | productName = XcoatOfPaint; 151 | productReference = CAB752C22635B1B000CA5396 /* XcoatOfPaint.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | CAB752BA2635B1B000CA5396 /* Project object */ = { 158 | isa = PBXProject; 159 | attributes = { 160 | LastSwiftUpdateCheck = 1240; 161 | LastUpgradeCheck = 1250; 162 | TargetAttributes = { 163 | CAB752C12635B1B000CA5396 = { 164 | CreatedOnToolsVersion = 12.4; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = CAB752BD2635B1B000CA5396 /* Build configuration list for PBXProject "XcoatOfPaint" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = CAB752B92635B1B000CA5396; 177 | productRefGroup = CAB752C32635B1B000CA5396 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | CAB752C12635B1B000CA5396 /* XcoatOfPaint */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | CAB752C02635B1B000CA5396 /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | CAB752CA2635B1B100CA5396 /* Assets.xcassets in Resources */, 192 | CAB752CD2635B1B100CA5396 /* Main.storyboard in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXShellScriptBuildPhase section */ 199 | CA915A3B26518534006B5F7D /* SwiftLint */ = { 200 | isa = PBXShellScriptBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | ); 204 | inputFileListPaths = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = SwiftLint; 209 | outputFileListPaths = ( 210 | ); 211 | outputPaths = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 216 | }; 217 | /* End PBXShellScriptBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | CAB752BE2635B1B000CA5396 /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | CA09FF7726372F8500EAD209 /* CoreUI_Swift.swift in Sources */, 225 | CA09FF7526372F8500EAD209 /* ImageSet.swift in Sources */, 226 | CAB752E02635C27400CA5396 /* RGBtoHSV.swift in Sources */, 227 | CA915A3A26516A52006B5F7D /* ViewModel.swift in Sources */, 228 | CAB752D72635B1F400CA5396 /* ImageEditor.swift in Sources */, 229 | CAB752C82635B1B000CA5396 /* ViewController.swift in Sources */, 230 | CAB752DD2635B92100CA5396 /* FileDropImageView.swift in Sources */, 231 | CA09FF7626372F8500EAD209 /* AssetsCatalog.swift in Sources */, 232 | CAB752E92635CBFD00CA5396 /* HueSlider.swift in Sources */, 233 | CAB752C62635B1B000CA5396 /* AppDelegate.swift in Sources */, 234 | CAB752DA2635B28700CA5396 /* XcodeManager.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | CAB752CB2635B1B100CA5396 /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | CAB752CC2635B1B100CA5396 /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXVariantGroup section */ 250 | 251 | /* Begin XCBuildConfiguration section */ 252 | CAB752D02635B1B100CA5396 /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_ANALYZER_NONNULL = YES; 257 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_ENABLE_OBJC_WEAK = YES; 263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_COMMA = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = dwarf; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | ENABLE_TESTABILITY = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu11; 290 | GCC_DYNAMIC_NO_PIC = NO; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_OPTIMIZATION_LEVEL = 0; 293 | GCC_PREPROCESSOR_DEFINITIONS = ( 294 | "DEBUG=1", 295 | "$(inherited)", 296 | ); 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | MACOSX_DEPLOYMENT_TARGET = 11.0; 304 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 305 | MTL_FAST_MATH = YES; 306 | ONLY_ACTIVE_ARCH = YES; 307 | SDKROOT = macosx; 308 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 309 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 310 | }; 311 | name = Debug; 312 | }; 313 | CAB752D12635B1B100CA5396 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | CLANG_ANALYZER_NONNULL = YES; 318 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_ENABLE_OBJC_WEAK = YES; 324 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 325 | CLANG_WARN_BOOL_CONVERSION = YES; 326 | CLANG_WARN_COMMA = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 329 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 330 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INFINITE_RECURSION = YES; 334 | CLANG_WARN_INT_CONVERSION = YES; 335 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 337 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 341 | CLANG_WARN_STRICT_PROTOTYPES = YES; 342 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 343 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | COPY_PHASE_STRIP = NO; 347 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 348 | ENABLE_NS_ASSERTIONS = NO; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | GCC_C_LANGUAGE_STANDARD = gnu11; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 354 | GCC_WARN_UNDECLARED_SELECTOR = YES; 355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 356 | GCC_WARN_UNUSED_FUNCTION = YES; 357 | GCC_WARN_UNUSED_VARIABLE = YES; 358 | MACOSX_DEPLOYMENT_TARGET = 11.0; 359 | MTL_ENABLE_DEBUG_INFO = NO; 360 | MTL_FAST_MATH = YES; 361 | SDKROOT = macosx; 362 | SWIFT_COMPILATION_MODE = wholemodule; 363 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 364 | }; 365 | name = Release; 366 | }; 367 | CAB752D32635B1B100CA5396 /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 371 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 372 | CODE_SIGN_ENTITLEMENTS = XcoatOfPaint/XcoatOfPaint.entitlements; 373 | CODE_SIGN_IDENTITY = "-"; 374 | CODE_SIGN_STYLE = Automatic; 375 | COMBINE_HIDPI_IMAGES = YES; 376 | CURRENT_PROJECT_VERSION = 10; 377 | DEVELOPMENT_TEAM = ""; 378 | ENABLE_HARDENED_RUNTIME = YES; 379 | FRAMEWORK_SEARCH_PATHS = "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; 380 | INFOPLIST_FILE = XcoatOfPaint/Info.plist; 381 | LD_RUNPATH_SEARCH_PATHS = ( 382 | "$(inherited)", 383 | "@executable_path/../Frameworks", 384 | ); 385 | MARKETING_VERSION = 1.3.1; 386 | PRODUCT_BUNDLE_IDENTIFIER = "de.christian-lobach.XcoatOfPaint"; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SWIFT_OBJC_BRIDGING_HEADER = "XcoatOfPaint/XcoatOfPaint-Bridging-Header.h"; 389 | SWIFT_VERSION = 5.0; 390 | }; 391 | name = Debug; 392 | }; 393 | CAB752D42635B1B100CA5396 /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 398 | CODE_SIGN_ENTITLEMENTS = XcoatOfPaint/XcoatOfPaint.entitlements; 399 | CODE_SIGN_IDENTITY = "-"; 400 | CODE_SIGN_STYLE = Automatic; 401 | COMBINE_HIDPI_IMAGES = YES; 402 | CURRENT_PROJECT_VERSION = 10; 403 | DEVELOPMENT_TEAM = ""; 404 | ENABLE_HARDENED_RUNTIME = YES; 405 | FRAMEWORK_SEARCH_PATHS = "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; 406 | INFOPLIST_FILE = XcoatOfPaint/Info.plist; 407 | LD_RUNPATH_SEARCH_PATHS = ( 408 | "$(inherited)", 409 | "@executable_path/../Frameworks", 410 | ); 411 | MARKETING_VERSION = 1.3.1; 412 | PRODUCT_BUNDLE_IDENTIFIER = "de.christian-lobach.XcoatOfPaint"; 413 | PRODUCT_NAME = "$(TARGET_NAME)"; 414 | SWIFT_OBJC_BRIDGING_HEADER = "XcoatOfPaint/XcoatOfPaint-Bridging-Header.h"; 415 | SWIFT_VERSION = 5.0; 416 | }; 417 | name = Release; 418 | }; 419 | /* End XCBuildConfiguration section */ 420 | 421 | /* Begin XCConfigurationList section */ 422 | CAB752BD2635B1B000CA5396 /* Build configuration list for PBXProject "XcoatOfPaint" */ = { 423 | isa = XCConfigurationList; 424 | buildConfigurations = ( 425 | CAB752D02635B1B100CA5396 /* Debug */, 426 | CAB752D12635B1B100CA5396 /* Release */, 427 | ); 428 | defaultConfigurationIsVisible = 0; 429 | defaultConfigurationName = Release; 430 | }; 431 | CAB752D22635B1B100CA5396 /* Build configuration list for PBXNativeTarget "XcoatOfPaint" */ = { 432 | isa = XCConfigurationList; 433 | buildConfigurations = ( 434 | CAB752D32635B1B100CA5396 /* Debug */, 435 | CAB752D42635B1B100CA5396 /* Release */, 436 | ); 437 | defaultConfigurationIsVisible = 0; 438 | defaultConfigurationName = Release; 439 | }; 440 | /* End XCConfigurationList section */ 441 | }; 442 | rootObject = CAB752BA2635B1B000CA5396 /* Project object */; 443 | } 444 | -------------------------------------------------------------------------------- /XcoatOfPaint.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XcoatOfPaint.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /XcoatOfPaint.xcodeproj/xcshareddata/xcschemes/XcoatOfPaint.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /XcoatOfPaint/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Xcoat macOS-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "Xcoat macOS-32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Xcoat macOS-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "Xcoat macOS-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Xcoat macOS-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "Xcoat macOS-256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Xcoat macOS-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "Xcoat macOS-512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Xcoat macOS-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "Xcoat macOS-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-1024.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-128.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-16.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-256.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-32.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-512.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/XcoatOfPaint/Assets.xcassets/AppIcon.appiconset/Xcoat macOS-64.png -------------------------------------------------------------------------------- /XcoatOfPaint/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /XcoatOfPaint/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | NSIsNotNil 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 553 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | -------------------------------------------------------------------------------- /XcoatOfPaint/FileDropImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileDropImageView.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import AppKit 9 | 10 | class FileDropImageView: NSImageView { 11 | 12 | var didReceiveFile: ((URL) -> Void)? 13 | 14 | override init(frame frameRect: NSRect) { 15 | super.init(frame: frameRect) 16 | registerForDraggedTypes([.fileURL]) 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | super.init(coder: coder) 21 | registerForDraggedTypes([.fileURL]) 22 | } 23 | 24 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { 25 | if NSURL(from: sender.draggingPasteboard) != nil { 26 | return .copy 27 | } 28 | return [] 29 | } 30 | 31 | override func draggingEnded(_ sender: NSDraggingInfo) { 32 | guard let fileURL = NSURL(from: sender.draggingPasteboard) else { return } 33 | 34 | didReceiveFile?(fileURL as URL) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XcoatOfPaint/HueSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HueSlider.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSGradient { 11 | static var allHues: NSGradient = { 12 | let colors = Array(0...100) 13 | .map { NSColor(calibratedHue: CGFloat($0) / 100, saturation: 1.0, brightness: 1.0, alpha: 1.0) } 14 | return NSGradient(colors: colors)! 15 | }() 16 | } 17 | 18 | public class HueSlider: NSSlider { 19 | 20 | private var barGradient: NSGradient = .allHues 21 | 22 | private let bezelXMargin: CGFloat = 6 23 | private let bezelyMargin: CGFloat = 12 24 | 25 | override public func draw(_ dirtyRect: NSRect) { 26 | assert(!self.isVertical) 27 | 28 | let bezelFrame = bounds.insetBy(dx: bezelXMargin, dy: bezelyMargin) 29 | let bar = NSBezierPath(roundedRect: bezelFrame, 30 | xRadius: bezelFrame.height * 0.5, 31 | yRadius: bezelFrame.height * 0.5) 32 | barGradient.draw(in: bar, angle: 0.0) 33 | 34 | let innerRect = bounds.insetBy(dx: 14 / 2, dy: 0) 35 | 36 | let knobX: CGFloat 37 | if maxValue - minValue == 0 { 38 | knobX = innerRect.minX 39 | } else { 40 | knobX = innerRect.minX + CGFloat((doubleValue - minValue) / maxValue) * innerRect.width 41 | } 42 | 43 | let shadowPath = NSBezierPath(roundedRect: 44 | NSRect(x: (knobX - bounds.height / 4), y: 0, width: 10, height: bounds.height) 45 | .insetBy(dx: 0.5, dy: 3.5), xRadius: 5, yRadius: 5) 46 | NSColor(white: 0.3, alpha: 0.3).setFill() 47 | shadowPath.fill() 48 | 49 | let knobPath = NSBezierPath(roundedRect: 50 | NSRect(x: (knobX - bounds.height / 4), y: 0, width: 10, height: bounds.height) 51 | .insetBy(dx: 1, dy: 4), xRadius: 5, yRadius: 5) 52 | 53 | let amount = CGFloat(floatValue / Float(maxValue)) 54 | let knobColor = barGradient.interpolatedColor(atLocation: amount) 55 | knobColor.setFill() 56 | knobPath.fill() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /XcoatOfPaint/ImageEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditor.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import Cocoa 9 | import CoreImage.CIFilterBuiltins 10 | import Combine 11 | 12 | // swiftlint:disable function_body_length identifier_name 13 | 14 | // https://github.com/trav-ma/TMReplaceColorHue/blob/master/TMReplaceColorHue/ViewController.swift 15 | class ImageEditor: NSObject { 16 | 17 | @objc var brightnessAdjustment: Float = 0 18 | @objc var saturationAdjustment: Float = 0 19 | @objc var destCenterHueAngle: Float = 0.57 20 | 21 | @objc dynamic var inputImage: NSImage? 22 | 23 | @objc dynamic var outputImage: NSImage? 24 | 25 | private let defaultHue: Float = 205 26 | private let maximumMonochromeSaturationThreshold: Float = 0.2 27 | private let maximumMonochromeBrightnessThreshold: Float = 0.2 28 | 29 | private var cancellables = Set() 30 | private let renderQueue = DispatchQueue(label: "RenderQueue", qos: .userInteractive) 31 | 32 | override init() { 33 | 34 | super.init() 35 | Publishers.Merge4( 36 | publisher(for: \.inputImage).map { _ in return }, 37 | publisher(for: \.brightnessAdjustment).map { _ in return }, 38 | publisher(for: \.saturationAdjustment).map { _ in return }, 39 | publisher(for: \.destCenterHueAngle).map { _ in return }) 40 | .throttle(for: .milliseconds(10), scheduler: renderQueue, latest: true) 41 | .map { [weak self] _ in 42 | self?.render() 43 | } 44 | .receive(on: DispatchQueue.main) 45 | .assign(to: \.outputImage, on: self) 46 | .store(in: &cancellables) 47 | } 48 | 49 | func render() -> NSImage? { 50 | guard let inputImage = inputImage, 51 | let bitmapRep = inputImage.representations 52 | .compactMap({ $0 as? NSBitmapImageRep }) 53 | .first, 54 | let ciImage = CIImage(bitmapImageRep: bitmapRep) 55 | else { return nil } 56 | 57 | let centerHueAngle: Float = defaultHue/360.0 58 | 59 | let hueAdjustment = centerHueAngle - destCenterHueAngle 60 | let size = 64 61 | var cubeData = [Float](repeating: 0, count: size * size * size * 4) 62 | var rgb: [Float] = [0, 0, 0] 63 | var hsv: (h: Float, s: Float, v: Float) 64 | var newRGB: (r: Float, g: Float, b: Float) 65 | var offset = 0 66 | for z in 0 ..< size { 67 | rgb[2] = Float(z) / Float(size) // blue value 68 | for y in 0 ..< size { 69 | rgb[1] = Float(y) / Float(size) // green value 70 | for x in 0 ..< size { 71 | rgb[0] = Float(x) / Float(size) // red value 72 | hsv = RGBtoHSV(rgb[0], g: rgb[1], b: rgb[2]) 73 | 74 | // special consigeration for monochrome elements 75 | // like the hammer or the "A" glyph 76 | let isConsideredMonochrome = 77 | hsv.s < maximumMonochromeSaturationThreshold 78 | || hsv.v < maximumMonochromeBrightnessThreshold 79 | 80 | if isConsideredMonochrome { 81 | if saturationAdjustment < 0 { 82 | hsv.s += saturationAdjustment 83 | } 84 | 85 | newRGB = HSVtoRGB(hsv.h, s: hsv.s, v: hsv.v) 86 | } else { 87 | hsv.s += saturationAdjustment 88 | hsv.v += (brightnessAdjustment * hsv.v) 89 | hsv.h -= hueAdjustment 90 | newRGB = HSVtoRGB(hsv.h, s: hsv.s, v: hsv.v) 91 | } 92 | cubeData[offset] = newRGB.r 93 | cubeData[offset+1] = newRGB.g 94 | cubeData[offset+2] = newRGB.b 95 | cubeData[offset+3] = 1.0 96 | offset += 4 97 | } 98 | } 99 | } 100 | let b = cubeData.withUnsafeBufferPointer { Data(buffer: $0) } 101 | let data = b as Data 102 | let colorCube = CIFilter.colorCube() 103 | colorCube.cubeDimension = Float(size) 104 | colorCube.cubeData = data 105 | colorCube.inputImage = ciImage 106 | 107 | let context = CIContext(options: nil) 108 | guard let outImage = colorCube.outputImage, 109 | let outputImageRef = context.createCGImage(outImage, from: outImage.extent) 110 | else { return nil } 111 | return NSImage(cgImage: outputImageRef, size: inputImage.size) 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /XcoatOfPaint/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /XcoatOfPaint/RGBtoHSV.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RGBtoHSV.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import Cocoa 9 | // swiftlint:disable identifier_name large_tuple 10 | // https://github.com/trav-ma/TMReplaceColorHue/blob/master/TMReplaceColorHue/ViewController.swift 11 | 12 | func HSVtoRGB(_ h: Float, s: Float, v: Float) -> (r: Float, g: Float, b: Float) { 13 | var r: Float = 0 14 | var g: Float = 0 15 | var b: Float = 0 16 | let C = s * v 17 | let HS = h * 6.0 18 | let X = C * (1.0 - fabsf(fmodf(HS, 2.0) - 1.0)) 19 | if HS >= 0 && HS < 1 { 20 | r = C 21 | g = X 22 | b = 0 23 | } else if HS >= 1 && HS < 2 { 24 | r = X 25 | g = C 26 | b = 0 27 | } else if HS >= 2 && HS < 3 { 28 | r = 0 29 | g = C 30 | b = X 31 | } else if HS >= 3 && HS < 4 { 32 | r = 0 33 | g = X 34 | b = C 35 | } else if HS >= 4 && HS < 5 { 36 | r = X 37 | g = 0 38 | b = C 39 | } else if HS >= 5 && HS < 6 { 40 | r = C 41 | g = 0 42 | b = X 43 | } 44 | let m = v - C 45 | r += m 46 | g += m 47 | b += m 48 | return (r, g, b) 49 | } 50 | 51 | func RGBtoHSV(_ r: Float, g: Float, b: Float) -> (h: Float, s: Float, v: Float) { 52 | var h: CGFloat = 0 53 | var s: CGFloat = 0 54 | var v: CGFloat = 0 55 | let col = NSColor(red: CGFloat(r), green: CGFloat(g), blue: CGFloat(b), alpha: 1.0) 56 | col.getHue(&h, saturation: &s, brightness: &v, alpha: nil) 57 | return (Float(h), Float(s), Float(v)) 58 | } 59 | -------------------------------------------------------------------------------- /XcoatOfPaint/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ViewController: NSViewController { 11 | 12 | @IBOutlet private weak var sourceImageView: FileDropImageView! 13 | 14 | @objc private let viewModel = ViewModel() 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | sourceImageView.didReceiveFile = { [weak self] fileURL in 20 | self?.viewModel.loadApp(at: fileURL) 21 | } 22 | 23 | viewModel.errorHandler = { [weak self] error in 24 | self?.handleError(error) 25 | } 26 | } 27 | 28 | @IBAction private func replaceIcon(_ sender: Any) { 29 | viewModel.replaceIcon() 30 | } 31 | 32 | @IBAction func saveDocument(_ sender: Any) { 33 | viewModel.saveIcon() 34 | } 35 | 36 | @IBAction private func restoreDefaultIcon(_ sender: Any) { 37 | viewModel.restoreDefaultIcon() 38 | } 39 | 40 | private func handleError(_ error: Error) { 41 | guard let window = view.window else { return } 42 | let nsError = error as NSError 43 | let alert = NSAlert() 44 | alert.messageText = nsError.localizedFailureReason ?? nsError.localizedDescription 45 | alert.informativeText = nsError.localizedRecoverySuggestion ?? "" 46 | 47 | let recoveryAction = (error as? XcodeManagerError)?.recoveryAction 48 | if let recoveryAction = recoveryAction { 49 | alert.addButton(withTitle: recoveryAction.title) 50 | alert.addButton(withTitle: "Cancel") 51 | } 52 | 53 | alert.beginSheetModal(for: window) { response in 54 | if response == .alertFirstButtonReturn { 55 | recoveryAction?.action() 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /XcoatOfPaint/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModel.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 16.05.21. 6 | // 7 | 8 | import Foundation 9 | import Cocoa 10 | import Combine 11 | 12 | class ViewModel: NSObject { 13 | 14 | @objc private let xcodeManager = XcodeManager() 15 | @objc private let imageEditor = ImageEditor() 16 | 17 | @objc dynamic private(set) var replaceIconButtonTitle: String = "Replace Xcode icon" 18 | 19 | override init() { 20 | super.init() 21 | startObserving() 22 | } 23 | 24 | private var cancellables = Set() 25 | private func startObserving() { 26 | xcodeManager 27 | .publisher(for: \.appName) 28 | .map { "Replace \($0) icon" } 29 | .assign(to: \.replaceIconButtonTitle, on: self) 30 | .store(in: &cancellables) 31 | } 32 | 33 | var errorHandler: ((Error) -> Void)? 34 | 35 | func loadApp(at url: URL) { 36 | xcodeManager.appURL = url 37 | imageEditor.inputImage = xcodeManager.appIcon 38 | } 39 | 40 | func replaceIcon() { 41 | guard let outputImage = imageEditor.outputImage else { return } 42 | do { 43 | try xcodeManager.replaceIcon(with: outputImage) 44 | } catch { 45 | errorHandler?(error) 46 | } 47 | } 48 | 49 | func restoreDefaultIcon() { 50 | do { 51 | try xcodeManager.restoreDefaultIcon() 52 | } catch { 53 | errorHandler?(error) 54 | } 55 | } 56 | 57 | func saveIcon() { 58 | guard let outputImage = imageEditor.outputImage, 59 | let cgImage = outputImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return } 60 | 61 | let bitmapRep = NSBitmapImageRep(cgImage: cgImage) 62 | bitmapRep.size = outputImage.size 63 | let pngData = bitmapRep.representation(using: .png, properties: [:]) 64 | 65 | let savePanel = NSSavePanel() 66 | savePanel.canCreateDirectories = true 67 | savePanel.showsTagField = false 68 | savePanel.nameFieldStringValue = "XcoatOfPaint.png" 69 | savePanel.level = .modalPanel 70 | savePanel.begin { result in 71 | guard let url = savePanel.url, result == .OK else { return } 72 | try? pngData?.write(to: url) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /XcoatOfPaint/XcoatOfPaint-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcoatOfPaint-Bridging-Header.h 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 26.04.21. 6 | // 7 | 8 | #import "CoreUI.h" 9 | -------------------------------------------------------------------------------- /XcoatOfPaint/XcoatOfPaint.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.files.user-selected.executable 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /XcoatOfPaint/XcodeManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeManager.swift 3 | // XcoatOfPaint 4 | // 5 | // Created by Christian Lobach on 25.04.21. 6 | // 7 | 8 | import Cocoa 9 | 10 | struct RecoveryAction { 11 | let title: String 12 | let action: () -> Void 13 | } 14 | 15 | struct XcodeManagerError: LocalizedError { 16 | 17 | fileprivate enum ErrorType { 18 | case needsToChangeInGetInfo(appName: String) 19 | case needsToRemoveInGetInfo(appName: String) 20 | case iconChangeFailed(appName: String) 21 | } 22 | 23 | fileprivate var errorType: ErrorType 24 | 25 | var recoveryAction: RecoveryAction? 26 | 27 | var failureReason: String? { 28 | switch errorType { 29 | case .needsToChangeInGetInfo, .needsToRemoveInGetInfo: 30 | // swiftlint:disable line_length 31 | return """ 32 | If you have installed Xcode from the App Store, this app doesn't have enough permissions to change the app icon automatically. 33 | You can change or remove the icon manually via the \"Get Info\" dialog. 34 | """ 35 | // swiftlint:enable line_length 36 | default: 37 | return nil 38 | } 39 | } 40 | 41 | var recoverySuggestion: String? { 42 | switch errorType { 43 | case .needsToChangeInGetInfo(let appName): 44 | return "Select the existing icon in the top of the \"\(appName) Info\" dialog and press ⌘ + V." 45 | case .needsToRemoveInGetInfo(let appName): 46 | return "Click on the icon in the \"\(appName) Info\" dialog and press the delete key on your keyboard." 47 | default: 48 | return nil 49 | } 50 | } 51 | } 52 | 53 | class XcodeManager: NSObject { 54 | 55 | @objc dynamic var appURL: URL? { 56 | didSet { 57 | guard var name = appURL?.lastPathComponent, name.hasSuffix(".app") else { 58 | appName = "Xcode" 59 | return 60 | } 61 | name.removeLast(4) 62 | appName = name 63 | } 64 | } 65 | 66 | @objc dynamic private(set) var appName: String = "Xcode" 67 | 68 | var appIcon: NSImage? { 69 | guard let xcodeURL = appURL else { return nil } 70 | return icon(["Xcode", "XcodeBeta", "AppIcon", "AppIconBeta", "Instruments", "InstrumentsBeta"], fromAssetCatalogRelativeToURL: xcodeURL) 71 | ?? iconFromICNS(["Xcode.icns", 72 | "XcodeBeta.icns", 73 | "AppIcon.icns", 74 | "AppIconBeta.icns", 75 | "Instruments.icns", 76 | "InstrumentsBeta" 77 | ], relativeToURL: xcodeURL) 78 | } 79 | 80 | private func iconFromICNS(_ icnsFiles: [String], relativeToURL url: URL) -> NSImage? { 81 | let icnsURLs = icnsFiles.map({ url.appendingPathComponent("Contents/Resources/\($0)") }) 82 | let data = icnsURLs.compactMap { try? Data(contentsOf: $0) }.first 83 | let image = data.flatMap(NSImage.init(data:)) 84 | return image 85 | } 86 | 87 | private func icon(_ imageSetNames: [String], fromAssetCatalogRelativeToURL url: URL) -> NSImage? { 88 | let path = url.appendingPathComponent("Contents/Resources/Assets.car").path 89 | let catalog = try? AssetsCatalog(path: path) 90 | let imageSet = catalog?.imageSets.first(where: { imageSetNames.contains($0.name) }) 91 | let mutableData = NSMutableData() 92 | 93 | guard let bestImage = imageSet?.namedImages 94 | .sorted(by: { $0.size.width * CGFloat($0.scale) > $1.size.width * CGFloat($1.scale) })[1], 95 | let cgImage = bestImage 96 | ._rendition() 97 | .unslicedImage()? 98 | .takeUnretainedValue(), 99 | let destination = CGImageDestinationCreateWithData(mutableData as CFMutableData, 100 | kUTTypePNG, 101 | 1, 102 | nil) 103 | else { return nil} 104 | CGImageDestinationAddImage(destination, cgImage, nil) 105 | guard CGImageDestinationFinalize(destination) else { return nil } 106 | 107 | let nsImage = NSImage(data: mutableData as Data) 108 | return nsImage 109 | } 110 | 111 | func replaceIcon(with image: NSImage) throws { 112 | guard let appURL = appURL else { return } 113 | let iconChangeSuccessful = NSWorkspace.shared.setIcon(image, 114 | forFile: appURL.path, 115 | options: []) 116 | if iconChangeSuccessful { return } 117 | 118 | let pasteboard = NSPasteboard.withUniqueName() 119 | pasteboard.declareTypes([.fileURL], owner: nil) 120 | (appURL as NSURL).write(to: pasteboard) 121 | 122 | throw XcodeManagerError(errorType: .needsToChangeInGetInfo(appName: appName), 123 | recoveryAction: RecoveryAction(title: "Open \"\(appName) Info\" dialog") { 124 | if NSPerformService("Finder/Show Info", pasteboard) { 125 | let rep = image.tiffRepresentation 126 | let generalPasteboard = NSPasteboard.general 127 | generalPasteboard.clearContents() 128 | generalPasteboard.setData(rep, forType: .tiff) 129 | } 130 | }) 131 | } 132 | 133 | func restoreDefaultIcon() throws { 134 | guard let appURL = appURL else { return } 135 | let iconChangeSuccessful = NSWorkspace.shared.setIcon(nil, 136 | forFile: appURL.path, 137 | options: []) 138 | if iconChangeSuccessful { return } 139 | 140 | let pasteboard = NSPasteboard.withUniqueName() 141 | pasteboard.declareTypes([.fileURL], owner: nil) 142 | (appURL as NSURL).write(to: pasteboard) 143 | 144 | throw XcodeManagerError(errorType: .needsToRemoveInGetInfo(appName: appName), 145 | recoveryAction: RecoveryAction(title: "Open \"\(appName) Info\" dialog") { 146 | NSPerformService("Finder/Show Info", pasteboard) 147 | }) 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /XcoatOfPaint/acextract/AssetsCatalog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CatalogReader.swift 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright (c) 2014 Bartosz Janda 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | import Foundation 27 | 28 | enum AssetsCatalogError: Error { 29 | case fileDoesntExists 30 | case cannotOpenAssetsCatalog 31 | } 32 | 33 | struct AssetsCatalog { 34 | // MARK: Properties 35 | let filePath: String 36 | let catalog: CUICatalog 37 | 38 | /** 39 | Returns all image sets from assets catalog. 40 | 41 | - returns: List of image sets. 42 | */ 43 | var imageSets: [ImageSet] { 44 | let array = self.catalog.allImageNames() 45 | var swiftArray = [ImageSet]() 46 | for string in array { 47 | swiftArray.append(imageSet(withName: String(string))) 48 | } 49 | return swiftArray 50 | } 51 | 52 | // MARK: Initialization 53 | init(path: String) throws { 54 | let filePath = (path as NSString).expandingTildeInPath 55 | guard FileManager.default.fileExists(atPath: filePath) else { 56 | throw AssetsCatalogError.fileDoesntExists 57 | } 58 | 59 | let url = NSURL(fileURLWithPath: filePath) 60 | self.filePath = filePath 61 | 62 | do { 63 | self.catalog = try CUICatalog(url: url as URL) 64 | } catch { 65 | throw AssetsCatalogError.cannotOpenAssetsCatalog 66 | } 67 | } 68 | 69 | // MARK: Methods 70 | /** 71 | Return image set with given name. 72 | 73 | - parameter name: Name of image set. 74 | 75 | - returns: Image set with given name. 76 | */ 77 | func imageSet(withName name: String) -> ImageSet { 78 | let images = self.catalog.images(withName: name) 79 | var swiftArray = [CUINamedImage]() 80 | for item in images { 81 | if let image = item as? CUINamedImage { 82 | swiftArray.append(image) 83 | } 84 | } 85 | return ImageSet(name: name, namedImages: swiftArray) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /XcoatOfPaint/acextract/CoreUI.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreUI.h 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright (c) 2014 Bartosz Janda 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | @import Foundation; 27 | @import CoreGraphics; 28 | 29 | // Hierarchy: 30 | // - CUICatalog: 31 | // - imagesWithName (CUINamedLookup, CUINamedImage); wrapper around CUIRenditionKey and CUIThemeRendition? 32 | // - _rendition (CUIThemeRendition) 33 | // - sliceInformation (CUIRenditionSliceInformation) 34 | // - unslicedImage (CGImageRef) 35 | // - pdfDocument (CGPDFDocument) 36 | // - data (NSData) 37 | // - renditionKey (CUIRenditionKey); contains usefull numerical information 38 | // 39 | // - baseKey (CUIRenditionKey) 40 | 41 | typedef NS_ENUM(NSInteger, CUIDeviceIdiom) { 42 | CUIDeviceIdiomUniversal = 0, 43 | CUIDeviceIdiomIPhone = 1, 44 | CUIDeviceIdiomIPad = 2, 45 | CUIDeviceIdiomAppleTV = 3, 46 | CUIDeviceIdiomAppleWatch = 5, 47 | }; 48 | 49 | typedef NS_ENUM(NSUInteger, CUISubtype) { 50 | CUISubtypeNormal = 0, 51 | CUISubtypeAppleWatch38 = 320, 52 | CUISubtypeAppleWatch42 = 384, 53 | CUISubtypeIPhone4Inch = 568, 54 | }; 55 | 56 | typedef NS_ENUM(NSInteger, CUIUserInterfaceSizeClass) { 57 | CUIUserInterfaceSizeClassAny = 0, 58 | CUIUserInterfaceSizeClassCompact = 1, 59 | CUIUserInterfaceSizeClassRegular = 2, 60 | }; 61 | 62 | typedef NS_ENUM(NSInteger, CUIRenderMode) { 63 | CUIRenderModeOriginal = 0, 64 | CUIRenderModeTemplate = 1, 65 | CUIRenderModeDefault = 2, 66 | }; 67 | 68 | typedef NS_ENUM(NSInteger, CUIResizingMode) { 69 | CUIResizingModeTiles = 0, 70 | CUIResizingModeStretches = 1, 71 | }; 72 | 73 | typedef NS_ENUM(NSInteger, CUIImageType) { 74 | CUIImageTypeNone = 0, 75 | CUIImageTypeHorizontal = 1, 76 | CUIImageTypeVertical = 2, 77 | CUIImageTypeHorizontalAndVertical = 3, 78 | }; 79 | 80 | typedef NS_ENUM(NSInteger, CUIGraphicalClass) { 81 | CUIGraphicalClassDefault = 0, 82 | CUIGraphicalClassMetal1v2 = 1, 83 | CUIGraphicalClassMetal2v2 = 2, 84 | CUIGraphicalClassMetal3v1 = 3, 85 | }; 86 | 87 | typedef NS_ENUM(NSInteger, CUIMemoryClass) { 88 | CUIMemoryClassDefault = 0, 89 | CUIMemoryClassMemory1GB = 1, 90 | CUIMemoryClassMemory2GB = 2, 91 | CUIMemoryClassMemory4GB = 3, 92 | }; 93 | 94 | @class CUIRenditionSliceInformation; 95 | 96 | @interface CUIRenditionKey : NSObject 97 | 98 | - (CUIGraphicalClass)themeGraphicsClass; 99 | - (CUIMemoryClass)themeMemoryClass; 100 | 101 | @end 102 | 103 | @interface CUIThemeRendition : NSObject 104 | 105 | - (nonnull NSString *)name; 106 | - (CUIImageType)type; 107 | - (unsigned int)subtype; 108 | - (nullable NSString *)utiType; 109 | - (nullable NSData *)data; 110 | - (nullable CGPDFDocumentRef)pdfDocument; 111 | - (nullable CUIRenditionSliceInformation *)sliceInformation; 112 | - (nullable CGImageRef)unslicedImage; 113 | 114 | @end 115 | 116 | @interface CUIRenditionSliceInformation : NSObject 117 | 118 | @property(readonly, nonatomic) NSEdgeInsets edgeInsets; 119 | @property(readonly, nonatomic) struct CGRect destinationRect; 120 | @property(readonly, nonatomic) CUIImageType renditionType; 121 | - (struct CGSize)_bottomRightCapSize; 122 | - (struct CGSize)_topLeftCapSize; 123 | - (nonnull NSString *)description; 124 | 125 | @end 126 | 127 | @interface CUINamedLookup : NSObject 128 | 129 | @property(copy, nonatomic, nonnull) NSString *name; 130 | @property(readonly, nonatomic) BOOL representsOnDemandContent; 131 | - (nonnull CUIRenditionKey *)renditionKey; 132 | - (nonnull NSString *)renditionName; 133 | - (nonnull CUIThemeRendition *)_rendition; 134 | 135 | @end 136 | 137 | @interface CUINamedImage : CUINamedLookup 138 | // Image: 139 | // - _rendition().unslicedImage() 140 | // 141 | // Device idiom: 142 | // - idiom() 143 | // - subtype() 144 | // - scale 145 | // 146 | // Size class: 147 | // - sizeClassVertical() 148 | // - sizeClassHorizontal() 149 | // 150 | // Graphical class: 151 | // - graphicsClass() 152 | // - baseKey().themeGraphicsClass() 153 | // - renditionKey().themeGraphicsClass() 154 | // 155 | // Alignment: 156 | // - hasAlignmentInformation 157 | // - alignmentEdgeInsets 158 | // 159 | // Slices: 160 | // - hasSliceInformation 161 | // - resizingMode 162 | // - imageType 163 | // - _rendition().type() 164 | // - _rendition().sliceInformation()?.renditionType 165 | // - _rendition().sliceInformation()?.edgeInsets 166 | // 167 | // Template: 168 | // - isTemplate 169 | // - templateRenderingMode 170 | 171 | @property(readonly, nonatomic) int exifOrientation; 172 | @property(readonly, nonatomic) CUIRenderMode templateRenderingMode; 173 | @property(readonly, nonatomic) BOOL isTemplate; 174 | @property(readonly, nonatomic) BOOL isVectorBased; 175 | @property(readonly, nonatomic) BOOL hasAlignmentInformation; 176 | @property(readonly, nonatomic) BOOL hasSliceInformation; 177 | @property(readonly, nonatomic) CUIResizingMode resizingMode; 178 | @property(readonly, nonatomic) int blendMode; 179 | @property(readonly, nonatomic) double opacity; 180 | @property(readonly, nonatomic) NSEdgeInsets alignmentEdgeInsets; 181 | @property(readonly, nonatomic) NSEdgeInsets edgeInsets; 182 | @property(readonly, nonatomic) CUIImageType imageType; 183 | @property(readonly, nonatomic) double scale; 184 | @property(readonly, nonatomic) struct CGSize size; 185 | @property(readonly, nonatomic, nonnull) struct CGImage *image; 186 | - (CUIUserInterfaceSizeClass)sizeClassVertical; 187 | - (CUIUserInterfaceSizeClass)sizeClassHorizontal; 188 | - (CUISubtype)subtype; 189 | - (CUIDeviceIdiom)idiom; 190 | 191 | - (nonnull CUIRenditionKey *)baseKey; 192 | - (CUIGraphicalClass)graphicsClass; 193 | - (CUIMemoryClass)memoryClass; 194 | 195 | 196 | @end 197 | 198 | @interface CUICatalog : NSObject 199 | 200 | - (nullable instancetype)initWithURL:(nonnull NSURL *)url error:(NSError *_Nullable __autoreleasing *_Nullable)error; 201 | - (nonnull NSArray *)allImageNames; 202 | //- (nonnull NSArray *)imagesWithName:(nonnull NSString *)name; 203 | - (nonnull NSArray *)imagesWithName:(nonnull NSString *)name; 204 | @end 205 | -------------------------------------------------------------------------------- /XcoatOfPaint/acextract/CoreUI_Swift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreUI_Swift.swift 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright (c) 2014 Bartosz Janda 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | // swiftlint:disable file_length 27 | import Foundation 28 | 29 | // MARK: - Protocols 30 | protocol NameStringConvertible { 31 | var name: String { get } 32 | } 33 | 34 | protocol ValueCorrectness { 35 | var correct: Bool { get } 36 | } 37 | 38 | protocol IncorrectValueAssertion { 39 | func assertIncorrectValue() -> Bool 40 | } 41 | 42 | extension IncorrectValueAssertion where Self: RawRepresentable & ValueCorrectness { 43 | func assertIncorrectValue() -> Bool { 44 | assert(correct, "Incorrect value: \(self) - \(rawValue)") 45 | return correct 46 | } 47 | } 48 | 49 | protocol AllValues { 50 | static var allValues: [Self] { get } 51 | } 52 | 53 | // MARK: - Custom types 54 | enum ScaleFactor { 55 | case scale1x 56 | case scale2x 57 | case scale3x 58 | } 59 | 60 | extension ScaleFactor: ExpressibleByFloatLiteral { 61 | init(floatLiteral value: Double) { 62 | switch value { 63 | case 1.0: self = .scale1x 64 | case 2.0: self = .scale2x 65 | case 3.0: self = .scale3x 66 | default: fatalError() 67 | } 68 | } 69 | } 70 | 71 | extension ScaleFactor: NameStringConvertible { 72 | var name: String { 73 | switch self { 74 | case .scale1x: return "" 75 | case .scale2x: return "@2x" 76 | case .scale3x: return "@3x" 77 | } 78 | } 79 | } 80 | 81 | // MARK: - CoreUI Extensions 82 | extension NSEdgeInsets: Equatable { } 83 | public func == (lhs: NSEdgeInsets, rhs: NSEdgeInsets) -> Bool { 84 | return lhs.top == rhs.top 85 | && lhs.left == rhs.left 86 | && lhs.bottom == rhs.bottom 87 | && lhs.right == rhs.right 88 | } 89 | 90 | // MARK: CUIDeviceIdiom 91 | extension CUIDeviceIdiom: NameStringConvertible { 92 | var name: String { 93 | // Idiom. 94 | switch self { 95 | case .universal: return "" 96 | case .iPhone: return "~iphone" 97 | case .iPad: return "~ipad" 98 | case .appleTV: return "~tv" 99 | case .appleWatch: return "~watch" 100 | default: return "" 101 | } 102 | } 103 | } 104 | 105 | extension CUIDeviceIdiom: ValueCorrectness, IncorrectValueAssertion { 106 | var correct: Bool { 107 | switch self { 108 | case .universal: return rawValue == CUIDeviceIdiom.universal.rawValue 109 | case .iPhone: return rawValue == CUIDeviceIdiom.iPhone.rawValue 110 | case .iPad: return rawValue == CUIDeviceIdiom.iPad.rawValue 111 | case .appleTV: return rawValue == CUIDeviceIdiom.appleTV.rawValue 112 | case .appleWatch: return rawValue == CUIDeviceIdiom.appleWatch.rawValue 113 | default: return true 114 | } 115 | } 116 | } 117 | 118 | extension CUIDeviceIdiom: AllValues { 119 | static var allValues: [CUIDeviceIdiom] { 120 | return [.universal, .iPhone, .iPad, .appleTV, .appleWatch] 121 | } 122 | } 123 | 124 | extension CUIDeviceIdiom: CustomStringConvertible { 125 | public var description: String { 126 | switch self { 127 | case .universal: return "universal" 128 | case .iPhone: return "iPhone" 129 | case .iPad: return "iPad" 130 | case .appleTV: return "AppleTV" 131 | case .appleWatch: return "AppleWatch" 132 | default: return "" 133 | } 134 | } 135 | } 136 | 137 | // MARK: CUISubtype 138 | extension CUISubtype: NameStringConvertible { 139 | var name: String { 140 | switch self { 141 | case .normal: return "" 142 | case .iPhone4Inch: return "-568h" 143 | case .appleWatch38: return "-38" 144 | case .appleWatch42: return "-42" 145 | default: return "" 146 | } 147 | } 148 | } 149 | 150 | extension CUISubtype: ValueCorrectness, IncorrectValueAssertion { 151 | var correct: Bool { 152 | switch self { 153 | case .normal: return rawValue == CUISubtype.normal.rawValue 154 | case .iPhone4Inch: return rawValue == CUISubtype.iPhone4Inch.rawValue 155 | case .appleWatch38: return rawValue == CUISubtype.appleWatch38.rawValue 156 | case .appleWatch42: return rawValue == CUISubtype.appleWatch42.rawValue 157 | default: return true 158 | } 159 | } 160 | } 161 | 162 | extension CUISubtype: CustomStringConvertible { 163 | public var description: String { 164 | switch self { 165 | case .normal: 166 | return "normal" 167 | case .appleWatch38: 168 | return "-38" 169 | case .appleWatch42: 170 | return "-42" 171 | case .iPhone4Inch: 172 | return "-568h" 173 | default: return "" 174 | } 175 | } 176 | } 177 | 178 | // MARK: CUIUserInterfaceSizeClass 179 | extension CUIUserInterfaceSizeClass: NameStringConvertible { 180 | var name: String { 181 | switch self { 182 | case .any: return "*" 183 | case .compact: return "-" 184 | case .regular: return "+" 185 | default: return "" 186 | } 187 | } 188 | } 189 | 190 | extension CUIUserInterfaceSizeClass: ValueCorrectness, IncorrectValueAssertion { 191 | var correct: Bool { 192 | switch self { 193 | case .any: return rawValue == CUIUserInterfaceSizeClass.any.rawValue 194 | case .compact: return rawValue == CUIUserInterfaceSizeClass.compact.rawValue 195 | case .regular: return rawValue == CUIUserInterfaceSizeClass.regular.rawValue 196 | default: return true 197 | } 198 | } 199 | } 200 | 201 | extension CUIUserInterfaceSizeClass: CustomStringConvertible { 202 | public var description: String { 203 | switch self { 204 | case .any: return "any" 205 | case .compact: return "compact" 206 | case .regular: return "regular" 207 | default: return "" 208 | } 209 | } 210 | } 211 | 212 | // MARK: CUIRenderMode 213 | extension CUIRenderMode: ValueCorrectness, IncorrectValueAssertion { 214 | var correct: Bool { 215 | switch self { 216 | case .original: return rawValue == CUIRenderMode.original.rawValue 217 | case .template: return rawValue == CUIRenderMode.template.rawValue 218 | case .default: return rawValue == CUIRenderMode.default.rawValue 219 | default: return true 220 | } 221 | } 222 | } 223 | 224 | extension CUIRenderMode: CustomStringConvertible { 225 | public var description: String { 226 | switch self { 227 | case .original: return "original" 228 | case .template: return "template" 229 | case .default: return "default" 230 | default: return "" 231 | } 232 | } 233 | } 234 | 235 | // MARK: CUIResizingMode 236 | extension CUIResizingMode: ValueCorrectness, IncorrectValueAssertion { 237 | var correct: Bool { 238 | switch self { 239 | case .tiles: return rawValue == CUIResizingMode.tiles.rawValue 240 | case .stretches: return rawValue == CUIResizingMode.stretches.rawValue 241 | default: return true 242 | } 243 | } 244 | } 245 | 246 | extension CUIResizingMode: CustomStringConvertible { 247 | public var description: String { 248 | switch self { 249 | case .tiles: return "tiles" 250 | case .stretches: return "stretches" 251 | default: return "" 252 | } 253 | } 254 | } 255 | 256 | // MARK: CUIImageType 257 | extension CUIImageType: ValueCorrectness, IncorrectValueAssertion { 258 | var correct: Bool { 259 | switch self { 260 | case .none: return rawValue == CUIImageType.none.rawValue 261 | case .horizontal: return rawValue == CUIImageType.horizontal.rawValue 262 | case .vertical: return rawValue == CUIImageType.vertical.rawValue 263 | case .horizontalAndVertical: return rawValue == CUIImageType.horizontalAndVertical.rawValue 264 | default: return true 265 | } 266 | } 267 | } 268 | 269 | extension CUIImageType: CustomStringConvertible { 270 | public var description: String { 271 | switch self { 272 | case .none: return "none" 273 | case .horizontal: return "horizontal" 274 | case .vertical: return "vertical" 275 | case .horizontalAndVertical: return "horizontal & vertical" 276 | default: return "" 277 | } 278 | } 279 | } 280 | 281 | // MARK: CUIGraphicalClass 282 | extension CUIGraphicalClass: NameStringConvertible { 283 | var name: String { 284 | switch self { 285 | case .default: return "" 286 | case .metal1v2: return "1v2" 287 | case .metal2v2: return "2v2" 288 | case .metal3v1: return "3v1" 289 | default: return "" 290 | } 291 | } 292 | } 293 | 294 | extension CUIGraphicalClass: ValueCorrectness, IncorrectValueAssertion { 295 | var correct: Bool { 296 | switch self { 297 | case .default: return rawValue == CUIGraphicalClass.default.rawValue 298 | case .metal1v2: return rawValue == CUIGraphicalClass.metal1v2.rawValue 299 | case .metal2v2: return rawValue == CUIGraphicalClass.metal2v2.rawValue 300 | case .metal3v1: return rawValue == CUIGraphicalClass.metal3v1.rawValue 301 | default: return true 302 | } 303 | } 304 | } 305 | 306 | extension CUIGraphicalClass: CustomStringConvertible { 307 | public var description: String { 308 | switch self { 309 | case .default: return "default" 310 | case .metal1v2: return "Metal 1v2" 311 | case .metal2v2: return "Metal 2v2" 312 | case .metal3v1: return "Metal 3v1" 313 | default: return "" 314 | } 315 | } 316 | } 317 | 318 | // MARK: CUIMemoryClass 319 | extension CUIMemoryClass: NameStringConvertible { 320 | var name: String { 321 | switch self { 322 | case .default: return "" 323 | case .memory1GB: return "1gb" 324 | case .memory2GB: return "2gb" 325 | case .memory4GB: return "4gb" 326 | default: return "" 327 | } 328 | } 329 | } 330 | 331 | extension CUIMemoryClass: ValueCorrectness, IncorrectValueAssertion { 332 | var correct: Bool { 333 | switch self { 334 | case .default: return rawValue == CUIMemoryClass.default.rawValue 335 | case .memory1GB: return rawValue == CUIMemoryClass.memory1GB.rawValue 336 | case .memory2GB: return rawValue == CUIMemoryClass.memory2GB.rawValue 337 | case .memory4GB: return rawValue == CUIMemoryClass.memory4GB.rawValue 338 | default: return true 339 | } 340 | } 341 | } 342 | 343 | extension CUIMemoryClass: CustomStringConvertible { 344 | public var description: String { 345 | switch self { 346 | case .default: return "default" 347 | case .memory1GB: return "1GB" 348 | case .memory2GB: return "2GB" 349 | case .memory4GB: return "4GB" 350 | default: return "" 351 | } 352 | } 353 | } 354 | 355 | // MARK: CUINamedImage 356 | extension CUINamedImage { 357 | var acScale: ScaleFactor { 358 | return ScaleFactor(floatLiteral: scale) 359 | } 360 | 361 | var acSizeClassString: String { 362 | switch (self.sizeClassHorizontal(), self.sizeClassVertical()) { 363 | case (.any, .any): return "" 364 | case let (horizontal, vertical): return "\(horizontal.name)\(vertical.name)" 365 | } 366 | } 367 | 368 | var acIsPDF: Bool { 369 | if self.isVectorBased && self.size == CGSize.zero { 370 | return true 371 | } 372 | return false 373 | } 374 | 375 | fileprivate var acFileExtension: String { 376 | if acIsPDF { 377 | return "pdf" 378 | } 379 | return "png" 380 | } 381 | 382 | var acImageName: String { 383 | // image size 384 | let width = self.size.width 385 | let height = self.size.height 386 | let size = width > 0 ? "\(Int(width))x\(Int(height))" : "" 387 | 388 | // Graphical class 389 | let graphics = self.graphicsClass().name 390 | 391 | // Memory class 392 | let memory = self.memoryClass().name 393 | 394 | // Size class suffix 395 | let sizeClassSuffix = acSizeClassString 396 | 397 | // Subtype (4 inch) 398 | let subtype = self.subtype().name 399 | 400 | // Scale 401 | let scale = self.scale>1 ? "@\(Int(self.scale))x" : "" 402 | 403 | // Idiom 404 | let idiom = self.idiom().name 405 | 406 | // File extension 407 | let fileExtension = acFileExtension 408 | 409 | // AppIcon76x76@2x~ipad.png 410 | return "\(self.name)\(size)\(graphics)\(memory)\(sizeClassSuffix)\(subtype)\(scale)\(idiom).\(fileExtension)" 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /XcoatOfPaint/acextract/ImageSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSet.swift 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright (c) 2014 Bartosz Janda 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | 26 | import Foundation 27 | 28 | struct ImageSet { 29 | // MARK: Properties 30 | let name: String 31 | let namedImages: [CUINamedImage] 32 | 33 | // MARK: Initialization 34 | init(name: String, namedImages: [CUINamedImage]) { 35 | self.name = name 36 | self.namedImages = namedImages 37 | } 38 | } 39 | 40 | extension ImageSet: CustomStringConvertible { 41 | var description: String { 42 | return "\(name)" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Xcoats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/Xcoats.png -------------------------------------------------------------------------------- /app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/app.png -------------------------------------------------------------------------------- /dock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerLobi/XcoatOfPaint/36069d2d25d4a4f4ecbbc69ebe629435d70c814b/dock.png --------------------------------------------------------------------------------