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