├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ ├── SkinSmoothingFilter-Package.xcscheme
│ └── SkinSmoothingFilter.xcscheme
├── Demo
├── Demo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── Demo
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── ContentView.swift
│ ├── Demo.entitlements
│ ├── DemoApp.swift
│ └── Preview Content
│ └── Preview Assets.xcassets
│ └── Contents.json
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── SkinSmoothingFilter
│ ├── Shaders.metal
│ └── SkinSmoothingFilter.swift
├── Tests
└── SkinSmoothingFilterTests
│ └── SkinSmoothingFilterTests.swift
├── sample1.JPG
└── sample2.JPG
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
92 | .DS_Store
93 | /.build
94 | /Packages
95 | xcuserdata/
96 | DerivedData/
97 | .swiftpm/configuration/registries.json
98 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
99 | .netrc
100 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SkinSmoothingFilter-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
65 |
66 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SkinSmoothingFilter.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 045DBAAB2B88C035000DC52B /* SkinSmoothingFilter in Frameworks */ = {isa = PBXBuildFile; productRef = 045DBAAA2B88C035000DC52B /* SkinSmoothingFilter */; };
11 | 048C9DDE2B7F78780029B9FE /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048C9DDD2B7F78780029B9FE /* DemoApp.swift */; };
12 | 048C9DE02B7F78780029B9FE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048C9DDF2B7F78780029B9FE /* ContentView.swift */; };
13 | 048C9DE22B7F78790029B9FE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 048C9DE12B7F78790029B9FE /* Assets.xcassets */; };
14 | 048C9DE62B7F78790029B9FE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 048C9DE52B7F78790029B9FE /* Preview Assets.xcassets */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 045DBAA82B88BF29000DC52B /* Sources */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Sources; path = ../Sources; sourceTree = ""; };
19 | 048C9DDA2B7F78770029B9FE /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 048C9DDD2B7F78780029B9FE /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; };
21 | 048C9DDF2B7F78780029B9FE /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
22 | 048C9DE12B7F78790029B9FE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23 | 048C9DE32B7F78790029B9FE /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; };
24 | 048C9DE52B7F78790029B9FE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
25 | /* End PBXFileReference section */
26 |
27 | /* Begin PBXFrameworksBuildPhase section */
28 | 048C9DD72B7F78770029B9FE /* Frameworks */ = {
29 | isa = PBXFrameworksBuildPhase;
30 | buildActionMask = 2147483647;
31 | files = (
32 | 045DBAAB2B88C035000DC52B /* SkinSmoothingFilter in Frameworks */,
33 | );
34 | runOnlyForDeploymentPostprocessing = 0;
35 | };
36 | /* End PBXFrameworksBuildPhase section */
37 |
38 | /* Begin PBXGroup section */
39 | 048C9DD12B7F78770029B9FE = {
40 | isa = PBXGroup;
41 | children = (
42 | 048C9DDC2B7F78780029B9FE /* Demo */,
43 | 048C9DDB2B7F78770029B9FE /* Products */,
44 | 04F51E712B7F7A9A00DE5D0D /* Frameworks */,
45 | );
46 | sourceTree = "";
47 | };
48 | 048C9DDB2B7F78770029B9FE /* Products */ = {
49 | isa = PBXGroup;
50 | children = (
51 | 048C9DDA2B7F78770029B9FE /* Demo.app */,
52 | );
53 | name = Products;
54 | sourceTree = "";
55 | };
56 | 048C9DDC2B7F78780029B9FE /* Demo */ = {
57 | isa = PBXGroup;
58 | children = (
59 | 048C9DDD2B7F78780029B9FE /* DemoApp.swift */,
60 | 048C9DDF2B7F78780029B9FE /* ContentView.swift */,
61 | 048C9DE12B7F78790029B9FE /* Assets.xcassets */,
62 | 048C9DE32B7F78790029B9FE /* Demo.entitlements */,
63 | 048C9DE42B7F78790029B9FE /* Preview Content */,
64 | );
65 | path = Demo;
66 | sourceTree = "";
67 | };
68 | 048C9DE42B7F78790029B9FE /* Preview Content */ = {
69 | isa = PBXGroup;
70 | children = (
71 | 048C9DE52B7F78790029B9FE /* Preview Assets.xcassets */,
72 | );
73 | path = "Preview Content";
74 | sourceTree = "";
75 | };
76 | 04F51E712B7F7A9A00DE5D0D /* Frameworks */ = {
77 | isa = PBXGroup;
78 | children = (
79 | 045DBAA82B88BF29000DC52B /* Sources */,
80 | );
81 | name = Frameworks;
82 | sourceTree = "";
83 | };
84 | /* End PBXGroup section */
85 |
86 | /* Begin PBXNativeTarget section */
87 | 048C9DD92B7F78770029B9FE /* Demo */ = {
88 | isa = PBXNativeTarget;
89 | buildConfigurationList = 048C9DE92B7F78790029B9FE /* Build configuration list for PBXNativeTarget "Demo" */;
90 | buildPhases = (
91 | 048C9DD62B7F78770029B9FE /* Sources */,
92 | 048C9DD72B7F78770029B9FE /* Frameworks */,
93 | 048C9DD82B7F78770029B9FE /* Resources */,
94 | );
95 | buildRules = (
96 | );
97 | dependencies = (
98 | );
99 | name = Demo;
100 | packageProductDependencies = (
101 | 045DBAAA2B88C035000DC52B /* SkinSmoothingFilter */,
102 | );
103 | productName = Demo;
104 | productReference = 048C9DDA2B7F78770029B9FE /* Demo.app */;
105 | productType = "com.apple.product-type.application";
106 | };
107 | /* End PBXNativeTarget section */
108 |
109 | /* Begin PBXProject section */
110 | 048C9DD22B7F78770029B9FE /* Project object */ = {
111 | isa = PBXProject;
112 | attributes = {
113 | BuildIndependentTargetsInParallel = 1;
114 | LastSwiftUpdateCheck = 1530;
115 | LastUpgradeCheck = 1530;
116 | TargetAttributes = {
117 | 048C9DD92B7F78770029B9FE = {
118 | CreatedOnToolsVersion = 15.3;
119 | };
120 | };
121 | };
122 | buildConfigurationList = 048C9DD52B7F78770029B9FE /* Build configuration list for PBXProject "Demo" */;
123 | compatibilityVersion = "Xcode 14.0";
124 | developmentRegion = en;
125 | hasScannedForEncodings = 0;
126 | knownRegions = (
127 | en,
128 | Base,
129 | );
130 | mainGroup = 048C9DD12B7F78770029B9FE;
131 | packageReferences = (
132 | 045DBAA92B88BF6C000DC52B /* XCRemoteSwiftPackageReference "SkinSmoothingFilter" */,
133 | );
134 | productRefGroup = 048C9DDB2B7F78770029B9FE /* Products */;
135 | projectDirPath = "";
136 | projectRoot = "";
137 | targets = (
138 | 048C9DD92B7F78770029B9FE /* Demo */,
139 | );
140 | };
141 | /* End PBXProject section */
142 |
143 | /* Begin PBXResourcesBuildPhase section */
144 | 048C9DD82B7F78770029B9FE /* Resources */ = {
145 | isa = PBXResourcesBuildPhase;
146 | buildActionMask = 2147483647;
147 | files = (
148 | 048C9DE62B7F78790029B9FE /* Preview Assets.xcassets in Resources */,
149 | 048C9DE22B7F78790029B9FE /* Assets.xcassets in Resources */,
150 | );
151 | runOnlyForDeploymentPostprocessing = 0;
152 | };
153 | /* End PBXResourcesBuildPhase section */
154 |
155 | /* Begin PBXSourcesBuildPhase section */
156 | 048C9DD62B7F78770029B9FE /* Sources */ = {
157 | isa = PBXSourcesBuildPhase;
158 | buildActionMask = 2147483647;
159 | files = (
160 | 048C9DE02B7F78780029B9FE /* ContentView.swift in Sources */,
161 | 048C9DDE2B7F78780029B9FE /* DemoApp.swift in Sources */,
162 | );
163 | runOnlyForDeploymentPostprocessing = 0;
164 | };
165 | /* End PBXSourcesBuildPhase section */
166 |
167 | /* Begin XCBuildConfiguration section */
168 | 048C9DE72B7F78790029B9FE /* Debug */ = {
169 | isa = XCBuildConfiguration;
170 | buildSettings = {
171 | ALWAYS_SEARCH_USER_PATHS = NO;
172 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
173 | CLANG_ANALYZER_NONNULL = YES;
174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
176 | CLANG_ENABLE_MODULES = YES;
177 | CLANG_ENABLE_OBJC_ARC = YES;
178 | CLANG_ENABLE_OBJC_WEAK = YES;
179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
180 | CLANG_WARN_BOOL_CONVERSION = YES;
181 | CLANG_WARN_COMMA = YES;
182 | CLANG_WARN_CONSTANT_CONVERSION = YES;
183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
186 | CLANG_WARN_EMPTY_BODY = YES;
187 | CLANG_WARN_ENUM_CONVERSION = YES;
188 | CLANG_WARN_INFINITE_RECURSION = YES;
189 | CLANG_WARN_INT_CONVERSION = YES;
190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
196 | CLANG_WARN_STRICT_PROTOTYPES = YES;
197 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | COPY_PHASE_STRIP = NO;
202 | DEBUG_INFORMATION_FORMAT = dwarf;
203 | ENABLE_STRICT_OBJC_MSGSEND = YES;
204 | ENABLE_TESTABILITY = YES;
205 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
206 | GCC_C_LANGUAGE_STANDARD = gnu17;
207 | GCC_DYNAMIC_NO_PIC = NO;
208 | GCC_NO_COMMON_BLOCKS = YES;
209 | GCC_OPTIMIZATION_LEVEL = 0;
210 | GCC_PREPROCESSOR_DEFINITIONS = (
211 | "DEBUG=1",
212 | "$(inherited)",
213 | );
214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
216 | GCC_WARN_UNDECLARED_SELECTOR = YES;
217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
218 | GCC_WARN_UNUSED_FUNCTION = YES;
219 | GCC_WARN_UNUSED_VARIABLE = YES;
220 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
221 | MTL_COMPILER_FLAGS = "-fcikernel";
222 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
223 | MTL_FAST_MATH = YES;
224 | ONLY_ACTIVE_ARCH = YES;
225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
227 | };
228 | name = Debug;
229 | };
230 | 048C9DE82B7F78790029B9FE /* Release */ = {
231 | isa = XCBuildConfiguration;
232 | buildSettings = {
233 | ALWAYS_SEARCH_USER_PATHS = NO;
234 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
235 | CLANG_ANALYZER_NONNULL = YES;
236 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
237 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
238 | CLANG_ENABLE_MODULES = YES;
239 | CLANG_ENABLE_OBJC_ARC = YES;
240 | CLANG_ENABLE_OBJC_WEAK = YES;
241 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
242 | CLANG_WARN_BOOL_CONVERSION = YES;
243 | CLANG_WARN_COMMA = YES;
244 | CLANG_WARN_CONSTANT_CONVERSION = YES;
245 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
246 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
247 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
248 | CLANG_WARN_EMPTY_BODY = YES;
249 | CLANG_WARN_ENUM_CONVERSION = YES;
250 | CLANG_WARN_INFINITE_RECURSION = YES;
251 | CLANG_WARN_INT_CONVERSION = YES;
252 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
253 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
254 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
256 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
261 | CLANG_WARN_UNREACHABLE_CODE = YES;
262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
268 | GCC_C_LANGUAGE_STANDARD = gnu17;
269 | GCC_NO_COMMON_BLOCKS = YES;
270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
272 | GCC_WARN_UNDECLARED_SELECTOR = YES;
273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
274 | GCC_WARN_UNUSED_FUNCTION = YES;
275 | GCC_WARN_UNUSED_VARIABLE = YES;
276 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
277 | MTL_COMPILER_FLAGS = "-fcikernel";
278 | MTL_ENABLE_DEBUG_INFO = NO;
279 | MTL_FAST_MATH = YES;
280 | SWIFT_COMPILATION_MODE = wholemodule;
281 | };
282 | name = Release;
283 | };
284 | 048C9DEA2B7F78790029B9FE /* Debug */ = {
285 | isa = XCBuildConfiguration;
286 | buildSettings = {
287 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
288 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
289 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
290 | CODE_SIGN_STYLE = Automatic;
291 | CURRENT_PROJECT_VERSION = 1;
292 | DEVELOPMENT_ASSET_PATHS = "";
293 | DEVELOPMENT_TEAM = 64T4PJ8WCZ;
294 | ENABLE_HARDENED_RUNTIME = YES;
295 | ENABLE_PREVIEWS = YES;
296 | GENERATE_INFOPLIST_FILE = YES;
297 | INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Used for saving photos.";
298 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
299 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
300 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
301 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
302 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
303 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
304 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
305 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
306 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
307 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
308 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
309 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
310 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
311 | MACOSX_DEPLOYMENT_TARGET = 14.2;
312 | MARKETING_VERSION = 1.0;
313 | PRODUCT_BUNDLE_IDENTIFIER = com.shima.apps.Demo;
314 | PRODUCT_NAME = "$(TARGET_NAME)";
315 | SDKROOT = auto;
316 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
317 | SWIFT_EMIT_LOC_STRINGS = YES;
318 | SWIFT_VERSION = 5.0;
319 | TARGETED_DEVICE_FAMILY = "1,2";
320 | };
321 | name = Debug;
322 | };
323 | 048C9DEB2B7F78790029B9FE /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
327 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
328 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
329 | CODE_SIGN_STYLE = Automatic;
330 | CURRENT_PROJECT_VERSION = 1;
331 | DEVELOPMENT_ASSET_PATHS = "";
332 | DEVELOPMENT_TEAM = 64T4PJ8WCZ;
333 | ENABLE_HARDENED_RUNTIME = YES;
334 | ENABLE_PREVIEWS = YES;
335 | GENERATE_INFOPLIST_FILE = YES;
336 | INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Used for saving photos.";
337 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
338 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
339 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
340 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
341 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
342 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
343 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
344 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
345 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
346 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
347 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
348 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
349 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
350 | MACOSX_DEPLOYMENT_TARGET = 14.2;
351 | MARKETING_VERSION = 1.0;
352 | PRODUCT_BUNDLE_IDENTIFIER = com.shima.apps.Demo;
353 | PRODUCT_NAME = "$(TARGET_NAME)";
354 | SDKROOT = auto;
355 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
356 | SWIFT_EMIT_LOC_STRINGS = YES;
357 | SWIFT_VERSION = 5.0;
358 | TARGETED_DEVICE_FAMILY = "1,2";
359 | };
360 | name = Release;
361 | };
362 | /* End XCBuildConfiguration section */
363 |
364 | /* Begin XCConfigurationList section */
365 | 048C9DD52B7F78770029B9FE /* Build configuration list for PBXProject "Demo" */ = {
366 | isa = XCConfigurationList;
367 | buildConfigurations = (
368 | 048C9DE72B7F78790029B9FE /* Debug */,
369 | 048C9DE82B7F78790029B9FE /* Release */,
370 | );
371 | defaultConfigurationIsVisible = 0;
372 | defaultConfigurationName = Release;
373 | };
374 | 048C9DE92B7F78790029B9FE /* Build configuration list for PBXNativeTarget "Demo" */ = {
375 | isa = XCConfigurationList;
376 | buildConfigurations = (
377 | 048C9DEA2B7F78790029B9FE /* Debug */,
378 | 048C9DEB2B7F78790029B9FE /* Release */,
379 | );
380 | defaultConfigurationIsVisible = 0;
381 | defaultConfigurationName = Release;
382 | };
383 | /* End XCConfigurationList section */
384 |
385 | /* Begin XCRemoteSwiftPackageReference section */
386 | 045DBAA92B88BF6C000DC52B /* XCRemoteSwiftPackageReference "SkinSmoothingFilter" */ = {
387 | isa = XCRemoteSwiftPackageReference;
388 | repositoryURL = "https://github.com/shima11/SkinSmoothingFilter";
389 | requirement = {
390 | branch = main;
391 | kind = branch;
392 | };
393 | };
394 | /* End XCRemoteSwiftPackageReference section */
395 |
396 | /* Begin XCSwiftPackageProductDependency section */
397 | 045DBAAA2B88C035000DC52B /* SkinSmoothingFilter */ = {
398 | isa = XCSwiftPackageProductDependency;
399 | package = 045DBAA92B88BF6C000DC52B /* XCRemoteSwiftPackageReference "SkinSmoothingFilter" */;
400 | productName = SkinSmoothingFilter;
401 | };
402 | /* End XCSwiftPackageProductDependency section */
403 | };
404 | rootObject = 048C9DD22B7F78770029B9FE /* Project object */;
405 | }
406 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "6a197446f8ae23b3f56abea04d7c1c91a4d1fbcd3b7f75f49273fd0a689a4b07",
3 | "pins" : [
4 | {
5 | "identity" : "skinsmoothingfilter",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/shima11/SkinSmoothingFilter",
8 | "state" : {
9 | "branch" : "main",
10 | "revision" : "ce14eb44def22bbe3430176fce87dffbc60e1978"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/Demo/Demo/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 |
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/Demo/ContentView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 | import SkinSmoothingFilter
4 | import PhotosUI
5 |
6 | @available(iOS 16.0, *)
7 | struct ContentView: View {
8 |
9 | @State var selectedPhotoItem: PhotosPickerItem? = nil
10 | @State var originalImage: UIImage? = nil
11 | @State var inputAmount: Double = 0.5
12 | @State var inputSharpness: Double = 0.3
13 | @State var inputRadius: Double = 8.0
14 | @State var edittedImage: UIImage? = nil
15 |
16 | @State var onPress: Bool = false
17 |
18 | var body: some View {
19 | NavigationView {
20 | ScrollView {
21 | VStack {
22 |
23 | if let originalImage = originalImage {
24 | Image(uiImage: originalImage)
25 | .resizable()
26 | .scaledToFit()
27 | .overlay(
28 | ZStack {
29 | if let edittedImage = edittedImage {
30 | Image(uiImage: edittedImage)
31 | .resizable()
32 | .scaledToFit()
33 | }
34 | }
35 | .opacity(onPress ? 0 : 1)
36 | )
37 | .gesture(
38 | DragGesture(minimumDistance: 0)
39 | .onChanged { _ in
40 | onPress = true
41 | }
42 | .onEnded { _ in
43 | onPress = false
44 | }
45 | )
46 | }
47 |
48 | if let edittedImage = edittedImage {
49 | Button("Save") {
50 | save(image: edittedImage)
51 | }
52 | }
53 |
54 | VStack {
55 | Text("amount: \(inputAmount)")
56 | Slider(value: $inputAmount, in: 0...5, step: 0.01)
57 | Text("sharpness: \(inputSharpness)")
58 | Slider(value: $inputSharpness, in: 0...5, step: 0.01)
59 | Text("radius: \(inputRadius)")
60 | Slider(value: $inputRadius, in: 0...15, step: 0.1)
61 | }
62 | .padding(.top, 24)
63 | .padding(.horizontal, 24)
64 |
65 | }
66 | .onChange(of: originalImage, perform: { _ in
67 | perform()
68 | })
69 | .onChange(of: [inputAmount, inputSharpness, inputRadius], perform: { _ in
70 | perform()
71 | })
72 | .onChange(of: selectedPhotoItem) { selectedPhotoItem in
73 | Task {
74 | do {
75 | if let data = try await selectedPhotoItem?.loadTransferable(type: Data.self) {
76 | if let uiImage = UIImage(data: data) {
77 | originalImage = uiImage
78 | }
79 | }
80 | } catch {
81 | print(error)
82 | }
83 | }
84 | }
85 | }
86 | .navigationTitle("SkinSmoothingFilter Demo")
87 | .navigationBarTitleDisplayMode(.inline)
88 | .toolbar {
89 | ToolbarItem(placement: .topBarTrailing) {
90 | PhotosPicker(selection: $selectedPhotoItem, matching: .images) {
91 | Image(systemName: "photo")
92 | }
93 | }
94 | }
95 | }
96 | }
97 |
98 | private func perform() {
99 | guard let uiImage = originalImage else { return }
100 | Task {
101 | let ciImage = CIImage(image: uiImage)!
102 |
103 | let filter = SkinSmoothingFilter()
104 | filter.inputImage = ciImage
105 | filter.inputAmount = inputAmount
106 | filter.inputSharpness = inputSharpness
107 | filter.inputRadius = inputRadius
108 |
109 | if let result = filter.outputImage?.toUIImage() {
110 | edittedImage = result
111 | }
112 | }
113 | }
114 |
115 | private func save(image: UIImage) {
116 | UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
117 | }
118 | }
119 |
120 | extension CIImage {
121 | func toCGImage() -> CGImage? {
122 | let context = { CIContext(options: nil) }()
123 | return context.createCGImage(self, from: self.extent)
124 | }
125 |
126 | func toUIImage() -> UIImage? {
127 | guard let cgImage = self.toCGImage() else { return nil }
128 | return UIImage(cgImage: cgImage)
129 | }
130 | }
131 |
132 | #Preview {
133 | if #available(iOS 16.0, *) {
134 | ContentView()
135 | } else {
136 | EmptyView()
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/Demo/DemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoApp.swift
3 | // Demo
4 | //
5 | // Created by Jinsei Shima on 2024/02/16.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct DemoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | if #available(iOS 16.0, *) {
15 | ContentView()
16 | } else {
17 | Text("WIP")
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/Demo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 shima
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SkinSmoothingFilter",
8 | platforms: [
9 | .iOS(.v15),
10 | .macOS(.v11),
11 | ],
12 | products: [
13 | .library(
14 | name: "SkinSmoothingFilter",
15 | targets: ["SkinSmoothingFilter"]
16 | ),
17 | ],
18 | targets: [
19 | .target(
20 | name: "SkinSmoothingFilter"
21 | ),
22 | .testTarget(
23 | name: "SkinSmoothingFilterTests",
24 | dependencies: ["SkinSmoothingFilter"]
25 | ),
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SkinSmoothingFilter
2 |
3 | A lightweight SkinSmoothing Filter using Metal and CoreImage.
4 |
5 | | Before | After |
6 | | ------ | ----- |
7 | |  |  |
8 |
9 |
10 | # Requirements
11 |
12 | iOS 15+, macOS 11+
13 |
14 | # Installation
15 |
16 | You can add SkinSmoothingFilter to your project via Swift Package Manager by adding the following to your `Package.swift` file:
17 |
18 | ```swift
19 | dependencies: [
20 | .package(url: "https://github.com/shima11/SkinSmoothingFilter.git", .upToNextMajor(from: "1.0.0"))
21 | ]
22 | ```
23 |
24 | # Usage
25 |
26 | To use the SkinSmoothingFilter, you first need to import the package into your project:
27 |
28 | ```swift
29 | import SkinSmoothingFilter
30 | ```
31 |
32 | Then, create an instance of SkinSmoothingFilter, set your desired parameters, and process your images like so:
33 |
34 | ```swift
35 | let filter = SkinSmoothingFilter()
36 | filter.inputImage = ciImage // Your input CIImage
37 | filter.inputAmount = inputAmount // Smoothing amount
38 | filter.inputSharpness = inputSharpness // Image sharpness
39 | filter.inputRadius = inputRadius // Blur radius
40 |
41 | let result = filter.outputImage // Processed CIImage
42 | ```
43 |
44 | # Contributing
45 |
46 | Contributions are welcome! Please feel free to submit a pull request or open an issue if you have any ideas, bug reports, or suggestions.
47 |
48 | # License
49 |
50 | SkinSmoothingFilter is available under the MIT license. See the LICENSE file for more info.
51 |
--------------------------------------------------------------------------------
/Sources/SkinSmoothingFilter/Shaders.metal:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | using namespace metal;
5 |
6 | [[stitchable]] float4 highpass(coreimage::sample_t image, coreimage::sample_t blurredImage) {
7 | return float4(float3(image.rgb - blurredImage.rgb) + 0.5, image.a);
8 | }
9 |
10 | float overlayBlend(float base, float blend) {
11 | return base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend));
12 | }
13 |
14 | [[stitchable]] float4 greenBlueOverlayBlendKernel(coreimage::sample_t image) {
15 |
16 | float green = image.g;
17 | float blue = image.b;
18 |
19 | float blendedGreen = overlayBlend(green, green);
20 | float blendedBlue = overlayBlend(blue, blue);
21 |
22 | return float4(0, blendedGreen, blendedBlue, image.a);
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/SkinSmoothingFilter/SkinSmoothingFilter.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreImage
3 |
4 | public final class SkinSmoothingFilter: CIFilter {
5 |
6 | public var inputImage: CIImage?
7 | public var inputAmount: Double = 0.5
8 | public var inputSharpness: Double = 0.3
9 | public var inputRadius: Double = 8.0
10 |
11 | override public var outputImage: CIImage? {
12 | guard let inputImage = self.inputImage else { return nil }
13 |
14 | let colorFilter = ColorOverlayBlendFilter()
15 | colorFilter.inputImage = inputImage
16 |
17 | let highpassFilter = HighpassFilter()
18 | highpassFilter.inputImage = colorFilter.outputImage
19 | highpassFilter.inputAmount = inputRadius
20 |
21 | let hardLightBlendFilter1 = CIFilter(name: "CIHardLightBlendMode")!
22 | hardLightBlendFilter1.setValue(highpassFilter.outputImage, forKey: kCIInputImageKey)
23 | hardLightBlendFilter1.setValue(highpassFilter.outputImage, forKey: kCIInputBackgroundImageKey)
24 |
25 | let hardLightBlendFilter2 = CIFilter(name: "CIHardLightBlendMode")!
26 | hardLightBlendFilter2.setValue(hardLightBlendFilter1.outputImage, forKey: kCIInputImageKey)
27 | hardLightBlendFilter2.setValue(hardLightBlendFilter1.outputImage, forKey: kCIInputBackgroundImageKey)
28 |
29 | let hardLightBlendFilter3 = CIFilter(name: "CIHardLightBlendMode")!
30 | hardLightBlendFilter3.setValue(hardLightBlendFilter2.outputImage, forKey: kCIInputImageKey)
31 | hardLightBlendFilter3.setValue(hardLightBlendFilter2.outputImage, forKey: kCIInputBackgroundImageKey)
32 |
33 | let toneFilter = CIFilter(name: "CIToneCurve")!
34 | toneFilter.setValue(inputImage, forKey: kCIInputImageKey)
35 | toneFilter.setValue(CIVector(x: 0.0, y: 0), forKey: "inputPoint0")
36 | toneFilter.setValue(CIVector(x: 120/255.0, y: 146/255.0), forKey: "inputPoint1")
37 | toneFilter.setValue(CIVector(x: 1.0, y: 1.0), forKey: "inputPoint2")
38 |
39 | let blendWithMaskFilter = CIFilter(name: "CIBlendWithMask")!
40 | blendWithMaskFilter.setValue(inputImage, forKey: kCIInputImageKey)
41 | blendWithMaskFilter.setValue(toneFilter.outputImage, forKey: kCIInputBackgroundImageKey)
42 | blendWithMaskFilter.setValue(hardLightBlendFilter3.outputImage, forKey: kCIInputMaskImageKey)
43 |
44 | let sharpenFilter = CIFilter(name: "CISharpenLuminance")!
45 | sharpenFilter.setValue(inputSharpness*inputAmount, forKey: kCIInputSharpnessKey)
46 | sharpenFilter.setValue(blendWithMaskFilter.outputImage, forKey: kCIInputImageKey)
47 |
48 | return sharpenFilter.outputImage
49 | }
50 | }
51 |
52 | final class ColorOverlayBlendFilter: CIFilter {
53 |
54 | var inputImage: CIImage?
55 |
56 | override var outputImage : CIImage? {
57 | guard let input = inputImage else {
58 | return nil
59 | }
60 |
61 | let ciImage = Self.kernel.apply(
62 | extent: input.extent,
63 | roiCallback: { index, rect in
64 | return rect
65 | },
66 | arguments: [input]
67 | )
68 | return ciImage
69 | }
70 |
71 | private static var kernel: CIKernel = { () -> CIKernel in
72 |
73 | let url = Bundle.module.url(
74 | forResource: "default",
75 | withExtension: "metallib"
76 | )!
77 |
78 | let data = try! Data(contentsOf: url)
79 | let kernel = try! CIColorKernel(
80 | functionName: "greenBlueOverlayBlendKernel",
81 | fromMetalLibraryData: data
82 | )
83 | return kernel
84 | }()
85 |
86 | }
87 |
88 | final class HighpassFilter: CIFilter {
89 | var inputImage: CIImage?
90 | var blurInputImage: CIImage?
91 | var inputAmount: Double = 5.0
92 |
93 | override public var outputImage: CIImage? {
94 | guard let inputImage = self.inputImage else {
95 | return nil
96 | }
97 | let blurInputImage = self.blurInputImage ?? makeBluredImage(from: inputImage)
98 | guard let highpassKernel = HighpassFilter.kernel else {
99 | return nil
100 | }
101 | return highpassKernel.apply(extent: inputImage.extent, arguments: [inputImage, blurInputImage])
102 | }
103 |
104 | private func makeBluredImage(from inputImage: CIImage) -> CIImage {
105 | return inputImage.clampedToExtent().applyingGaussianBlur(sigma: inputAmount).cropped(to: inputImage.extent)
106 | }
107 |
108 | private static var kernel: CIColorKernel? {
109 | guard let url = Bundle.module.url(forResource: "default", withExtension: "metallib") else { return nil }
110 | guard let data = try? Data(contentsOf: url) else { return nil }
111 | return try? CIColorKernel(functionName: "highpass", fromMetalLibraryData: data)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Tests/SkinSmoothingFilterTests/SkinSmoothingFilterTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SkinSmoothingFilter
3 |
4 | final class SkinSmoothingFilterTests: XCTestCase {
5 | func testExample() throws {
6 | // XCTest Documentation
7 | // https://developer.apple.com/documentation/xctest
8 |
9 | // Defining Test Cases and Test Methods
10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/sample1.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shima11/SkinSmoothingFilter/42875d65aa2f0b14e6d492a7b988fde9fcc51e3f/sample1.JPG
--------------------------------------------------------------------------------
/sample2.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shima11/SkinSmoothingFilter/42875d65aa2f0b14e6d492a7b988fde9fcc51e3f/sample2.JPG
--------------------------------------------------------------------------------