├── .gitignore ├── LICENSE ├── README.md ├── Resources ├── act.jpg ├── act_beauty.jpg ├── landscape.jpg ├── landscape_bilateral.jpg ├── landscape_gaussian.jpg ├── lena.jpg ├── lena_derivatives.jpg ├── lena_laplace.jpg ├── model.jpg └── model_beauty.jpg ├── SwiftImageProcessor.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── SwiftImageProcessor ├── CommandLineProcessor.swift ├── FileExtension.swift ├── Generator.swift ├── Kernel.swift ├── PixelFormat.swift ├── Processor.swift ├── Shader ├── Color │ └── gray.metal ├── Combined │ └── beauty.metal ├── Edge │ ├── derivatives.metal │ ├── laplace.metal │ ├── prewitt.metal │ └── sobel.metal ├── Smoothing │ ├── bilateral.metal │ ├── gauss.metal │ └── median.metal ├── functions.metal └── utilities.h └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | **/.DS_Store 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yuya 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 | # SwiftImageProcessor 2 | This is a sample project for image processing by Metal. Can try on macOS. 3 | 4 | You can see the mechanism [here](https://medium.com/@yuyaHorita/swift-metal-image-processing-75f1c2342306) 5 | 6 | # Edge Detection 7 | Input image. 8 | 9 | ![lena](Resources/lena.jpg) 10 | 11 | Derivatives 12 | 13 | ![lena_derivatives](Resources/lena_derivatives.jpg) 14 | 15 | Laplace 16 | 17 | ![lena_laplace](Resources/lena_laplace.jpg) 18 | 19 | # Smoothing 20 | 21 | Input image. 22 | 23 | ![landscape](Resources/landscape.jpg) 24 | 25 | Gaussian 26 | 27 | ![landscape_derivatives](Resources/landscape_gaussian.jpg) 28 | 29 | Bilateral 30 | 31 | ![landscape_laplace](Resources/landscape_bilateral.jpg) 32 | 33 | # Beauty 34 | 35 | Reference: [here](https://www.csie.ntu.edu.tw/~fuh/personal/FaceBeautificationandColorEnhancement.A2-1-0040.pdf) 36 | 37 | Input image. 38 | 39 | ![Original](Resources/act.jpg) 40 | 41 | Beauty 42 | 43 | ![Beauty](Resources/act_beauty.jpg) 44 | -------------------------------------------------------------------------------- /Resources/act.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/act.jpg -------------------------------------------------------------------------------- /Resources/act_beauty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/act_beauty.jpg -------------------------------------------------------------------------------- /Resources/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/landscape.jpg -------------------------------------------------------------------------------- /Resources/landscape_bilateral.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/landscape_bilateral.jpg -------------------------------------------------------------------------------- /Resources/landscape_gaussian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/landscape_gaussian.jpg -------------------------------------------------------------------------------- /Resources/lena.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/lena.jpg -------------------------------------------------------------------------------- /Resources/lena_derivatives.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/lena_derivatives.jpg -------------------------------------------------------------------------------- /Resources/lena_laplace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/lena_laplace.jpg -------------------------------------------------------------------------------- /Resources/model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/model.jpg -------------------------------------------------------------------------------- /Resources/model_beauty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horita-yuya/SwiftImageProcessor/a0b4665d88430cde3a55751bee76e1a38839e78a/Resources/model_beauty.jpg -------------------------------------------------------------------------------- /SwiftImageProcessor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8C47283622378ED800A371E0 /* gray.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8C47283522378ED800A371E0 /* gray.metal */; }; 11 | 8C4728392238B8FD00A371E0 /* derivatives.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8C4728382238B8FD00A371E0 /* derivatives.metal */; }; 12 | 8C4F3D0522316F19001A97E6 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4F3D0422316F19001A97E6 /* main.swift */; }; 13 | 8C4F3D1422316F6A001A97E6 /* Processor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4F3D1122316F6A001A97E6 /* Processor.swift */; }; 14 | 8C4F3D1522316F6A001A97E6 /* Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4F3D1222316F6A001A97E6 /* Generator.swift */; }; 15 | 8C4F3D1622316F6A001A97E6 /* PixelFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4F3D1322316F6A001A97E6 /* PixelFormat.swift */; }; 16 | 8C58018C225C8F0F0074FF8A /* beauty.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8C58018B225C8F0F0074FF8A /* beauty.metal */; }; 17 | 8C580191225D8D660074FF8A /* functions.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8C580190225D8D660074FF8A /* functions.metal */; }; 18 | 8CA475572231FF970071F6CF /* Kernel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA475562231FF970071F6CF /* Kernel.swift */; }; 19 | 8CA4755B223230B70071F6CF /* FileExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4755A223230B70071F6CF /* FileExtension.swift */; }; 20 | 8CA4755D223236E00071F6CF /* CommandLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4755C223236E00071F6CF /* CommandLineProcessor.swift */; }; 21 | 8CA4756722325D930071F6CF /* median.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756422325D930071F6CF /* median.metal */; }; 22 | 8CA4756822325D930071F6CF /* bilateral.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756522325D930071F6CF /* bilateral.metal */; }; 23 | 8CA4756922325D940071F6CF /* gauss.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756622325D930071F6CF /* gauss.metal */; }; 24 | 8CA4756D22325D9B0071F6CF /* laplace.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756A22325D9B0071F6CF /* laplace.metal */; }; 25 | 8CA4756E22325D9B0071F6CF /* prewitt.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756B22325D9B0071F6CF /* prewitt.metal */; }; 26 | 8CA4756F22325D9B0071F6CF /* sobel.metal in Sources */ = {isa = PBXBuildFile; fileRef = 8CA4756C22325D9B0071F6CF /* sobel.metal */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 8C4F3CFF22316F19001A97E6 /* CopyFiles */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = /usr/share/man/man1/; 34 | dstSubfolderSpec = 0; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 1; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 8C47283522378ED800A371E0 /* gray.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = gray.metal; sourceTree = ""; }; 43 | 8C47283722379B0A00A371E0 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = SOURCE_ROOT; }; 44 | 8C4728382238B8FD00A371E0 /* derivatives.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = derivatives.metal; sourceTree = ""; }; 45 | 8C4F3D0122316F19001A97E6 /* SwiftImageProcessor */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftImageProcessor; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 8C4F3D0422316F19001A97E6 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 47 | 8C4F3D0E22316F4A001A97E6 /* utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utilities.h; sourceTree = ""; }; 48 | 8C4F3D1122316F6A001A97E6 /* Processor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Processor.swift; sourceTree = ""; }; 49 | 8C4F3D1222316F6A001A97E6 /* Generator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Generator.swift; sourceTree = ""; }; 50 | 8C4F3D1322316F6A001A97E6 /* PixelFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PixelFormat.swift; sourceTree = ""; }; 51 | 8C58018B225C8F0F0074FF8A /* beauty.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = beauty.metal; sourceTree = ""; }; 52 | 8C580190225D8D660074FF8A /* functions.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = functions.metal; sourceTree = ""; }; 53 | 8CA475562231FF970071F6CF /* Kernel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Kernel.swift; sourceTree = ""; }; 54 | 8CA4755A223230B70071F6CF /* FileExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileExtension.swift; sourceTree = ""; }; 55 | 8CA4755C223236E00071F6CF /* CommandLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLineProcessor.swift; sourceTree = ""; }; 56 | 8CA4756422325D930071F6CF /* median.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = median.metal; sourceTree = ""; }; 57 | 8CA4756522325D930071F6CF /* bilateral.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = bilateral.metal; sourceTree = ""; }; 58 | 8CA4756622325D930071F6CF /* gauss.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = gauss.metal; sourceTree = ""; }; 59 | 8CA4756A22325D9B0071F6CF /* laplace.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = laplace.metal; sourceTree = ""; }; 60 | 8CA4756B22325D9B0071F6CF /* prewitt.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = prewitt.metal; sourceTree = ""; }; 61 | 8CA4756C22325D9B0071F6CF /* sobel.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = sobel.metal; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 8C4F3CFE22316F19001A97E6 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | 8C47283422378ECC00A371E0 /* Color */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 8C47283522378ED800A371E0 /* gray.metal */, 79 | ); 80 | path = Color; 81 | sourceTree = ""; 82 | }; 83 | 8C4F3CF822316F19001A97E6 = { 84 | isa = PBXGroup; 85 | children = ( 86 | 8C4F3D0322316F19001A97E6 /* SwiftImageProcessor */, 87 | 8C4F3D0222316F19001A97E6 /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 8C4F3D0222316F19001A97E6 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 8C4F3D0122316F19001A97E6 /* SwiftImageProcessor */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 8C4F3D0322316F19001A97E6 /* SwiftImageProcessor */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 8C47283722379B0A00A371E0 /* Resources */, 103 | 8C4F3D0B22316F22001A97E6 /* Shader */, 104 | 8C4F3D0422316F19001A97E6 /* main.swift */, 105 | 8CA4755C223236E00071F6CF /* CommandLineProcessor.swift */, 106 | 8C4F3D1222316F6A001A97E6 /* Generator.swift */, 107 | 8CA4755A223230B70071F6CF /* FileExtension.swift */, 108 | 8C4F3D1322316F6A001A97E6 /* PixelFormat.swift */, 109 | 8C4F3D1122316F6A001A97E6 /* Processor.swift */, 110 | 8CA475562231FF970071F6CF /* Kernel.swift */, 111 | ); 112 | path = SwiftImageProcessor; 113 | sourceTree = ""; 114 | }; 115 | 8C4F3D0B22316F22001A97E6 /* Shader */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 8C58018A225C8EFA0074FF8A /* Combined */, 119 | 8C47283422378ECC00A371E0 /* Color */, 120 | 8CA4756322325D7C0071F6CF /* Smoothing */, 121 | 8CA4756222325D630071F6CF /* Edge */, 122 | 8C4F3D0E22316F4A001A97E6 /* utilities.h */, 123 | 8C580190225D8D660074FF8A /* functions.metal */, 124 | ); 125 | path = Shader; 126 | sourceTree = ""; 127 | }; 128 | 8C58018A225C8EFA0074FF8A /* Combined */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 8C58018B225C8F0F0074FF8A /* beauty.metal */, 132 | ); 133 | path = Combined; 134 | sourceTree = ""; 135 | }; 136 | 8CA4756222325D630071F6CF /* Edge */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 8CA4756A22325D9B0071F6CF /* laplace.metal */, 140 | 8C4728382238B8FD00A371E0 /* derivatives.metal */, 141 | 8CA4756B22325D9B0071F6CF /* prewitt.metal */, 142 | 8CA4756C22325D9B0071F6CF /* sobel.metal */, 143 | ); 144 | path = Edge; 145 | sourceTree = ""; 146 | }; 147 | 8CA4756322325D7C0071F6CF /* Smoothing */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 8CA4756522325D930071F6CF /* bilateral.metal */, 151 | 8CA4756622325D930071F6CF /* gauss.metal */, 152 | 8CA4756422325D930071F6CF /* median.metal */, 153 | ); 154 | path = Smoothing; 155 | sourceTree = ""; 156 | }; 157 | /* End PBXGroup section */ 158 | 159 | /* Begin PBXNativeTarget section */ 160 | 8C4F3D0022316F19001A97E6 /* SwiftImageProcessor */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = 8C4F3D0822316F19001A97E6 /* Build configuration list for PBXNativeTarget "SwiftImageProcessor" */; 163 | buildPhases = ( 164 | 8C4F3CFD22316F19001A97E6 /* Sources */, 165 | 8C4F3CFE22316F19001A97E6 /* Frameworks */, 166 | 8C4F3CFF22316F19001A97E6 /* CopyFiles */, 167 | ); 168 | buildRules = ( 169 | ); 170 | dependencies = ( 171 | ); 172 | name = SwiftImageProcessor; 173 | productName = SwiftImageProcessor; 174 | productReference = 8C4F3D0122316F19001A97E6 /* SwiftImageProcessor */; 175 | productType = "com.apple.product-type.tool"; 176 | }; 177 | /* End PBXNativeTarget section */ 178 | 179 | /* Begin PBXProject section */ 180 | 8C4F3CF922316F19001A97E6 /* Project object */ = { 181 | isa = PBXProject; 182 | attributes = { 183 | LastSwiftUpdateCheck = 1010; 184 | LastUpgradeCheck = 1010; 185 | ORGANIZATIONNAME = horitayuya; 186 | TargetAttributes = { 187 | 8C4F3D0022316F19001A97E6 = { 188 | CreatedOnToolsVersion = 10.1; 189 | }; 190 | }; 191 | }; 192 | buildConfigurationList = 8C4F3CFC22316F19001A97E6 /* Build configuration list for PBXProject "SwiftImageProcessor" */; 193 | compatibilityVersion = "Xcode 9.3"; 194 | developmentRegion = en; 195 | hasScannedForEncodings = 0; 196 | knownRegions = ( 197 | en, 198 | ); 199 | mainGroup = 8C4F3CF822316F19001A97E6; 200 | productRefGroup = 8C4F3D0222316F19001A97E6 /* Products */; 201 | projectDirPath = ""; 202 | projectRoot = ""; 203 | targets = ( 204 | 8C4F3D0022316F19001A97E6 /* SwiftImageProcessor */, 205 | ); 206 | }; 207 | /* End PBXProject section */ 208 | 209 | /* Begin PBXSourcesBuildPhase section */ 210 | 8C4F3CFD22316F19001A97E6 /* Sources */ = { 211 | isa = PBXSourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 8CA4756F22325D9B0071F6CF /* sobel.metal in Sources */, 215 | 8C4F3D0522316F19001A97E6 /* main.swift in Sources */, 216 | 8C4F3D1422316F6A001A97E6 /* Processor.swift in Sources */, 217 | 8CA4756D22325D9B0071F6CF /* laplace.metal in Sources */, 218 | 8C4F3D1622316F6A001A97E6 /* PixelFormat.swift in Sources */, 219 | 8CA4756922325D940071F6CF /* gauss.metal in Sources */, 220 | 8C47283622378ED800A371E0 /* gray.metal in Sources */, 221 | 8C58018C225C8F0F0074FF8A /* beauty.metal in Sources */, 222 | 8CA4756722325D930071F6CF /* median.metal in Sources */, 223 | 8CA4755B223230B70071F6CF /* FileExtension.swift in Sources */, 224 | 8CA4755D223236E00071F6CF /* CommandLineProcessor.swift in Sources */, 225 | 8C580191225D8D660074FF8A /* functions.metal in Sources */, 226 | 8CA475572231FF970071F6CF /* Kernel.swift in Sources */, 227 | 8CA4756E22325D9B0071F6CF /* prewitt.metal in Sources */, 228 | 8C4F3D1522316F6A001A97E6 /* Generator.swift in Sources */, 229 | 8C4728392238B8FD00A371E0 /* derivatives.metal in Sources */, 230 | 8CA4756822325D930071F6CF /* bilateral.metal in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXSourcesBuildPhase section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 8C4F3D0622316F19001A97E6 /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_ENABLE_OBJC_WEAK = YES; 248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_COMMA = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | CODE_SIGN_IDENTITY = "-"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = dwarf; 272 | ENABLE_STRICT_OBJC_MSGSEND = YES; 273 | ENABLE_TESTABILITY = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu11; 275 | GCC_DYNAMIC_NO_PIC = NO; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_OPTIMIZATION_LEVEL = 0; 278 | GCC_PREPROCESSOR_DEFINITIONS = ( 279 | "DEBUG=1", 280 | "$(inherited)", 281 | ); 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | MACOSX_DEPLOYMENT_TARGET = 10.14; 289 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 290 | MTL_FAST_MATH = YES; 291 | ONLY_ACTIVE_ARCH = YES; 292 | SDKROOT = macosx; 293 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 294 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 295 | }; 296 | name = Debug; 297 | }; 298 | 8C4F3D0722316F19001A97E6 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_ENABLE_OBJC_WEAK = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | CODE_SIGN_IDENTITY = "-"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu11; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | MACOSX_DEPLOYMENT_TARGET = 10.14; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | MTL_FAST_MATH = YES; 346 | SDKROOT = macosx; 347 | SWIFT_COMPILATION_MODE = wholemodule; 348 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 349 | }; 350 | name = Release; 351 | }; 352 | 8C4F3D0922316F19001A97E6 /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | CODE_SIGN_STYLE = Automatic; 356 | MTL_COMPILER_FLAGS = ""; 357 | MTL_HEADER_SEARCH_PATHS = "$(TARGET_NAME)/Shader"; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | SWIFT_VERSION = 4.2; 360 | }; 361 | name = Debug; 362 | }; 363 | 8C4F3D0A22316F19001A97E6 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | CODE_SIGN_STYLE = Automatic; 367 | MTL_COMPILER_FLAGS = ""; 368 | MTL_HEADER_SEARCH_PATHS = "$(TARGET_NAME)/Shader"; 369 | PRODUCT_NAME = "$(TARGET_NAME)"; 370 | SWIFT_VERSION = 4.2; 371 | }; 372 | name = Release; 373 | }; 374 | /* End XCBuildConfiguration section */ 375 | 376 | /* Begin XCConfigurationList section */ 377 | 8C4F3CFC22316F19001A97E6 /* Build configuration list for PBXProject "SwiftImageProcessor" */ = { 378 | isa = XCConfigurationList; 379 | buildConfigurations = ( 380 | 8C4F3D0622316F19001A97E6 /* Debug */, 381 | 8C4F3D0722316F19001A97E6 /* Release */, 382 | ); 383 | defaultConfigurationIsVisible = 0; 384 | defaultConfigurationName = Release; 385 | }; 386 | 8C4F3D0822316F19001A97E6 /* Build configuration list for PBXNativeTarget "SwiftImageProcessor" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | 8C4F3D0922316F19001A97E6 /* Debug */, 390 | 8C4F3D0A22316F19001A97E6 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Release; 394 | }; 395 | /* End XCConfigurationList section */ 396 | }; 397 | rootObject = 8C4F3CF922316F19001A97E6 /* Project object */; 398 | } 399 | -------------------------------------------------------------------------------- /SwiftImageProcessor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftImageProcessor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftImageProcessor/CommandLineProcessor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct CommandLineProcessor { 4 | let fileName: String 5 | let fileExtension: FileExtension 6 | let kernel: Kernel 7 | let outFile: String 8 | 9 | // TODO: Option implementation 10 | init() throws { 11 | let arguments = CommandLine.arguments 12 | 13 | let inputFileName: String 14 | let sepecifyedOutFile: String? 15 | 16 | switch arguments.count { 17 | 18 | // case for Xcode Debugging 19 | case 1: 20 | inputFileName = "act.jpg" 21 | self.kernel = .beauty(sigma: 12.0, luminanceSigma: 0.1) 22 | sepecifyedOutFile = nil 23 | 24 | default: 25 | fatalError() 26 | 27 | // ********************* 28 | // Under Construction 29 | // case 3: 30 | // inputFileName = arguments[1] 31 | // self.kernel = Kernel(name: arguments[2], parameters: []) 32 | // sepecifyedOutFile = nil 33 | // 34 | // case 4: 35 | // inputFileName = arguments[1] 36 | // self.kernel = Kernel(name: arguments[2], parameters: []) 37 | // sepecifyedOutFile = arguments[3] 38 | // 39 | // default: 40 | // inputFileName = arguments[1] 41 | // let parameters = arguments[4...].compactMap(Float.init) 42 | // self.kernel = Kernel(name: arguments[2], parameters: parameters) 43 | // sepecifyedOutFile = arguments[3] 44 | } 45 | 46 | let components = inputFileName.components(separatedBy: ".") 47 | 48 | if components.count == 2 { 49 | self.fileName = components[0] 50 | self.fileExtension = FileExtension(rawValue: components[1]) 51 | if let outFile = sepecifyedOutFile { 52 | self.outFile = outFile 53 | } else { 54 | self.outFile = components[0] + "_" + kernel.functionName + "." + components[1] 55 | } 56 | 57 | } else { 58 | throw Error.invalidFormat 59 | } 60 | } 61 | } 62 | 63 | extension CommandLineProcessor { 64 | enum Error: Swift.Error { 65 | case invalidFormat 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SwiftImageProcessor/FileExtension.swift: -------------------------------------------------------------------------------- 1 | enum FileExtension: String { 2 | case jpg 3 | case png 4 | case unknwon 5 | 6 | init(rawValue: String) { 7 | switch rawValue { 8 | case "jpg": self = .jpg 9 | case "jpeg": self = .jpg 10 | case "png": self = .png 11 | 12 | // Use @unknown in the future 13 | default: self = .unknwon 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Generator.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | 3 | struct Generator { 4 | private let texture: MTLTexture 5 | private let pixelFormat: PixelFormat 6 | 7 | init(texture: MTLTexture, pixelFormat: PixelFormat? = nil) { 8 | self.texture = texture 9 | self.pixelFormat = pixelFormat ?? PixelFormat(pixelFormat: texture.pixelFormat) 10 | } 11 | 12 | func run(fileName: String) throws { 13 | let region = MTLRegionMake2D(0, 0, texture.width, texture.height) 14 | let memorySize = MemoryLayout.size 15 | let capacity = texture.width * texture.height * pixelFormat.bitsPerPixel / memorySize 16 | 17 | let bytesPerRow = pixelFormat.bitsPerPixel / memorySize * texture.width 18 | var imageBytes = Array(repeating: 0, count: capacity) 19 | 20 | texture.getBytes(&imageBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0) 21 | 22 | let provider = CGDataProvider(data: NSData(bytes: &imageBytes, length: imageBytes.count * memorySize)) 23 | let colorSpace = CGColorSpaceCreateDeviceRGB() 24 | let bitmap = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.noneSkipLast.rawValue) 25 | let renderingIntent = CGColorRenderingIntent.perceptual 26 | let imageReference = CGImage( 27 | width: texture.width, 28 | height: texture.height, 29 | bitsPerComponent: pixelFormat.bitsPerComponent, 30 | bitsPerPixel: pixelFormat.bitsPerPixel, 31 | bytesPerRow: bytesPerRow, 32 | space: colorSpace, 33 | bitmapInfo: bitmap, 34 | provider: provider!, 35 | decode: nil, 36 | shouldInterpolate: false, 37 | intent: renderingIntent 38 | ) 39 | 40 | let destinationUrl = URL(fileURLWithPath: FileManager.default.currentDirectoryPath + "/" + fileName) 41 | guard let destination = CGImageDestinationCreateWithURL(destinationUrl as CFURL, kUTTypeJPEG, 1, nil) else { 42 | throw Error.failedToCreateDestination(destinationPath: destinationUrl.absoluteString) 43 | } 44 | CGImageDestinationAddImage(destination, imageReference!, nil) 45 | CGImageDestinationFinalize(destination) 46 | } 47 | } 48 | 49 | extension Generator { 50 | enum Error: Swift.Error, CustomStringConvertible { 51 | case failedToCreateDestination(destinationPath: String) 52 | 53 | var description: String { 54 | switch self { 55 | case .failedToCreateDestination(let destinationPath): return "Failed to create destination to '\(destinationPath)'" 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Kernel.swift: -------------------------------------------------------------------------------- 1 | enum Kernel { 2 | case gray_average 3 | case gray_common 4 | case gray_bt709 5 | case gray_bt601 6 | 7 | case gaussian(sigma: Float) 8 | case gaussian_three_dim 9 | case gaussian_five_dim 10 | 11 | case beauty(sigma: Float, luminanceSigma: Float) 12 | case bilateral(sigma: Float, luminanceSigma: Float) 13 | case median 14 | 15 | case derivatives 16 | case sobel 17 | case prewitt 18 | 19 | case laplace 20 | case laplace_eight_surrounding 21 | 22 | case unknown(name: String, parameters: [Float]) 23 | 24 | init(name: String, parameters: [Float]) { 25 | switch name { 26 | case "gray_average": self = .gray_average 27 | case "gray_common": self = .gray_common 28 | case "gray_bt709": self = .gray_bt709 29 | case "gray_bt601": self = .gray_bt601 30 | 31 | case "gaussian": self = .gaussian(sigma: parameters.first ?? 1) 32 | case "gaussian_three_dim": self = .gaussian_three_dim 33 | case "gaussian_five_dim": self = .gaussian_three_dim 34 | 35 | case "beauty": 36 | self = parameters.count == 2 37 | ? .beauty(sigma: parameters[0], luminanceSigma: parameters[1]) 38 | : .beauty(sigma: 1, luminanceSigma: 1) 39 | 40 | case "bilateral": 41 | self = parameters.count == 2 42 | ? .bilateral(sigma: parameters[0], luminanceSigma: parameters[1]) 43 | : .bilateral(sigma: 1, luminanceSigma: 1) 44 | 45 | case "median": self = .median 46 | 47 | case "derivatives": self = .derivatives 48 | case "sobel": self = .sobel 49 | case "prewitt": self = .prewitt 50 | 51 | case "laplace": self = .laplace 52 | case "laplace_eight_surrounding": self = .laplace_eight_surrounding 53 | 54 | default: self = .unknown(name: name, parameters: parameters) 55 | } 56 | } 57 | } 58 | 59 | extension Kernel { 60 | var functionName: String { 61 | switch self { 62 | case .gray_average: return "gray_average" 63 | case .gray_common: return "gray_common" 64 | case .gray_bt709: return "gray_bt709" 65 | case .gray_bt601: return "gray_bt601" 66 | 67 | case .gaussian: return "gaussian" 68 | case .gaussian_three_dim: return "gaussian_three_dim" 69 | case .gaussian_five_dim: return "gaussian_five_dim" 70 | 71 | case .bilateral: return "bilateral" 72 | case .beauty: return "beauty" 73 | 74 | case .median: return "median" 75 | case .derivatives: return "derivatives" 76 | case .sobel: return "sobel" 77 | case .prewitt: return "prewitt" 78 | 79 | case .laplace: return "laplace" 80 | case .laplace_eight_surrounding: return "laplace_eight_surrounding" 81 | 82 | case .unknown(let name, _): return name 83 | } 84 | } 85 | 86 | var parameters: [Float] { 87 | switch self { 88 | case .gaussian(let sigma): return [sigma] 89 | case .bilateral(let sigma, let luminanceSigma): return [sigma, luminanceSigma] 90 | case .beauty(let sigma, let luminanceSigma): return [sigma, luminanceSigma] 91 | case .unknown(_, let parameters): return parameters 92 | 93 | // I want to use @unknown attribute in the future. 94 | default: return [] 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SwiftImageProcessor/PixelFormat.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | 3 | struct PixelFormat { 4 | let pixelFormat: MTLPixelFormat 5 | let bitsPerComponent: Int 6 | let bitsPerPixel: Int 7 | 8 | init(pixelFormat: MTLPixelFormat) { 9 | self.pixelFormat = pixelFormat 10 | 11 | switch pixelFormat { 12 | case .rgba8Unorm: 13 | self.bitsPerComponent = 8 14 | self.bitsPerPixel = 32 15 | 16 | case .bgra8Unorm: 17 | self.bitsPerComponent = 8 18 | self.bitsPerPixel = 32 19 | 20 | default: 21 | fatalError("Unsupported pixelformat is used.") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Processor.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import MetalKit 3 | import simd 4 | 5 | struct Processor { 6 | private let device: MTLDevice 7 | private let library: MTLLibrary 8 | 9 | init() throws { 10 | guard let libraryPath = Bundle.main.path(forResource: "default", ofType: "metallib") else { throw Error.libraryNotFound } 11 | guard let device = MTLCreateSystemDefaultDevice() else { throw Error.systemMetalDeviceNotFound } 12 | 13 | self.device = device 14 | self.library = try device.makeLibrary(filepath: libraryPath) 15 | } 16 | } 17 | 18 | extension Processor { 19 | func run(fileName: String, fileExtension: FileExtension, kernel: Kernel) throws -> MTLTexture { 20 | guard fileExtension != .unknwon else { throw Error.unsuppoertedExtension } 21 | 22 | let commandQueue = device.makeCommandQueue() 23 | let commandBuffer = commandQueue?.makeCommandBuffer() 24 | let commandEncoder = commandBuffer?.makeComputeCommandEncoder() 25 | 26 | guard let kernelFunction = library.makeFunction(name: kernel.functionName) else { 27 | throw Error.kernelFunctionNotFound(inputFunction: kernel.functionName, availableFunctions: library.functionNames) 28 | } 29 | let computePipelineState = try device.makeComputePipelineState(function: kernelFunction) 30 | 31 | let textureLoader = MTKTextureLoader(device: device) 32 | 33 | let imageName = fileName + "." + fileExtension.rawValue 34 | let url = URL(fileURLWithPath: FileManager.default.currentDirectoryPath + "/" + imageName) 35 | 36 | let textureLoaderOption = [ 37 | MTKTextureLoader.Option.allocateMipmaps: NSNumber(value: false), 38 | MTKTextureLoader.Option.SRGB: NSNumber(value: false) 39 | ] 40 | guard let texture = try? textureLoader.newTexture(URL: url, options: textureLoaderOption) else { 41 | throw Error.imageDataNotFound(name: imageName, path: FileManager.default.currentDirectoryPath) 42 | } 43 | 44 | let outTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor( 45 | pixelFormat: .rgba8Unorm, 46 | width: texture.width, 47 | height: texture.height, 48 | mipmapped: false 49 | ) 50 | outTextureDescriptor.usage = [.shaderWrite, .shaderRead] 51 | guard let outTexture = device.makeTexture(descriptor: outTextureDescriptor) else { 52 | throw Error.failedToMakeTexture 53 | } 54 | 55 | let threads = makeThreadgroups(textureWidth: outTexture.width, textureHeight: outTexture.height) 56 | 57 | commandEncoder?.setComputePipelineState(computePipelineState) 58 | commandEncoder?.setTexture(texture, index: 0) 59 | commandEncoder?.setTexture(outTexture, index: 1) 60 | 61 | for (index, parameter) in kernel.parameters.enumerated() { 62 | let buffer = device.makeBuffer(bytes: [parameter], length: MemoryLayout.size, options: []) 63 | commandEncoder?.setBuffer(buffer, offset: 0, index: index) 64 | } 65 | 66 | commandEncoder?.dispatchThreadgroups(threads.threadgroupsPerGrid, threadsPerThreadgroup: threads.threadsPerThreadgroup) 67 | commandEncoder?.endEncoding() 68 | 69 | let syncEncoder = commandBuffer?.makeBlitCommandEncoder() 70 | syncEncoder?.synchronize(resource: outTexture) 71 | syncEncoder?.endEncoding() 72 | 73 | commandBuffer?.commit() 74 | commandBuffer?.waitUntilCompleted() 75 | 76 | return outTexture 77 | } 78 | } 79 | 80 | private extension Processor { 81 | func makeThreadgroups(textureWidth: Int, textureHeight: Int) -> (threadgroupsPerGrid: MTLSize, threadsPerThreadgroup: MTLSize) { 82 | let threadSize = 16 83 | let threadsPerThreadgroup = MTLSizeMake(threadSize, threadSize, 1) 84 | let horizontalThreadgroupCount = textureWidth / threadsPerThreadgroup.width + 1 85 | let verticalThreadgroupCount = textureHeight / threadsPerThreadgroup.height + 1 86 | let threadgroupsPerGrid = MTLSizeMake(horizontalThreadgroupCount, verticalThreadgroupCount, 1) 87 | 88 | return (threadgroupsPerGrid: threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) 89 | } 90 | } 91 | 92 | extension Processor { 93 | enum Error: Swift.Error, CustomStringConvertible { 94 | case libraryNotFound 95 | case systemMetalDeviceNotFound 96 | case unsuppoertedExtension 97 | case imageDataNotFound(name: String, path: String) 98 | case failedToMakeTexture 99 | case kernelFunctionNotFound(inputFunction: String, availableFunctions: [String]) 100 | 101 | var description: String { 102 | switch self { 103 | case .libraryNotFound: return "default.metallib not found." 104 | case .systemMetalDeviceNotFound: return "Seems system metal device is unavailable." 105 | case .unsuppoertedExtension: return "Unsupported file extension is used." 106 | case .imageDataNotFound(let name, let path): return "'\(name)' doesn't exist in '\(path)'" 107 | case .failedToMakeTexture: return "Failed to make texture." 108 | case .kernelFunctionNotFound(let inputFunction, let availableFunctions): 109 | let listText = availableFunctions.reduce("") { acc, value in 110 | return acc + "\(value), " 111 | }.dropLast(2) 112 | return "\(inputFunction) is unavailable.\n" + "Available functions: [" + listText + "]" 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Color/gray.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void gray_average(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | uint2 gid [[ thread_position_in_grid ]]) { 8 | half3 color = inTexture.read(gid).rgb; 9 | half gray = (color.r + color.g + color.b) / 3; 10 | outTexture.write(half4(gray, gray, gray, 1), gid); 11 | } 12 | 13 | kernel void gray_common(texture2d inTexture [[ texture(0) ]], 14 | texture2d outTexture [[ texture(1) ]], 15 | uint2 gid [[ thread_position_in_grid ]]) { 16 | half gray = dot(inTexture.read(gid).rgb, gray_common_factor); 17 | outTexture.write(half4(gray, gray, gray, 1), gid); 18 | } 19 | 20 | kernel void gray_bt709(texture2d inTexture [[ texture(0) ]], 21 | texture2d outTexture [[ texture(1) ]], 22 | uint2 gid [[ thread_position_in_grid ]]) { 23 | half gray = dot(inTexture.read(gid).rgb, bt709); 24 | outTexture.write(half4(gray, gray, gray, 1), gid); 25 | } 26 | 27 | kernel void gray_bt601(texture2d inTexture [[ texture(0) ]], 28 | texture2d outTexture [[ texture(1) ]], 29 | uint2 gid [[ thread_position_in_grid ]]) { 30 | half gray = dot(inTexture.read(gid).rgb, bt601); 31 | outTexture.write(half4(gray, gray, gray, 1), gid); 32 | } 33 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Combined/beauty.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | half kernel_f(half center_luminance, 6 | half surrounding_luminance, 7 | half sigma, 8 | half luminance_sigma, 9 | int2 normalized_position) { 10 | half luminance_gauss = gauss(center_luminance - surrounding_luminance, luminance_sigma); 11 | half space_gauss = gauss(normalized_position.x, sigma) * gauss(normalized_position.y, sigma); 12 | 13 | return space_gauss * luminance_gauss; 14 | } 15 | 16 | kernel void beauty(texture2d inTexture [[ texture(0) ]], 17 | texture2d outTexture [[ texture(1) ]], 18 | constant float &sigma [[ buffer(0) ]], 19 | constant float &luminance_sigma [[ buffer(1) ]], 20 | uint2 gid [[ thread_position_in_grid ]]) { 21 | 22 | // Bilateral 23 | constexpr int kernel_size = 25; 24 | constexpr int radius = kernel_size / 2; 25 | 26 | half3 central_rgb = inTexture.read(gid).rgb; 27 | half3 central_hsv = rgb2hsv(central_rgb); 28 | half kernel_weight = 0; 29 | half center_luminance = central_hsv.z; 30 | for (int j = 0; j <= kernel_size - 1; j++) { 31 | for (int i = 0; i <= kernel_size - 1; i++) { 32 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 33 | half surrounding_luminance = rgb2hsv(inTexture.read(texture_index).rgb).z; 34 | int2 normalized_position(i - radius, j - radius); 35 | 36 | kernel_weight += kernel_f(center_luminance, surrounding_luminance, sigma, luminance_sigma, normalized_position); 37 | } 38 | } 39 | 40 | half bilateral_luminance = 0.0; 41 | for (int j = 0; j <= kernel_size - 1; j++) { 42 | for (int i = 0; i <= kernel_size - 1; i++) { 43 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 44 | half4 texture = inTexture.read(texture_index); 45 | half surrounding_luminance = rgb2hsv(texture.rgb).z; 46 | int2 normalized_position(i - radius, j - radius); 47 | 48 | half factor = kernel_f(center_luminance, surrounding_luminance, sigma, luminance_sigma, normalized_position) / kernel_weight; 49 | bilateral_luminance += factor * surrounding_luminance; 50 | } 51 | } 52 | // Bilateral End 53 | 54 | // Sobel 55 | half3x3 sobel_horizontal_kernel = half3x3(-1, -2, -1, 56 | 0, 0, 0, 57 | 1, 2, 1); 58 | half3x3 sobel_vertical_kernel = half3x3(1, 0, -1, 59 | 2, 0, -2, 60 | 1, 0, -1); 61 | 62 | half3 result_horizontal(0, 0, 0); 63 | half3 result_vertical(0, 0, 0); 64 | for (int j = 0; j <= 2; j++) { 65 | for (int i = 0; i <= 2; i++) { 66 | uint2 texture_index(gid.x + (i - 1), gid.y + (j - 1)); 67 | result_horizontal += sobel_horizontal_kernel[i][j] * inTexture.read(texture_index).rgb; 68 | result_vertical += sobel_vertical_kernel[i][j] * inTexture.read(texture_index).rgb; 69 | } 70 | } 71 | 72 | half gray_horizontal = rgb2hsv(result_horizontal.rgb).z; 73 | half gray_vertical = rgb2hsv(result_vertical.rgb).z; 74 | 75 | half magnitude = length(half2(gray_horizontal, gray_vertical)); 76 | magnitude = abs(1 - magnitude); 77 | if (magnitude > 0.8) { 78 | magnitude = 1; 79 | } else { 80 | magnitude = 0; 81 | } 82 | // Soben End 83 | 84 | // combine 85 | half smooth = bilateral_luminance; 86 | if (magnitude < 0.5) { 87 | half alpha = 0.1; 88 | half smooth_luminance = bilateral_luminance + (center_luminance - bilateral_luminance) * alpha; 89 | smooth = smooth_luminance; 90 | } 91 | 92 | half3 final_color = hsv2rgb(half3(central_hsv.x, central_hsv.y, smooth)); 93 | 94 | // Wever-Fechner Law 95 | final_color = log(1.0 + 0.2 * final_color) / log(1.2); 96 | 97 | outTexture.write(half4(final_color, 1), gid); 98 | } 99 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Edge/derivatives.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void derivatives(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | uint2 gid [[ thread_position_in_grid ]]) { 8 | constexpr int kernel_size = 3; 9 | constexpr int radius = kernel_size / 2; 10 | 11 | half3x3 horizontal_kernel = half3x3(0, 0, 0, 12 | -1, 0, 1, 13 | 0, 0, 0); 14 | half3x3 vertical_kernel = half3x3(0, -1, 0, 15 | 0, 0, 0, 16 | 0, 1, 0); 17 | 18 | half3 result_horizontal(0, 0, 0); 19 | half3 result_vertical(0, 0, 0); 20 | for (int j = 0; j <= kernel_size - 1; j++) { 21 | for (int i = 0; i <= kernel_size - 1; i++) { 22 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 23 | result_horizontal += horizontal_kernel[i][j] * inTexture.read(texture_index).rgb; 24 | result_vertical += vertical_kernel[i][j] * inTexture.read(texture_index).rgb; 25 | } 26 | } 27 | 28 | half gray_horizontal = dot(result_horizontal.rgb, bt601); 29 | half gray_vertical = dot(result_vertical.rgb, bt601); 30 | 31 | half magnitude = length(half2(gray_horizontal, gray_vertical)); 32 | 33 | outTexture.write(half4(half3(magnitude), 1), gid); 34 | } 35 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Edge/laplace.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void laplace(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | uint2 gid [[ thread_position_in_grid ]]) { 8 | constexpr int kernel_size = 3; 9 | constexpr int radius = kernel_size / 2; 10 | 11 | half3x3 laplace_kernel = half3x3(0, 1, 0, 12 | 1, -4, 1, 13 | 0, 1, 0); 14 | 15 | half4 acc_color(0, 0, 0, 0); 16 | for (int j = 0; j <= kernel_size - 1; j++) { 17 | for (int i = 0; i <= kernel_size - 1; i++) { 18 | uint2 textureIndex(gid.x + (i - radius), gid.y + (j - radius)); 19 | acc_color += laplace_kernel[i][j] * inTexture.read(textureIndex).rgba; 20 | } 21 | } 22 | 23 | half value = dot(acc_color.rgb, bt601); 24 | half4 gray_color(value, value, value, 1.0); 25 | 26 | outTexture.write(gray_color, gid); 27 | } 28 | 29 | kernel void laplace_eight_surrounding(texture2d inTexture [[ texture(0) ]], 30 | texture2d outTexture [[ texture(1) ]], 31 | uint2 gid [[ thread_position_in_grid ]]) { 32 | constexpr int kernel_size = 3; 33 | constexpr int radius = kernel_size / 2; 34 | 35 | half3x3 laplace_kernel = half3x3(1, 1, 1, 36 | 1, -8, 1, 37 | 1, 1, 1); 38 | 39 | half4 acc_color(0, 0, 0, 0); 40 | for (int j = 0; j <= kernel_size - 1; j++) { 41 | for (int i = 0; i <= kernel_size - 1; i++) { 42 | uint2 textureIndex(gid.x + (i - radius), gid.y + (j - radius)); 43 | acc_color += laplace_kernel[i][j] * inTexture.read(textureIndex).rgba; 44 | } 45 | } 46 | 47 | half value = dot(acc_color.rgb, bt601); 48 | half4 gray_color(value, value, value, 1.0); 49 | 50 | outTexture.write(gray_color, gid); 51 | } 52 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Edge/prewitt.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void prewitt(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | uint2 gid [[ thread_position_in_grid ]]) { 8 | constexpr int kernel_size = 3; 9 | constexpr int radius = kernel_size / 2; 10 | 11 | half3x3 prewitt_horizontal_kernel = half3x3(-1, -1, -1, 12 | 0, 0, 0, 13 | 1, 1, 1); 14 | half3x3 prewitt_vertical_kernel = half3x3(1, 0, -1, 15 | 1, 0, -1, 16 | 1, 0, -1); 17 | 18 | half3 result_horizontal(0, 0, 0); 19 | half3 result_vertical(0, 0, 0); 20 | for (int j = 0; j <= kernel_size - 1; j++) { 21 | for (int i = 0; i <= kernel_size - 1; i++) { 22 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 23 | result_horizontal += prewitt_horizontal_kernel[i][j] * inTexture.read(texture_index).rgb; 24 | result_vertical += prewitt_vertical_kernel[i][j] * inTexture.read(texture_index).rgb; 25 | } 26 | } 27 | 28 | half gray_horizontal = dot(result_horizontal.rgb, bt601); 29 | half gray_vertical = dot(result_vertical.rgb, bt601); 30 | 31 | half magnitude = length(half2(gray_horizontal, gray_vertical)); 32 | 33 | outTexture.write(half4(half3(magnitude), 1), gid); 34 | } 35 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Edge/sobel.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void sobel(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | uint2 gid [[ thread_position_in_grid ]]) { 8 | constexpr int kernel_size = 3; 9 | constexpr int radius = kernel_size / 2; 10 | 11 | half3x3 sobel_horizontal_kernel = half3x3(-1, -2, -1, 12 | 0, 0, 0, 13 | 1, 2, 1); 14 | half3x3 sobel_vertical_kernel = half3x3(1, 0, -1, 15 | 2, 0, -2, 16 | 1, 0, -1); 17 | 18 | half3 result_horizontal(0, 0, 0); 19 | half3 result_vertical(0, 0, 0); 20 | for (int j = 0; j <= kernel_size - 1; j++) { 21 | for (int i = 0; i <= kernel_size - 1; i++) { 22 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 23 | result_horizontal += sobel_horizontal_kernel[i][j] * inTexture.read(texture_index).rgb; 24 | result_vertical += sobel_vertical_kernel[i][j] * inTexture.read(texture_index).rgb; 25 | } 26 | } 27 | 28 | half gray_horizontal = dot(result_horizontal.rgb, bt601); 29 | half gray_vertical = dot(result_vertical.rgb, bt601); 30 | 31 | half magnitude = length(half2(gray_horizontal, gray_vertical)); 32 | magnitude = abs(1 - magnitude); 33 | 34 | outTexture.write(half4(half3(magnitude), 1), gid); 35 | } 36 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Smoothing/bilateral.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | half kernel_factor(half center_luminance, 6 | half surrounding_luminance, 7 | half sigma, 8 | half luminance_sigma, 9 | int2 normalized_position) { 10 | half luminance_gauss = gauss(center_luminance - surrounding_luminance, luminance_sigma); 11 | half space_gauss = gauss(normalized_position.x, sigma) * gauss(normalized_position.y, sigma); 12 | 13 | return space_gauss * luminance_gauss; 14 | } 15 | 16 | kernel void bilateral(texture2d inTexture [[ texture(0) ]], 17 | texture2d outTexture [[ texture(1) ]], 18 | constant float &sigma [[ buffer(0) ]], 19 | constant float &luminance_sigma [[ buffer(1) ]], 20 | uint2 gid [[ thread_position_in_grid ]]) { 21 | constexpr int kernel_size = 7; 22 | constexpr int radius = kernel_size / 2; 23 | 24 | half kernel_weight = 0; 25 | half center_luminance = dot(inTexture.read(gid).rgb, luminance_vector); 26 | for (int j = 0; j <= kernel_size - 1; j++) { 27 | for (int i = 0; i <= kernel_size - 1; i++) { 28 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 29 | half surrounding_luminance = dot(inTexture.read(texture_index).rgb, luminance_vector); 30 | int2 normalized_position(i - radius, j - radius); 31 | 32 | kernel_weight += kernel_factor(center_luminance, surrounding_luminance, sigma, luminance_sigma, normalized_position); 33 | } 34 | } 35 | 36 | half4 acc_color(0, 0, 0, 0); 37 | for (int j = 0; j <= kernel_size - 1; j++) { 38 | for (int i = 0; i <= kernel_size - 1; i++) { 39 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 40 | half4 texture = inTexture.read(texture_index); 41 | half surrounding_luminance = dot(texture.rgb, luminance_vector); 42 | int2 normalized_position(i - radius, j - radius); 43 | 44 | half factor = kernel_factor(center_luminance, surrounding_luminance, sigma, luminance_sigma, normalized_position) / kernel_weight; 45 | acc_color += factor * texture.rgba; 46 | } 47 | } 48 | 49 | outTexture.write(acc_color, gid); 50 | } 51 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Smoothing/gauss.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | kernel void gaussian(texture2d inTexture [[ texture(0) ]], 6 | texture2d outTexture [[ texture(1) ]], 7 | constant float &sigma [[ buffer(0) ]], 8 | uint2 gid [[ thread_position_in_grid ]]) { 9 | constexpr int kernel_size = 7; 10 | constexpr int radius = kernel_size / 2; 11 | 12 | half kernel_weight = 0; 13 | for (int j = 0; j <= kernel_size - 1; j++) { 14 | for (int i = 0; i <= kernel_size - 1; i++) { 15 | int2 normalized_position(i - radius, j - radius); 16 | kernel_weight += gauss(normalized_position.x, sigma) * gauss(normalized_position.y, sigma); 17 | } 18 | } 19 | 20 | half4 acc_color(0, 0, 0, 0); 21 | for (int j = 0; j <= kernel_size - 1; j++) { 22 | for (int i = 0; i <= kernel_size - 1; i++) { 23 | int2 normalized_position(i - radius, j - radius); 24 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 25 | half factor = gauss(normalized_position.x, sigma) * gauss(normalized_position.y, sigma) / kernel_weight; 26 | acc_color += factor * inTexture.read(texture_index).rgba; 27 | } 28 | } 29 | 30 | outTexture.write(acc_color, gid); 31 | } 32 | 33 | kernel void gaussian_three_dim(texture2d inTexture [[ texture(0) ]], 34 | texture2d outTexture [[ texture(1) ]], 35 | uint2 gid [[ thread_position_in_grid ]]) { 36 | constexpr int kernel_size = 3; 37 | constexpr int radius = kernel_size / 2; 38 | 39 | constexpr half kernel_weight = 16; 40 | half3x3 gauss_kernel = half3x3(1, 2, 1, 41 | 2, 4, 2, 42 | 1, 2, 1); 43 | 44 | half4 acc_color(0, 0, 0, 0); 45 | for (int j = 0; j <= kernel_size - 1; j++) { 46 | for (int i = 0; i <= kernel_size - 1; i++) { 47 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 48 | acc_color += gauss_kernel[i][j] * inTexture.read(texture_index).rgba / kernel_weight; 49 | } 50 | } 51 | 52 | outTexture.write(acc_color, gid); 53 | } 54 | 55 | kernel void gaussian_five_dim(texture2d inTexture [[ texture(0) ]], 56 | texture2d outTexture [[ texture(1) ]], 57 | uint2 gid [[ thread_position_in_grid ]]) { 58 | constexpr int kernel_size = 5; 59 | constexpr int radius = kernel_size / 2; 60 | 61 | constexpr half kernel_weight = 256; 62 | // 5x5 Gauss Kernel 63 | matrix m; 64 | m[0][0] = 1; m[0][1] = 4; m[0][2] = 6; m[0][3] = 4; m[0][4] = 1; 65 | m[1][0] = 4; m[1][1] = 16; m[1][2] = 24; m[1][3] = 16; m[1][4] = 4; 66 | m[2][0] = 6; m[2][1] = 24; m[2][2] = 36; m[2][3] = 24; m[2][4] = 6; 67 | m[3][0] = 4; m[3][1] = 16; m[3][2] = 24; m[3][3] = 16; m[3][4] = 4; 68 | m[4][0] = 1; m[4][1] = 4; m[4][2] = 6; m[4][3] = 4; m[4][4] = 1; 69 | 70 | half4 acc_color(0, 0, 0, 0); 71 | for (int j = 0; j <= kernel_size - 1; j++) { 72 | for (int i = 0; i <= kernel_size - 1; i++) { 73 | uint2 texture_index(gid.x + (i - radius), gid.y + (j - radius)); 74 | acc_color += m[i][j] * inTexture.read(texture_index).rgba / kernel_weight; 75 | } 76 | } 77 | 78 | outTexture.write(acc_color, gid); 79 | } 80 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/Smoothing/median.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utilities.h" 3 | using namespace metal; 4 | 5 | // Under constracting 6 | //kernel void median(texture2d inTexture [[ texture(0) ]], 7 | // texture2d outTexture [[ texture(1) ]], 8 | // uint2 gid [[ thread_position_in_grid ]]) { 9 | // 10 | // outTexture.write(half4(m, m, m, 1), gid); 11 | //} 12 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/functions.metal: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace metal; 3 | 4 | half gauss(half x, half sigma) { 5 | return 1 / sqrt(2 * M_PI_H * sigma * sigma) * exp(-x * x / (2 * sigma * sigma)); 6 | }; 7 | 8 | half3 rgb2hsv(half3 col) { 9 | half r = col.r; 10 | half g = col.g; 11 | half b = col.b; 12 | 13 | half max_value = r > g ? r : g; 14 | max_value = max_value > b ? max_value : b; 15 | half min_value = r < g ? r : g; 16 | min_value = min_value < b ? min_value : b; 17 | 18 | half h = max_value - min_value; 19 | half s = max_value - min_value; 20 | half v = max_value; 21 | 22 | if (h > 0.0h) { 23 | if (max_value == r) { 24 | h = (g - b) / h; 25 | if (h < 0.0h) { 26 | h += 6.0; 27 | } 28 | } else if (max_value == g) { 29 | h = 2.0 * (b - r) / h; 30 | } else { 31 | h = 4.0f + (r - g) / h; 32 | } 33 | } 34 | 35 | h /= 6.0h; 36 | if (max_value != 0.0h) 37 | s /= max_value; 38 | 39 | return half3(h, s, v); 40 | } 41 | 42 | half3 hsv2rgb(half3 col) { 43 | half h = col.x; 44 | half s = col.y; 45 | half v = col.z; 46 | 47 | half r = v; 48 | half g = v; 49 | half b = v; 50 | if (s == 0) { return half3(r, g, b); } 51 | 52 | h *= 6.0h; 53 | int i = int(h); 54 | half f = h - half(i); 55 | 56 | switch (i) { 57 | case 0: 58 | g *= 1 - s * (1 - f); 59 | b *= 1 - s; 60 | break; 61 | case 1: 62 | r *= 1 - s * f; 63 | b *= 1 - s; 64 | break; 65 | case 2: 66 | r *= 1 - s; 67 | b *= 1 - s * (1 - f); 68 | break; 69 | case 3: 70 | r *= 1 - s; 71 | g *= 1 - s * f; 72 | break; 73 | case 4: 74 | r *= 1 - s * (1 - f); 75 | g *= 1 - s; 76 | break; 77 | case 5: 78 | g *= 1 - s; 79 | b *= 1 - s * f; 80 | break; 81 | } 82 | 83 | return half3(r, g, b); 84 | } 85 | -------------------------------------------------------------------------------- /SwiftImageProcessor/Shader/utilities.h: -------------------------------------------------------------------------------- 1 | #ifndef utilities_h 2 | #define utilities_h 3 | #include 4 | 5 | constant half3 gray_common_factor(0.3, 0.59, 0.11); 6 | constant half3 bt709(0.2126, 0.7152, 0.0722); 7 | constant half3 bt601(0.299, 0.587, 0.114); 8 | constant half3 luminance_vector(0.2125, 0.7154, 0.0721); 9 | 10 | half gauss(half x, half sigma); 11 | half3 rgb2hsv(half3 col); 12 | half3 hsv2rgb(half3 col); 13 | #endif 14 | -------------------------------------------------------------------------------- /SwiftImageProcessor/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Metal 3 | import MetalKit 4 | 5 | do { 6 | let commandLine = try CommandLineProcessor() 7 | let processor = try Processor() 8 | let texture = try processor.run( 9 | fileName: commandLine.fileName, 10 | fileExtension: commandLine.fileExtension, 11 | kernel: commandLine.kernel 12 | ) 13 | let generator = Generator(texture: texture) 14 | 15 | try generator.run(fileName: commandLine.outFile) 16 | print("Success") 17 | 18 | } catch { 19 | print(error) 20 | } 21 | --------------------------------------------------------------------------------